Compare commits

..

34 Commits

Author SHA1 Message Date
Yurii Motov
d928bff07f Merge remote-tracking branch 'upstream/master' into check-translation-script 2026-01-07 23:16:58 +01:00
Yurii Motov
cfc9b9430b Refactor translation_fixer.process_one_page - move some logic to doc_parsing_utils.check_translation 2026-01-07 23:11:58 +01:00
github-actions[bot]
e6a08f313d 🎨 Auto format 2026-01-06 10:36:07 +00:00
Yurii Motov
bf8507209a Remove cmpr.py 2026-01-06 11:30:36 +01:00
Yurii Motov
9a96763bad Improve error messages in replace_multiline_code_block 2026-01-06 11:27:26 +01:00
Yurii Motov
b08681fafd Add tests for code blocks 2026-01-06 11:04:52 +01:00
Yurii Motov
6fcc6054ff Only warn about mermaid diagram if it's changed 2026-01-06 11:04:36 +01:00
Yurii Motov
ce8a5ab91c Fix CODE_BLOCK_LANG_RE to handle 4 backticks 2026-01-06 10:38:21 +01:00
Yurii Motov
f5112778d0 Add tests for html links 2026-01-06 09:54:29 +01:00
Yurii Motov
a8bf5871d7 Add tests for markdown links 2026-01-06 08:43:19 +01:00
Yurii Motov
5ca9472d8a Fix check for markdown links number mismatch 2026-01-06 08:26:08 +01:00
Yurii Motov
5c50b3dd15 Add tests for header and permalinks 2026-01-06 07:23:01 +01:00
Yurii Motov
8c5f21c83c Fix error message on header level mismatch 2026-01-06 07:20:38 +01:00
Yurii Motov
6b987b7262 Add more tests for code includes 2026-01-06 06:44:31 +01:00
Yurii Motov
50dd09a7b2 Remove commented code 2026-01-06 06:18:53 +01:00
Yurii Motov
e076f651e7 Increase verbosity of replace_multiline_code_block and replace_multiline_code_blocks_in_text 2026-01-06 06:03:38 +01:00
Yurii Motov
badefaba9f Refactor replace_html_links, improve error message 2026-01-06 05:57:26 +01:00
Yurii Motov
5b812d4754 Improve error message in replace_header_permalinks 2026-01-05 20:17:24 +01:00
Yurii Motov
44b530168e Simplify replace_markdown_links, improve error message 2026-01-05 20:16:59 +01:00
Yurii Motov
6c50c68761 Fix formatting 2026-01-05 20:00:08 +01:00
Yurii Motov
c42dd05cb8 Simplify replace_placeholders_with_code_includes and improve error message 2026-01-05 20:00:08 +01:00
Yurii Motov
aba58fc19d Fix bug with all_good flag 2026-01-05 20:00:08 +01:00
Yurii Motov
c70d79afe9 Add test 2026-01-05 20:00:08 +01:00
Yurii Motov
44f25ad0ac Fix constructing URL for static assets 2026-01-05 16:35:52 +01:00
Yurii Motov
51df013955 Remove debug printing 2026-01-05 14:50:22 +01:00
Yurii Motov
7ff3dfb4fc Add hash-style-comments and slash-style-comments code block languages 2026-01-05 12:10:06 +01:00
Yurii Motov
9aa406d624 Add fix-allcommand. Handle errors (skip document, exit with status code 1 later) 2026-01-05 12:10:06 +01:00
Yurii Motov
b3ad074153 Fix comment regexes (require leading whitespace if comment starts not at the line start) 2026-01-05 12:10:06 +01:00
Yurii Motov
e7fb2453ea Fix header permalinks replacement 2026-01-05 11:17:51 +01:00
Yurii Motov
beff498743 Handle code blocks, fix some bugs, add fix-all command 2025-12-30 17:44:57 +01:00
Yurii Motov
0339277673 Fix links, permalinks, code includes 2025-12-30 10:13:01 +01:00
Yurii Motov
2c56706505 Add more supported languages 2025-12-29 19:32:13 +01:00
Yurii Motov
e15cff7376 Print pure text in non-interactive mode 2025-12-24 16:19:18 +01:00
Yurii Motov
844ded6b43 Add script to compare fixed elements in translated page with En page 2025-12-23 22:46:48 +01:00
79 changed files with 4622 additions and 1827 deletions

View File

@@ -321,8 +321,6 @@ extra:
name: pt - português
- link: /ru/
name: ru - русский язык
- link: /uk/
name: uk - українська мова
extra_css:
- css/termynal.css
- css/custom.css

View File

@@ -1,14 +1,8 @@
# FastAPI { #fastapi }
<style>
.md-content .md-typeset h1 { display: none; }
</style>
<p align="center">
<a href="https://fastapi.tiangolo.com/uk"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
</p>
<p align="center">
<em>Фреймворк FastAPI: висока продуктивність, легко вивчати, швидко писати код, готовий до продакшину</em>
<em>Готовий до продакшину, високопродуктивний, простий у вивченні та швидкий для написання коду фреймворк</em>
</p>
<p align="center">
<a href="https://github.com/fastapi/fastapi/actions?query=workflow%3ATest+event%3Apush+branch%3Amaster" target="_blank">
@@ -27,51 +21,46 @@
---
**Документація**: <a href="https://fastapi.tiangolo.com/uk" target="_blank">https://fastapi.tiangolo.com</a>
**Документація**: <a href="https://fastapi.tiangolo.com" target="_blank">https://fastapi.tiangolo.com</a>
**Вихідний код**: <a href="https://github.com/fastapi/fastapi" target="_blank">https://github.com/fastapi/fastapi</a>
**Програмний код**: <a href="https://github.com/fastapi/fastapi" target="_blank">https://github.com/fastapi/fastapi</a>
---
FastAPI це сучасний, швидкий (високопродуктивний) вебфреймворк для створення API за допомогою Python, що базується на стандартних підказках типів Python.
FastAPI - це сучасний, швидкий (високопродуктивний), вебфреймворк для створення API за допомогою Python,в основі якого лежить стандартна анотація типів Python.
Ключові особливості:
* **Швидкий**: дуже висока продуктивність, на рівні з **NodeJS** та **Go** (завдяки Starlette та Pydantic). [Один із найшвидших Python-фреймворків](#performance).
* **Швидке написання коду**: пришвидшує розробку функціоналу приблизно на 200%300%. *
* **Менше помилок**: зменшує приблизно на 40% кількість помилок, спричинених людиною (розробником). *
* **Інтуїтивний**: чудова підтримка редакторами коду. <abbr title="also known as auto-complete, autocompletion, IntelliSense">Автодоповнення</abbr> всюди. Менше часу на налагодження.
* **Простий**: спроєктований так, щоб бути простим у використанні та вивченні. Менше часу на читання документації.
* **Короткий**: мінімізує дублювання коду. Кілька можливостей з кожного оголошення параметра. Менше помилок.
* **Надійний**: ви отримуєте код, готовий до продакшину. З автоматичною інтерактивною документацією.
* **Заснований на стандартах**: базується на (і повністю сумісний з) відкритими стандартами для API: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (раніше відомий як Swagger) та <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>.
* **Швидкий**: Дуже висока продуктивність, на рівні з **NodeJS** та **Go** (завдяки Starlette та Pydantic). [Один із найшвидших фреймворків](#performance).
<small>* оцінка на основі тестів, проведених внутрішньою командою розробників, що створює продакшн-застосунки.</small>
* **Швидке написання коду**: Пришвидшує розробку функціоналу приблизно на 200%-300%. *
* **Менше помилок**: Зменшить кількість помилок спричинених людиною (розробником) на 40%. *
* **Інтуїтивний**: Чудова підтримка редакторами коду. <abbr title="Також відоме як auto-complete, autocompletion, IntelliSense.">Доповнення</abbr> всюди. Зменште час на налагодження.
* **Простий**: Спроектований, для легкого використання та навчання. Знадобиться менше часу на читання документації.
* **Короткий**: Зведе до мінімуму дублювання коду. Кожен оголошений параметр може виконувати кілька функцій.
* **Надійний**: Ви матимете стабільний код готовий до продакшину з автоматичною інтерактивною документацією.
* **Стандартизований**: Оснований та повністю сумісний з відкритими стандартами для API: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (попередньо відомий як Swagger) та <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>.
## Спонсори { #sponsors }
<small>* оцінка на основі тестів внутрішньої команди розробників, створення продуктових застосунків.</small>
## Спонсори
<!-- sponsors -->
### Ключовий спонсор { #keystone-sponsor }
{% for sponsor in sponsors.keystone -%}
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
{% endfor -%}
### Золоті та срібні спонсори { #gold-and-silver-sponsors }
{% if sponsors %}
{% for sponsor in sponsors.gold -%}
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
{% endfor -%}
{%- for sponsor in sponsors.silver -%}
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
{% endfor %}
{% endif %}
<!-- /sponsors -->
<a href="https://fastapi.tiangolo.com/uk/fastapi-people/#sponsors" class="external-link" target="_blank">Інші спонсори</a>
<a href="https://fastapi.tiangolo.com/fastapi-people/#sponsors" class="external-link" target="_blank">Other sponsors</a>
## Враження { #opinions }
## Враження
"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._"
@@ -99,7 +88,7 @@ FastAPI — це сучасний, швидкий (високопродукти
"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._"
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="https://github.com/hugapi/hug" target="_blank">Hug</a> creator</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="https://github.com/hugapi/hug" target="_blank">Hug</a> creator</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
---
@@ -111,54 +100,50 @@ FastAPI — це сучасний, швидкий (високопродукти
---
"_If anyone is looking to build a production Python API, I would highly recommend **FastAPI**. It is **beautifully designed**, **simple to use** and **highly scalable**, it has become a **key component** in our API first development strategy and is driving many automations and services such as our Virtual TAC Engineer._"
<div style="text-align: right; margin-right: 10%;">Deon Pillsbury - <strong>Cisco</strong> <a href="https://www.linkedin.com/posts/deonpillsbury_cisco-cx-python-activity-6963242628536487936-trAp/" target="_blank"><small>(ref)</small></a></div>
---
## Міні-документальний фільм про FastAPI { #fastapi-mini-documentary }
Наприкінці 2025 року вийшов <a href="https://www.youtube.com/watch?v=mpR8ngthqiE" class="external-link" target="_blank">міні-документальний фільм про FastAPI</a>, ви можете переглянути його онлайн:
<a href="https://www.youtube.com/watch?v=mpR8ngthqiE" target="_blank"><img src="https://fastapi.tiangolo.com/img/fastapi-documentary.jpg" alt="FastAPI Mini Documentary"></a>
## **Typer**, FastAPI для CLI { #typer-the-fastapi-of-clis }
## **Typer**, FastAPI CLI
<a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a>
Якщо ви створюєте застосунок <abbr title="Command Line Interface">CLI</abbr> для використання в терміналі замість веб-API, зверніть увагу на <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>.
Створюючи <abbr title="Command Line Interface">CLI</abbr> застосунок для використання в терміналі, замість веб-API зверніть увагу на <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>.
**Typer** молодший брат FastAPI. І його задумано як **FastAPI для CLI**. ⌨️ 🚀
**Typer** є молодшим братом FastAPI. І це **FastAPI для CLI**. ⌨️ 🚀
## Вимоги { #requirements }
## Вимоги
FastAPI стоїть на плечах гігантів:
* <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> для вебчастини.
* <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> для web частини.
* <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> для частини даних.
## Встановлення { #installation }
Створіть і активуйте <a href="https://fastapi.tiangolo.com/uk/virtual-environments/" class="external-link" target="_blank">віртуальне середовище</a>, а потім встановіть FastAPI:
## Вставновлення
<div class="termy">
```console
$ pip install "fastapi[standard]"
$ pip install fastapi
---> 100%
```
</div>
**Примітка**: переконайтеся, що ви взяли `"fastapi[standard]"` у лапки, щоб це працювало в усіх терміналах.
Вам також знадобиться сервер ASGI для продакшину, наприклад <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a> або <a href="https://github.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>.
## Приклад { #example }
<div class="termy">
### Створіть { #create-it }
```console
$ pip install uvicorn[standard]
Створіть файл `main.py` з:
---> 100%
```
</div>
## Приклад
### Створіть
* Створіть файл `main.py` з:
```Python
from typing import Union
@@ -203,35 +188,22 @@ async def read_item(item_id: int, q: Union[str, None] = None):
**Примітка**:
Якщо ви не знаєте, перегляньте розділ _"In a hurry?"_ про <a href="https://fastapi.tiangolo.com/uk/async/#in-a-hurry" target="_blank">`async` та `await` у документації</a>.
Стикнувшись з проблемами, не зайвим буде ознайомитися з розділом _"In a hurry?"_ про <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank">`async` та `await` у документації</a>.
</details>
### Запустіть { #run-it }
### Запустіть
Запустіть сервер за допомогою:
Запустіть server з:
<div class="termy">
```console
$ fastapi dev main.py
$ uvicorn main:app --reload
╭────────── FastAPI CLI - Development mode ───────────╮
│ │
│ Serving at: http://127.0.0.1:8000 │
│ │
│ API docs: http://127.0.0.1:8000/docs │
│ │
│ Running in development mode, for production use: │
│ │
│ fastapi run │
│ │
╰─────────────────────────────────────────────────────╯
INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [2248755] using WatchFiles
INFO: Started server process [2248757]
INFO: Started reloader process [28720]
INFO: Started server process [28722]
INFO: Waiting for application startup.
INFO: Application startup complete.
```
@@ -239,21 +211,21 @@ INFO: Application startup complete.
</div>
<details markdown="1">
<summary>Про команду <code>fastapi dev main.py</code>...</summary>
<summary>Про команди <code>uvicorn main:app --reload</code>...</summary>
Команда `fastapi dev` читає ваш файл `main.py`, знаходить у ньому застосунок **FastAPI** і запускає сервер за допомогою <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a>.
Команда `uvicorn main:app` посилається на:
За замовчуванням `fastapi dev` запускається з авто-перезавантаженням для локальної розробки.
Докладніше читайте в <a href="https://fastapi.tiangolo.com/uk/fastapi-cli/" target="_blank">документації FastAPI CLI</a>.
* `main`: файл `main.py` ("Модуль" Python).
* `app`: об’єкт створений усередині `main.py` рядком `app = FastAPI()`.
* `--reload`: перезапускає сервер після зміни коду. Використовуйте виключно для розробки.
</details>
### Перевірте { #check-it }
### Перевірте
Відкрийте браузер і перейдіть на <a href="http://127.0.0.1:8000/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>.
Відкрийте браузер та введіть адресу <a href="http://127.0.0.1:8000/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>.
Ви побачите JSON-відповідь:
Ви побачите у відповідь подібний JSON:
```JSON
{"item_id": 5, "q": "somequery"}
@@ -261,32 +233,32 @@ INFO: Application startup complete.
Ви вже створили API, який:
* Отримує HTTP-запити за _шляхами_ `/` та `/items/{item_id}`.
* Отримує HTTP запити за _шляхами_ `/` та `/items/{item_id}`.
* Обидва _шляхи_ приймають `GET` <em>операції</em> (також відомі як HTTP _методи_).
* _Шлях_ `/items/{item_id}` містить _параметр шляху_ `item_id`, який має бути типу `int`.
* _Шлях_ `/items/{item_id}` містить _параметр шляху_ `item_id` який має бути типу `int`.
* _Шлях_ `/items/{item_id}` містить необовʼязковий `str` _параметр запиту_ `q`.
### Інтерактивна документація API { #interactive-api-docs }
### Інтерактивні документації API
Тепер перейдіть на <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Перейдемо сюди <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Ви побачите автоматичну інтерактивну документацію API (надану <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
Ви побачите автоматичну інтерактивну API документацію (створену завдяки <a href="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 }
### Альтернативні документації API
А тепер перейдіть на <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
Тепер перейдемо сюди <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
Ви побачите альтернативну автоматичну документацію (надану <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
Ви побачите альтернативну автоматичну документацію (створену завдяки <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)
## Приклад оновлення { #example-upgrade }
## Приклад оновлення
Тепер змініть файл `main.py`, щоб отримувати тіло `PUT`-запиту.
Тепер модифікуйте файл `main.py`, щоб отримати вміст запиту `PUT`.
Оголосіть тіло, використовуючи стандартні типи Python, завдяки Pydantic.
Оголошуйте вміст запиту за допомогою стандартних типів Python завдяки Pydantic.
```Python hl_lines="4 9-12 25-27"
from typing import Union
@@ -318,41 +290,41 @@ def update_item(item_id: int, item: Item):
return {"item_name": item.name, "item_id": item_id}
```
Сервер `fastapi dev` має автоматично перезавантажитися.
Сервер повинен автоматично перезавантажуватися (тому що Ви додали `--reload` до `uvicorn` команди вище).
### Оновлення інтерактивної документації API { #interactive-api-docs-upgrade }
### Оновлення інтерактивної API документації
Тепер перейдіть на <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Тепер перейдемо сюди <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
* Інтерактивна документація API буде автоматично оновлена, включно з новим тілом:
* Інтерактивна документація API буде автоматично оновлена, включаючи новий вміст:
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png)
* Натисніть кнопку "Try it out", вона дозволяє заповнити параметри та безпосередньо взаємодіяти з API:
* Натисніть кнопку "Try it out", це дозволить вам заповнити параметри та безпосередньо взаємодіяти з API:
![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png)
* Потім натисніть кнопку "Execute", інтерфейс користувача зв'яжеться з вашим API, надішле параметри, отримає результати та покаже їх на екрані:
* Потім натисніть кнопку "Execute", інтерфейс користувача зв'яжеться з вашим API, надішле параметри, у відповідь отримає результати та покаже їх на екрані:
![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png)
### Оновлення альтернативної документації API { #alternative-api-docs-upgrade }
### Оновлення альтернативної API документації
А тепер перейдіть на <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
Зараз перейдемо <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
* Альтернативна документація також відобразить новий параметр запиту та тіло:
* Альтернативна документація також показуватиме новий параметр і вміст запиту:
![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png)
### Підсумки { #recap }
### Підсумки
Отже, ви оголошуєте **один раз** типи параметрів, тіла тощо як параметри функції.
Таким чином, Ви **один раз** оголошуєте типи параметрів, тіла тощо, як параметри функції.
Ви робите це за допомогою стандартних сучасних типів Python.
Вам не потрібно вивчати новий синтаксис, методи чи класи конкретної бібліотеки тощо.
Лише стандартний **Python**.
Використовуючи стандартний **Python**.
Наприклад, для `int`:
@@ -360,35 +332,35 @@ def update_item(item_id: int, item: Item):
item_id: int
```
або для складнішої моделі `Item`:
або для більш складної моделі `Item`:
```Python
item: Item
```
...і з цим єдиним оголошенням ви отримуєте:
...і з цим єдиним оголошенням Ви отримуєте:
* Підтримку редактора, включно з:
* Автодоповненням.
* Перевіркою типів.
* Валідацію даних:
* Автоматичні та зрозумілі помилки, коли дані некоректні.
* Валідацію навіть для глибоко вкладених JSON-обʼєктів.
* <abbr title="also known as: serialization, parsing, marshalling">Перетворення</abbr> вхідних даних: з мережі до даних і типів Python. Читання з:
* Підтримку редактора, включаючи:
* Варіанти заповнення.
* Перевірку типів.
* Перевірку даних:
* Автоматичні та зрозумілі помилки, у разі некоректних даних.
* Перевірка навіть для JSON з високим рівнем вкладеності.
* <abbr title="також відомий як: serialization, parsing, marshalling">Перетворення</abbr> вхідних даних: з мережі до даних і типів Python. Читання з:
* JSON.
* Параметрів шляху.
* Параметрів запиту.
* Cookies.
* Headers.
* Forms.
* Files.
* <abbr title="also known as: serialization, parsing, marshalling">Перетворення</abbr> вихідних даних: перетворення з даних і типів Python у мережеві дані (як JSON):
* Перетворення типів Python (`str`, `int`, `float`, `bool`, `list`, тощо).
* Обʼєктів `datetime`.
* Обʼєктів `UUID`.
* Моделей бази даних.
* Файлів.
* <abbr title="також відомий як: serialization, parsing, marshalling">Перетворення</abbr> вихідних даних: з типів і даних Python до мережевих даних (як JSON):
* Конвертація Python типів (`str`, `int`, `float`, `bool`, `list`, тощо).
* `datetime` об'єкти.
* `UUID` об'єкти.
* Моделі бази даних.
* ...та багато іншого.
* Автоматичну інтерактивну документацію API, включно з 2 альтернативними інтерфейсами користувача:
* Автоматичну інтерактивну документацію API, включаючи 2 альтернативні інтерфейси користувача:
* Swagger UI.
* ReDoc.
@@ -396,26 +368,26 @@ item: Item
Повертаючись до попереднього прикладу коду, **FastAPI**:
* Перевірить, що `item_id` є у шляху для `GET` та `PUT`-запитів.
* Перевірить, що `item_id` має тип `int` для `GET` та `PUT`-запитів.
* Підтвердить наявність `item_id` у шляху для запитів `GET` та `PUT`.
* Підтвердить, що `item_id` має тип `int` для запитів `GET` and `PUT`.
* Якщо це не так, клієнт побачить корисну, зрозумілу помилку.
* Перевірить, чи є необов'язковий параметр запиту з назвою `q` (як у `http://127.0.0.1:8000/items/foo?q=somequery`) для `GET`-запитів.
* Перевірить, чи є необов'язковий параметр запиту з назвою `q` (а саме `http://127.0.0.1:8000/items/foo?q=somequery`) для запитів `GET`.
* Оскільки параметр `q` оголошено як `= None`, він необов'язковий.
* Без `None` він був би обов'язковим (як і тіло у випадку з `PUT`).
* Для `PUT`-запитів до `/items/{item_id}` прочитає тіло як JSON:
* Перевірить, що є обовʼязковий атрибут `name`, який має бути типу `str`.
* Перевірить, що є обовʼязковий атрибут `price`, який має бути типу `float`.
* Перевірить, що є необовʼязковий атрибут `is_offer`, який має бути типу `bool`, якщо він присутній.
* Усе це також працюватиме для глибоко вкладених JSON-обʼєктів.
* Автоматично перетворюватиме з та в JSON.
* Документуватиме все за допомогою OpenAPI, який може бути використано в:
* За відсутності `None` він був би обов'язковим (як і вміст у випадку з `PUT`).
* Для запитів `PUT` із `/items/{item_id}`, читає вміст як JSON:
* Перевірить, чи має обов'язковий атрибут `name` тип `str`.
* Перевірить, чи має обов'язковий атрибут `price` тип `float`.
* Перевірить, чи існує необов'язковий атрибут `is_offer` та чи має він тип `bool`.
* Усе це також працюватиме для глибоко вкладених об'єктів JSON.
* Автоматично конвертує із та в JSON.
* Документує все за допомогою OpenAPI, який може бути використано в:
* Інтерактивних системах документації.
* Системах автоматичної генерації клієнтського коду для багатьох мов.
* Надаватиме безпосередньо 2 вебінтерфейси інтерактивної документації.
* Надає безпосередньо 2 вебінтерфейси інтерактивної документації.
---
Ми лише трішки доторкнулися до поверхні, але ви вже маєте уявлення про те, як усе працює.
Ми лише трішки доторкнулися до коду, але Ви вже маєте уявлення про те, як все працює.
Спробуйте змінити рядок:
@@ -435,131 +407,57 @@ item: Item
... "item_price": item.price ...
```
...і побачите, як ваш редактор автоматично доповнюватиме атрибути та знатиме їхні типи:
...і побачите, як ваш редактор автоматично заповнюватиме атрибути та знатиме їхні типи:
![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png)
Для більш повного прикладу, що включає більше можливостей, перегляньте <a href="https://fastapi.tiangolo.com/uk/tutorial/">Туторіал Посібник користувача</a>.
Для більш повного ознайомлення з додатковими функціями, перегляньте <a href="https://fastapi.tiangolo.com/tutorial/">Туторіал - Посібник Користувача</a>.
**Spoiler alert**: туторіал посібник користувача містить:
**Spoiler alert**: туторіал - посібник користувача містить:
* Оголошення **параметрів** з інших різних місць, як-от: **headers**, **cookies**, **form fields** та **files**.
* Як встановлювати **обмеження валідації** як `maximum_length` або `regex`.
* Дуже потужну і просту у використанні систему **<abbr title="also known as components, resources, providers, services, injectables">Dependency Injection</abbr>**.
* Безпеку та автентифікацію, включно з підтримкою **OAuth2** з **JWT tokens** та **HTTP Basic** auth.
* Оголошення **параметрів** з інших місць як: **headers**, **cookies**, **form fields** та **files**.
* Як встановити **перевірку обмежень** як `maximum_length` або `regex`.
* Дуже потужна і проста у використанні система **<abbr title="також відома як: components, resources, providers, services, injectables">Ін'єкція Залежностей</abbr>**.
* Безпека та автентифікація, включаючи підтримку **OAuth2** з **JWT tokens** та **HTTP Basic** автентифікацію.
* Досконаліші (але однаково прості) техніки для оголошення **глибоко вкладених моделей JSON** (завдяки Pydantic).
* Інтеграцію **GraphQL** з <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> та іншими бібліотеками.
* Багато додаткових можливостей (завдяки Starlette) як-от:
* Багато додаткових функцій (завдяки Starlette) як-от:
* **WebSockets**
* надзвичайно прості тести на основі HTTPX та `pytest`
* **CORS**
* **Cookie Sessions**
* ...та більше.
### Розгортання застосунку (необовʼязково) { #deploy-your-app-optional }
## Продуктивність
За бажання ви можете розгорнути ваш застосунок FastAPI у <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>, перейдіть і приєднайтеся до списку очікування, якщо ви ще цього не зробили. 🚀
Незалежні тести 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). (*)
Якщо у вас вже є обліковий запис **FastAPI Cloud** (ми запросили вас зі списку очікування 😉), ви можете розгорнути ваш застосунок однією командою.
Щоб дізнатися більше про це, перегляньте розділ <a href="https://fastapi.tiangolo.com/benchmarks/" class="internal-link" target="_blank">Benchmarks</a>.
Перед розгортанням переконайтеся, що ви ввійшли в систему:
## Необов'язкові залежності
<div class="termy">
```console
$ fastapi login
You are logged in to FastAPI Cloud 🚀
```
</div>
Потім розгорніть ваш застосунок:
<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 під час їх **розгортання** у хмарі. 🎉
FastAPI Cloud — основний спонсор і джерело фінансування open source проєктів *FastAPI and friends*. ✨
#### Розгортання в інших хмарних провайдерів { #deploy-to-other-cloud-providers }
FastAPI — open source і базується на стандартах. Ви можете розгортати застосунки FastAPI в будь-якому хмарному провайдері, який ви оберете.
Дотримуйтеся інструкцій вашого хмарного провайдера, щоб розгорнути застосунки FastAPI у нього. 🤓
## Продуктивність { #performance }
Незалежні тести 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). (*)
Щоб дізнатися більше, перегляньте розділ <a href="https://fastapi.tiangolo.com/uk/benchmarks/" class="internal-link" target="_blank">Benchmarks</a>.
## Залежності { #dependencies }
FastAPI залежить від Pydantic і Starlette.
### Залежності `standard` { #standard-dependencies }
Коли ви встановлюєте FastAPI за допомогою `pip install "fastapi[standard]"`, ви отримуєте групу необовʼязкових залежностей `standard`:
Використовується Pydantic:
Pydantic використовує:
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email-validator</code></a> - для валідації електронної пошти.
Використовується Starlette:
* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - потрібно, якщо ви хочете використовувати `TestClient`.
* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - потрібно, якщо ви хочете використовувати конфігурацію шаблонів за замовчуванням.
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - потрібно, якщо ви хочете підтримувати <abbr title="converting the string that comes from an HTTP request into Python data">«parsing»</abbr> форм за допомогою `request.form()`.
Використовується FastAPI:
* <a href="https://www.uvicorn.dev" target="_blank"><code>uvicorn</code></a> - для сервера, який завантажує та обслуговує ваш застосунок. Це включає `uvicorn[standard]`, до якого входять деякі залежності (наприклад, `uvloop`), потрібні для високопродуктивної роботи сервера.
* `fastapi-cli[standard]` - щоб надати команду `fastapi`.
* Це включає `fastapi-cloud-cli`, який дозволяє розгортати ваш застосунок FastAPI у <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>.
### Без залежностей `standard` { #without-standard-dependencies }
Якщо ви не хочете включати необовʼязкові залежності `standard`, ви можете встановити через `pip install fastapi` замість `pip install "fastapi[standard]"`.
### Без `fastapi-cloud-cli` { #without-fastapi-cloud-cli }
Якщо ви хочете встановити FastAPI зі стандартними залежностями, але без `fastapi-cloud-cli`, ви можете встановити через `pip install "fastapi[standard-no-fastapi-cloud-cli]"`.
### Додаткові необовʼязкові залежності { #additional-optional-dependencies }
Є ще деякі додаткові залежності, які ви можете захотіти встановити.
Додаткові необовʼязкові залежності Pydantic:
* <a href="https://docs.pydantic.dev/latest/usage/pydantic_settings/" target="_blank"><code>pydantic-settings</code></a> - для керування налаштуваннями.
* <a href="https://docs.pydantic.dev/latest/usage/pydantic_settings/" target="_blank"><code>pydantic-settings</code></a> - для управління налаштуваннями.
* <a href="https://docs.pydantic.dev/latest/usage/types/extra_types/extra_types/" target="_blank"><code>pydantic-extra-types</code></a> - для додаткових типів, що можуть бути використані з Pydantic.
Додаткові необовʼязкові залежності FastAPI:
* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - потрібно, якщо ви хочете використовувати `ORJSONResponse`.
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - потрібно, якщо ви хочете використовувати `UJSONResponse`.
Starlette використовує:
## Ліцензія { #license }
* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - Необхідно, якщо Ви хочете використовувати `TestClient`.
* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - Необхідно, якщо Ви хочете використовувати шаблони як конфігурацію за замовчуванням.
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - Необхідно, якщо Ви хочете підтримувати <abbr title="перетворення рядка, який надходить із запиту HTTP, на дані Python">"розбір"</abbr> форми за допомогою `request.form()`.
* <a href="https://pythonhosted.org/itsdangerous/" target="_blank"><code>itsdangerous</code></a> - Необхідно для підтримки `SessionMiddleware`.
* <a href="https://pyyaml.org/wiki/PyYAMLDocumentation" target="_blank"><code>pyyaml</code></a> - Необхідно для підтримки Starlette `SchemaGenerator` (ймовірно, вам це не потрібно з FastAPI).
FastAPI / Starlette використовують:
* <a href="https://www.uvicorn.dev" target="_blank"><code>uvicorn</code></a> - для сервера, який завантажує та обслуговує вашу програму.
* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - Необхідно, якщо Ви хочете використовувати `ORJSONResponse`.
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - Необхідно, якщо Ви хочете використовувати `UJSONResponse`.
Ви можете встановити все це за допомогою `pip install fastapi[all]`.
## Ліцензія
Цей проєкт ліцензовано згідно з умовами ліцензії MIT.

View File

@@ -1,5 +1,5 @@
# Навчання { #learn }
# Навчання
У цьому розділі надані вступні розділи та навчальні матеріали для вивчення **FastAPI**.
У цьому розділі надані вступні та навчальні матеріали для вивчення FastAPI.
Це можна розглядати як **книгу**, **курс**, або **офіційний** та рекомендований спосіб освоїти FastAPI. 😎

View File

@@ -1,28 +1,29 @@
# Вступ до типів Python { #python-types-intro }
# Вступ до типів Python
Python підтримує додаткові «підказки типів» (також звані «анотаціями типів»).
Python підтримує додаткові "підказки типу" ("type hints") (також звані "анотаціями типу" ("type annotations")).
Ці **«підказки типів»** або анотації — це спеціальний синтаксис, що дозволяє оголошувати <abbr title="наприклад: str, int, float, bool">тип</abbr> змінної.
Ці **"type hints"** є спеціальним синтаксисом, що дозволяє оголошувати <abbr title="наприклад: str, int, float, bool">тип</abbr> змінної.
За допомогою оголошення типів для ваших змінних редактори та інструменти можуть надати вам кращу підтримку.
За допомогою оголошення типів для ваших змінних, редактори та інструменти можуть надати вам кращу підтримку.
Це лише **швидкий туторіал / нагадування** про підказки типів у Python. Він покриває лише мінімум, необхідний щоб використовувати їх з **FastAPI**... що насправді дуже мало.
Це просто **швидкий посібник / нагадування** про анотації типів у Python. Він покриває лише мінімум, необхідний щоб використовувати їх з **FastAPI**... що насправді дуже мало.
**FastAPI** повністю базується на цих підказках типів, вони дають йому багато переваг і користі.
**FastAPI** повністю базується на цих анотаціях типів, вони дають йому багато переваг.
Але навіть якщо ви ніколи не використаєте **FastAPI**, вам буде корисно дізнатись трохи про них.
/// note | Примітка
/// note
Якщо ви експерт у Python і ви вже знаєте все про підказки типів, перейдіть до наступного розділу.
Якщо ви експерт у Python і ви вже знаєте усе про анотації типів - перейдіть до наступного розділу.
///
## Мотивація { #motivation }
## Мотивація
Давайте почнемо з простого прикладу:
{* ../../docs_src/python_types/tutorial001_py39.py *}
{* ../../docs_src/python_types/tutorial001.py *}
Виклик цієї програми виводить:
@@ -33,12 +34,13 @@ John Doe
Функція виконує наступне:
* Бере `first_name` та `last_name`.
* Перетворює першу літеру кожного з них у верхній регістр за допомогою `title()`.
* <abbr title="Об’єднує їх разом, як одне ціле. З вмістом один за одним.">Конкатенує</abbr> їх разом із пробілом по середині.
* Конвертує кожну літеру кожного слова у верхній регістр за допомогою `title()`.
* <abbr title="З’єднує їх, як одне ціле. З вмістом один за одним.">Конкатенує</abbr> їх разом із пробілом по середині.
{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *}
{* ../../docs_src/python_types/tutorial001.py hl[2] *}
### Редагуйте це { #edit-it }
### Редагуйте це
Це дуже проста програма.
@@ -46,11 +48,11 @@ John Doe
У певний момент ви розпочали б визначення функції, у вас були б готові параметри...
Але тоді вам потрібно викликати «той метод, який перетворює першу літеру у верхній регістр».
Але тоді вам потрібно викликати "той метод, який переводить першу літеру у верхній регістр".
Це буде `upper`? Чи `uppercase`? `first_uppercase`? `capitalize`?
Тоді ви спробуєте давнього друга програміста автозаповнення редактора коду.
Тоді ви спробуєте давнього друга програміста - автозаповнення редактора коду.
Ви надрукуєте перший параметр функції, `first_name`, тоді крапку (`.`), а тоді натиснете `Ctrl+Space`, щоб запустити автозаповнення.
@@ -58,7 +60,7 @@ John Doe
<img src="/img/python-types/image01.png">
### Додайте типи { #add-types }
### Додайте типи
Давайте змінимо один рядок з попередньої версії.
@@ -76,9 +78,10 @@ John Doe
Ось і все.
Це «підказки типів»:
Це "type hints":
{* ../../docs_src/python_types/tutorial002.py hl[1] *}
{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}
Це не те саме, що оголошення значень за замовчуванням, як це було б з:
@@ -88,41 +91,43 @@ John Doe
Це зовсім інше.
Ми використовуємо двокрапку (`:`), не знак дорівнює (`=`).
Ми використовуємо двокрапку (`:`), не дорівнює (`=`).
І додавання підказок типів зазвичай не змінює того, що відбувається, порівняно з тим, що відбувалося б без них.
І додавання анотації типу зазвичай не змінює того, що сталось би без них.
Але тепер уявіть, що ви знову посеред процесу створення функції, але з підказками типів.
Але тепер, уявіть що ви посеред процесу створення функції, але з анотаціями типів.
У той самий момент ви спробуєте викликати автозаповнення за допомогою `Ctrl+Space` і побачите:
В цей же момент, ви спробуєте викликати автозаповнення з допомогою `Ctrl+Space` і побачите:
<img src="/img/python-types/image02.png">
Разом з цим ви можете прокручувати, переглядаючи опції, допоки не знайдете ту, що «щось вам підказує»:
Разом з цим, ви можете прокручувати, переглядати опції, допоки ви не знайдете одну, що звучить схоже:
<img src="/img/python-types/image03.png">
## Більше мотивації { #more-motivation }
## Більше мотивації
Перевірте цю функцію, вона вже має підказки типів:
Перевірте цю функцію, вона вже має анотацію типу:
{* ../../docs_src/python_types/tutorial003.py hl[1] *}
{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *}
Оскільки редактор знає типи змінних, ви не тільки отримаєте автозаповнення, ви також отримаєте перевірку помилок:
<img src="/img/python-types/image04.png">
Тепер ви знаєте, щоб виправити це, вам потрібно перетворити `age` у рядок за допомогою `str(age)`:
Тепер ви знаєте, щоб виправити це, вам потрібно перетворити `age` у строку з допомогою `str(age)`:
{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *}
{* ../../docs_src/python_types/tutorial004.py hl[2] *}
## Оголошення типів { #declaring-types }
Щойно ви побачили основне місце для оголошення підказок типів. Як параметри функції.
## Оголошення типів
Щойно ви побачили основне місце для оголошення анотацій типу. Як параметри функції.
Це також основне місце, де ви б їх використовували у **FastAPI**.
### Прості типи { #simple-types }
### Прості типи
Ви можете оголошувати усі стандартні типи у Python, не тільки `str`.
@@ -133,51 +138,78 @@ John Doe
* `bool`
* `bytes`
{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *}
{* ../../docs_src/python_types/tutorial005.py hl[1] *}
### Generic-типи з параметрами типів { #generic-types-with-type-parameters }
### Generic-типи з параметрами типів
Існують деякі структури даних, які можуть містити інші значення, наприклад `dict`, `list`, `set` та `tuple`. І внутрішні значення також можуть мати свій тип.
Ці типи, які мають внутрішні типи, називаються «**generic**» типами. І оголосити їх можна навіть із внутрішніми типами.
Ці типи, які мають внутрішні типи, називаються "**generic**" типами. І оголосити їх можна навіть із внутрішніми типами.
Щоб оголосити ці типи та внутрішні типи, ви можете використовувати стандартний модуль Python `typing`. Він існує спеціально для підтримки цих підказок типів.
Щоб оголосити ці типи та внутрішні типи, ви можете використовувати стандартний модуль Python `typing`. Він існує спеціально для підтримки анотацій типів.
#### Новіші версії Python { #newer-versions-of-python }
#### Новіші версії Python
Синтаксис із використанням `typing` **сумісний** з усіма версіями, від Python 3.6 до останніх, включаючи Python 3.9, Python 3.10 тощо.
У міру розвитку Python **новіші версії** мають покращену підтримку цих анотацій типів і в багатьох випадках вам навіть не потрібно буде імпортувати та використовувати модуль `typing` для оголошення анотацій типів.
У міру розвитку Python **новіші версії** мають покращену підтримку анотацій типів і в багатьох випадках вам навіть не потрібно буде імпортувати та використовувати модуль `typing` для оголошення анотацій типу.
Якщо ви можете вибрати новішу версію Python для свого проекту, ви зможете скористатися цією додатковою простотою.
Якщо ви можете вибрати новішу версію Python для свого проекту, ви зможете скористатися цією додатковою простотою. Дивіться кілька прикладів нижче.
У всій документації є приклади, сумісні з кожною версією Python (коли є різниця).
Наприклад, «**Python 3.6+**» означає, що це сумісно з Python 3.6 або вище (включно з 3.7, 3.8, 3.9, 3.10 тощо). А «**Python 3.9+**» означає, що це сумісно з Python 3.9 або вище (включаючи 3.10 тощо).
Якщо ви можете використовувати **останні версії Python**, використовуйте приклади для останньої версії — вони матимуть **найкращий і найпростіший синтаксис**, наприклад, «**Python 3.10+**».
#### List { #list }
#### List (список)
Наприклад, давайте визначимо змінну, яка буде `list` із `str`.
Оголосіть змінну з тим самим синтаксисом двокрапки (`:`).
//// tab | Python 3.8 і вище
Як тип вкажіть `list`.
З модуля `typing`, імпортуємо `List` (з великої літери `L`):
```Python hl_lines="1"
{!> ../../docs_src/python_types/tutorial006.py!}
```
Оголосимо змінну з тим самим синтаксисом двокрапки (`:`).
Як тип вкажемо `List`, який ви імпортували з `typing`.
Оскільки список є типом, який містить деякі внутрішні типи, ви поміщаєте їх у квадратні дужки:
{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *}
```Python hl_lines="4"
{!> ../../docs_src/python_types/tutorial006.py!}
```
/// info | Інформація
////
Ці внутрішні типи в квадратних дужках називаються «параметрами типу».
//// tab | Python 3.9 і вище
У цьому випадку `str` — це параметр типу, переданий у `list`.
Оголосимо змінну з тим самим синтаксисом двокрапки (`:`).
Як тип вкажемо `list`.
Оскільки список є типом, який містить деякі внутрішні типи, ви поміщаєте їх у квадратні дужки:
```Python hl_lines="1"
{!> ../../docs_src/python_types/tutorial006_py39.py!}
```
////
/// info
Ці внутрішні типи в квадратних дужках називаються "параметрами типу".
У цьому випадку, `str` це параметр типу переданий у `List` (або `list` у Python 3.9 і вище).
///
Це означає: «змінна `items` це `list`, і кожен з елементів у цьому списку `str`».
Це означає: "змінна `items` це `list`, і кожен з елементів у цьому списку - `str`".
/// tip
Якщо ви використовуєте Python 3.9 і вище, вам не потрібно імпортувати `List` з `typing`, ви можете використовувати натомість тип `list`.
///
Зробивши це, ваш редактор може надати підтримку навіть під час обробки елементів зі списку:
@@ -189,42 +221,78 @@ John Doe
І все ж редактор знає, що це `str`, і надає підтримку для цього.
#### Tuple and Set { #tuple-and-set }
#### Tuple and Set (кортеж та набір)
Ви повинні зробити те ж саме, щоб оголосити `tuple` і `set`:
{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *}
//// tab | Python 3.8 і вище
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial007.py!}
```
////
//// tab | Python 3.9 і вище
```Python hl_lines="1"
{!> ../../docs_src/python_types/tutorial007_py39.py!}
```
////
Це означає:
* Змінна `items_t` це `tuple` з 3 елементами: `int`, ще `int`, та `str`.
* Змінна `items_s` це `set`, і кожен його елемент має тип `bytes`.
* Змінна `items_t` це `tuple` з 3 елементами, `int`, ще `int`, та `str`.
* Змінна `items_s` це `set`, і кожен його елемент типу `bytes`.
#### Dict { #dict }
#### Dict (словник)
Щоб оголосити `dict`, вам потрібно передати 2 параметри типу, розділені комами.
Перший параметр типу для ключів у `dict`.
Перший параметр типу для ключа у `dict`.
Другий параметр типу для значень у `dict`:
Другий параметр типу для значення у `dict`:
{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *}
//// tab | Python 3.8 і вище
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial008.py!}
```
////
//// tab | Python 3.9 і вище
```Python hl_lines="1"
{!> ../../docs_src/python_types/tutorial008_py39.py!}
```
////
Це означає:
* Змінна `prices` це `dict`:
* Ключі цього `dict` мають тип `str` (скажімо, назва кожного елементу).
* Значення цього `dict` мають тип `float` (скажімо, ціна кожного елементу).
* Змінна `prices` це `dict`:
* Ключі цього `dict` типу `str` (наприклад, назва кожного елементу).
* Значення цього `dict` типу `float` (наприклад, ціна кожного елементу).
#### Union { #union }
#### Union (об'єднання)
Ви можете оголосити, що змінна може бути будь-яким із **кількох типів**, наприклад `int` або `str`.
Ви можете оголосити, що змінна може бути будь-яким із **кількох типів**, наприклад, `int` або `str`.
У Python 3.6 і вище (включаючи Python 3.10) ви можете використовувати тип `Union` з `typing` і вставляти в квадратні дужки можливі типи, які можна прийняти.
У Python 3.10 також є **новий синтаксис**, у якому ви можете розділити можливі типи за допомогою <abbr title='також називають «побітовим "або" оператором», але це значення тут не актуальне'>вертикальної смуги (`|`)</abbr>.
У Python 3.10 також є **альтернативний синтаксис**, у якому ви можете розділити можливі типи за допомогою <abbr title='також називають «побітовим "або" оператором», але це значення тут не актуальне'>вертикальної смуги (`|`)</abbr>.
//// tab | Python 3.10+
//// tab | Python 3.8 і вище
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial008b.py!}
```
////
//// tab | Python 3.10 і вище
```Python hl_lines="1"
{!> ../../docs_src/python_types/tutorial008b_py310.py!}
@@ -232,24 +300,16 @@ John Doe
////
//// tab | Python 3.9+
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial008b_py39.py!}
```
////
В обох випадках це означає, що `item` може бути `int` або `str`.
#### Можливо `None` { #possibly-none }
#### Possibly `None` (Optional)
Ви можете оголосити, що значення може мати тип, наприклад `str`, але також може бути `None`.
У Python 3.6 і вище (включаючи Python 3.10) ви можете оголосити його, імпортувавши та використовуючи `Optional` з модуля `typing`.
```Python hl_lines="1 4"
{!../../docs_src/python_types/tutorial009_py39.py!}
{!../../docs_src/python_types/tutorial009.py!}
```
Використання `Optional[str]` замість просто `str` дозволить редактору допомогти вам виявити помилки, коли ви могли б вважати, що значенням завжди є `str`, хоча насправді воно також може бути `None`.
@@ -258,7 +318,23 @@ John Doe
Це також означає, що в Python 3.10 ви можете використовувати `Something | None`:
//// tab | Python 3.10+
//// tab | Python 3.8 і вище
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial009.py!}
```
////
//// tab | Python 3.8 і вище - альтернатива
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial009b.py!}
```
////
//// tab | Python 3.10 і вище
```Python hl_lines="1"
{!> ../../docs_src/python_types/tutorial009_py310.py!}
@@ -266,90 +342,32 @@ John Doe
////
//// tab | Python 3.9+
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial009_py39.py!}
```
////
//// tab | Python 3.9+ alternative
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial009b_py39.py!}
```
////
#### Використання `Union` або `Optional` { #using-union-or-optional }
Якщо ви використовуєте версію Python нижче 3.10, ось порада з моєї дуже **суб’єктивної** точки зору:
* 🚨 Уникайте використання `Optional[SomeType]`
* Натомість ✨ **використовуйте `Union[SomeType, None]`** ✨.
Обидва варіанти еквівалентні й «під капотом» це одне й те саме, але я рекомендую `Union` замість `Optional`, тому що слово «**optional**» може створювати враження, ніби значення є необов’язковим, хоча насправді це означає «воно може бути `None`», навіть якщо воно не є необов’язковим і все одно є обов’язковим.
Я вважаю, що `Union[SomeType, None]` більш явно показує, що саме мається на увазі.
Це лише про слова й назви. Але ці слова можуть впливати на те, як ви та ваша команда думаєте про код.
Як приклад, розгляньмо цю функцію:
{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *}
Параметр `name` визначено як `Optional[str]`, але він **не є необов’язковим**, ви не можете викликати функцію без параметра:
```Python
say_hi() # Ой, ні, це викликає помилку! 😱
```
Параметр `name` **все ще є обов’язковим** (не *optional*), тому що він не має значення за замовчуванням. Водночас `name` приймає `None` як значення:
```Python
say_hi(name=None) # Це працює, None є валідним 🎉
```
Добра новина: щойно ви перейдете на Python 3.10, вам не доведеться про це хвилюватися, адже ви зможете просто використовувати `|` для визначення об’єднань типів:
{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *}
І тоді вам не доведеться хвилюватися про назви на кшталт `Optional` і `Union`. 😎
#### Generic типи { #generic-types }
#### Generic типи
Ці типи, які приймають параметри типу у квадратних дужках, називаються **Generic types** or **Generics**, наприклад:
//// tab | Python 3.10+
Ви можете використовувати ті самі вбудовані типи як generic (з квадратними дужками та типами всередині):
* `list`
* `tuple`
* `set`
* `dict`
І так само, як і в попередніх версіях Python, з модуля `typing`:
//// tab | Python 3.8 і вище
* `List`
* `Tuple`
* `Set`
* `Dict`
* `Union`
* `Optional`
* ...та інші.
У Python 3.10 як альтернативу використанню generic `Union` та `Optional` ви можете використовувати <abbr title='також називають «побітовим "або" оператором», але це значення тут не актуальне'>вертикальну смугу (`|`)</abbr> для оголошення об’єднань типів — це значно краще й простіше.
////
//// tab | Python 3.9+
//// tab | Python 3.9 і вище
Ви можете використовувати ті самі вбудовані типи як generic (з квадратними дужками та типами всередині):
Ви можете використовувати ті самі вбудовані типи, як generic (з квадратними дужками та типами всередині):
* `list`
* `tuple`
* `set`
* `dict`
І generic з модуля `typing`:
І те саме, що й у Python 3.8, із модуля `typing`:
* `Union`
* `Optional`
@@ -357,29 +375,46 @@ say_hi(name=None) # Це працює, None є валідним 🎉
////
### Класи як типи { #classes-as-types }
//// tab | Python 3.10 і вище
Ви можете використовувати ті самі вбудовані типи, як generic (з квадратними дужками та типами всередині):
* `list`
* `tuple`
* `set`
* `dict`
І те саме, що й у Python 3.8, із модуля `typing`:
* `Union`
* `Optional` (так само як у Python 3.8)
* ...та інші.
У Python 3.10, як альтернатива використанню `Union` та `Optional`, ви можете використовувати <abbr title='також називають «побітовим "або" оператором», але це значення тут не актуальне'>вертикальну смугу (`|`)</abbr> щоб оголосити об'єднання типів.
////
### Класи як типи
Ви також можете оголосити клас як тип змінної.
Скажімо, у вас є клас `Person` з імʼям:
{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *}
{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
Потім ви можете оголосити змінну типу `Person`:
{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *}
{* ../../docs_src/python_types/tutorial010.py hl[6] *}
І знову ж таки, ви отримуєте всю підтримку редактора:
<img src="/img/python-types/image06.png">
Зверніть увагу, що це означає: «`one_person` — це **екземпляр** класу `Person`».
## Pydantic моделі
Це не означає: «`one_person` — це **клас** з назвою `Person.
## Pydantic моделі { #pydantic-models }
<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> — це бібліотека Python для валідації даних.
<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> це бібліотека Python для валідації даних.
Ви оголошуєте «форму» даних як класи з атрибутами.
@@ -389,11 +424,33 @@ say_hi(name=None) # Це працює, None є валідним 🎉
І ви отримуєте всю підтримку редактора з цим отриманим об’єктом.
Приклад з офіційної документації Pydantic:
Приклад з документації Pydantic:
{* ../../docs_src/python_types/tutorial011_py310.py *}
//// tab | Python 3.8 і вище
/// info | Інформація
```Python
{!> ../../docs_src/python_types/tutorial011.py!}
```
////
//// tab | Python 3.9 і вище
```Python
{!> ../../docs_src/python_types/tutorial011_py39.py!}
```
////
//// tab | Python 3.10 і вище
```Python
{!> ../../docs_src/python_types/tutorial011_py310.py!}
```
////
/// info
Щоб дізнатись більше про <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic, перегляньте його документацію</a>.
@@ -403,43 +460,11 @@ say_hi(name=None) # Це працює, None є валідним 🎉
Ви побачите набагато більше цього всього на практиці в [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}.
/// tip | Порада
## Анотації типів у **FastAPI**
Pydantic має спеціальну поведінку, коли ви використовуєте `Optional` або `Union[Something, None]` без значення за замовчуванням; детальніше про це можна прочитати в документації Pydantic про <a href="https://docs.pydantic.dev/2.3/usage/models/#required-fields" class="external-link" target="_blank">Required Optional fields</a>.
**FastAPI** використовує ці підказки для виконання кількох речей.
///
## Підказки типів з анотаціями метаданих { #type-hints-with-metadata-annotations }
У Python також є можливість додавати **додаткові <abbr title="Дані про дані, у цьому випадку — інформація про тип, наприклад опис.">метадані</abbr>** до цих підказок типів за допомогою `Annotated`.
Починаючи з Python 3.9, `Annotated` є частиною стандартної бібліотеки, тож ви можете імпортувати його з `typing`.
{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *}
Сам Python нічого не робить із цим `Annotated`. А для редакторів та інших інструментів тип усе ще є `str`.
Але ви можете використати це місце в `Annotated`, щоб надати **FastAPI** додаткові метадані про те, як ви хочете, щоб ваш застосунок поводився.
Важливо пам’ятати, що **перший *параметр типу***, який ви передаєте в `Annotated`, — це **фактичний тип**. Решта — це лише метадані для інших інструментів.
Наразі вам просто потрібно знати, що `Annotated` існує і що це стандартний Python. 😎
Пізніше ви побачите, наскільки **потужним** це може бути.
/// tip | Порада
Той факт, що це **стандартний Python**, означає, що ви й надалі отримуватимете **найкращий можливий досвід розробки** у вашому редакторі, з інструментами, які ви використовуєте для аналізу та рефакторингу коду тощо. ✨
А також те, що ваш код буде дуже сумісним із багатьма іншими інструментами та бібліотеками Python. 🚀
///
## Анотації типів у **FastAPI** { #type-hints-in-fastapi }
**FastAPI** використовує ці підказки типів для виконання кількох речей.
З **FastAPI** ви оголошуєте параметри з підказками типів, і отримуєте:
З **FastAPI** ви оголошуєте параметри з підказками типу, і отримуєте:
* **Підтримку редактора**.
* **Перевірку типів**.
@@ -448,17 +473,17 @@ Pydantic має спеціальну поведінку, коли ви вико
* **Визначення вимог**: з параметрів шляху запиту, параметрів запиту, заголовків, тіл, залежностей тощо.
* **Перетворення даних**: із запиту в необхідний тип.
* **Перевірки даних**: що надходять від кожного запиту:
* **Перевірка даних**: що надходять від кожного запиту:
* Генерування **автоматичних помилок**, що повертаються клієнту, коли дані недійсні.
* **Документування** API за допомогою OpenAPI:
* який потім використовується для автоматичної інтерактивної документації користувальницьких інтерфейсів.
Все це може здатися абстрактним. Не хвилюйтеся. Ви побачите все це в дії в [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}.
Все це може здатися абстрактним. Не хвилюйтеся. Ви побачите все це в дії в [Туторіал - Посібник користувача](tutorial/index.md){.internal-link target=_blank}.
Важливо те, що за допомогою стандартних типів Python в одному місці (замість того, щоб додавати більше класів, декораторів тощо), **FastAPI** зробить багато роботи за вас.
/// info | Інформація
/// info
Якщо ви вже пройшли весь туторіал і повернулися, щоб дізнатися більше про типи, ось хороший ресурс: <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">«шпаргалка» від `mypy`</a>.
Якщо ви вже пройшли весь навчальний посібник і повернулися, щоб дізнатися більше про типи, ось хороший ресурс <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">"шпаргалка" від `mypy`</a>.
///

View File

@@ -1,27 +1,28 @@
# Фонові задачі { #background-tasks }
# Фонові задачі
Ви можете створювати фонові задачі, які будуть виконуватися *після* повернення відповіді.
Це корисно для операцій, які потрібно виконати після обробки запиту, але клієнту не обов’язково чекати завершення цієї операції перед отриманням відповіді.
Це включає, наприклад:
Приклади використання:
* Надсилання email-сповіщень після виконання певної дії:
* Підключення до поштового сервера та надсилання листа може займати кілька секунд. Ви можете відразу повернути відповідь, а email-сповіщення надіслати у фоні.
* Підключення до поштового сервера та надсилання листа може займати кілька секунд. Ви можете відразу повернути відповідь, а email відправити у фоні.
* Обробка даних:
* Наприклад, якщо ви отримали файл, який потрібно обробити довготривалим процесом, можна повернути відповідь «Accepted» (HTTP 202) і виконати обробку файлу у фоні.
* Наприклад, якщо отримано файл, який потрібно обробити довготривалим процесом, можна повернути відповідь "Accepted" ("Прийнято", HTTP 202) і виконати обробку файлу у фоні.
## Використання `BackgroundTasks` { #using-backgroundtasks }
## Використання `BackgroundTasks`
Спочатку імпортуйте `BackgroundTasks` і оголосіть параметр у вашій *функції операції шляху* з анотацією типу `BackgroundTasks`:
Спочатку імпортуйте `BackgroundTasks` і додайте його як параметр у Вашу *функцію операції шляху* (path operation function) до `BackgroundTasks`:
{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *}
{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *}
**FastAPI** створить для вас обєкт типу `BackgroundTasks` і передасть його як цей параметр.
**FastAPI** автоматично створить об'єкт `BackgroundTasks` і передасть його у цей параметр.
## Створення функції задачі { #create-a-task-function }
Створіть функцію, яка буде виконуватися як фонова задача.
## Створення функції задачі
Створіть функцію, яка буде виконувати фонову задачу.
Це звичайна функція, яка може отримувати параметри.
@@ -31,54 +32,54 @@
І оскільки операція запису не використовує `async` та `await`, ми визначаємо функцію як звичайну `def`:
{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *}
{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *}
## Додавання фонової задачі { #add-the-background-task }
## Додавання фонової задачі
Усередині вашої *функції операції шляху*, передайте функцію задачі в об'єкт *background tasks*, використовуючи метод `.add_task()`:
Усередині Вашої *функції обробки шляху*, передайте функцію задачі в об'єкт *background tasks*, використовуючи метод `.add_task()`:
{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *}
{* ../../docs_src/background_tasks/tutorial001.py hl[14] *}
`.add_task()` приймає аргументи:
* Функцію задачі, яка буде виконуватися у фоновому режимі (`write_notification`).
* Будь-яку послідовність аргументів, які потрібно передати у функцію задачі у відповідному порядку (`email`).
* Будь-які іменовані аргументи, які потрібно передати у функцію задачі (`message="some notification"`).
* Функція задача, яка буде виконуватися у фоновому режимі (`write_notification`). Зверніть увагу, що передається обʼєкт без дужок.
* Будь-яка послідовність аргументів, які потрібно передати у функцію завдання у відповідному порядку (`email`).
* Будь-які іменовані аргументи, які потрібно передати у функцію задачу (`message="some notification"`).
## Впровадження залежностей { #dependency-injection }
## Впровадження залежностей
Використання `BackgroundTasks` також працює з системою впровадження залежностей. Ви можете оголосити параметр типу `BackgroundTasks` на різних рівнях: у *функції операції шляху*, у залежності (dependable), у підзалежності тощо.
Використання `BackgroundTasks` також працює з системою впровадження залежностей. Ви можете оголосити параметр типу `BackgroundTasks` на різних рівнях: у *функції операції шляху*, у залежності (dependable), у під залежності тощо.
**FastAPI** знає, як діяти в кожному випадку і як повторно використовувати один і той самий об'єкт, щоб усі фонові задачі були об’єднані та виконувалися у фоновому режимі після завершення основного запиту:
**FastAPI** знає, як діяти в кожному випадку і як повторно використовувати один і той самий об'єкт, щоб усі фонові задачі були об’єднані та виконувалися у фоновому режимі після завершення основного запиту.
{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *}
У цьому прикладі повідомлення будуть записані у файл `log.txt` *після* того, як відповідь буде надіслана.
Якщо у запиті був переданий query, він буде записаний у лог у фоновій задачі.
Якщо у запиті був переданий query-параметр, він буде записаний у лог у фоновій задачі.
А потім інша фонова задача, згенерована у *функції операції шляху*, запише повідомлення з використанням path параметра `email`.
А потім інша фонова задача, яка створюється у *функції операції шляху*, запише повідомлення з використанням path параметра `email`.
## Технічні деталі { #technical-details }
## Технічні деталі
Клас `BackgroundTasks` походить безпосередньо з <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">`starlette.background`</a>.
Він імпортується/включається безпосередньо у FastAPI, щоб ви могли імпортувати його з `fastapi` і випадково не імпортували альтернативний `BackgroundTask` (без `s` в кінці) з `starlette.background`.
Він імпортується безпосередньо у FastAPI, щоб Ви могли використовувати його з `fastapi` і випадково не імпортували `BackgroundTask` (без s в кінці) з `starlette.background`.
Якщо використовувати лише `BackgroundTasks` (а не `BackgroundTask`), то його можна передавати як параметр у *функції операції шляху*, і **FastAPI** подбає про все інше, так само як і про використання об'єкта `Request`.
Також можна використовувати `BackgroundTask` окремо в FastAPI, але для цього вам доведеться створити об'єкт у коді та повернути Starlette `Response`, включаючи його.
Також можна використовувати `BackgroundTask` окремо в FastAPI, але для цього Вам доведеться створити об'єкт у коді та повернути Starlette `Response`, включаючи його.
Детальніше можна почитати в <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">офіційній документації Starlette про Background Tasks</a>.
Детальніше можна почитати в <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">офіційній документації Starlette про фонові задачі </a>.
## Застереження { #caveat }
## Застереження
Якщо вам потрібно виконувати складні фонові обчислення, і при цьому нема потреби запускати їх у тому ж процесі (наприклад, не потрібно спільного доступу до пам’яті чи змінних), можливо, варто скористатися більш потужними інструментами, такими як <a href="https://docs.celeryq.dev" class="external-link" target="_blank">Celery</a>.
Якщо Вам потрібно виконувати складні фонові обчислення, і при цьому нема потреби запускати їх у тому ж процесі (наприклад, не потрібно спільного доступу до пам’яті чи змінних), можливо, варто скористатися більш потужними інструментами, такими як <a href="https://docs.celeryq.dev" class="external-link" target="_blank">Celery</a>.
Такі інструменти зазвичай потребують складнішої конфігурації та менеджера черги повідомлень/завдань, наприклад, RabbitMQ або Redis. Однак вони дозволяють виконувати фонові задачі в кількох процесах і особливо — на кількох серверах.
Такі інструменти зазвичай потребують складнішої конфігурації та менеджера черги повідомлень/завдань, наприклад, RabbitMQ або Redis. Однак вони дозволяють виконувати фонові задачі в кількох процесах і навіть на кількох серверах.
Якщо ж вам потрібно отримати доступ до змінних і об’єктів із тієї ж **FastAPI**-програми або виконувати невеликі фонові завдання (наприклад, надсилати email-сповіщення), достатньо просто використовувати `BackgroundTasks`.
Якщо ж Вам потрібно отримати доступ до змінних і об’єктів із тієї ж **FastAPI** - програми або виконувати невеликі фонові завдання (наприклад, надсилати сповіщення електронною поштою), достатньо просто використовувати `BackgroundTasks`.
## Підсумок { #recap }
## Підсумок
Імпортуйте та використовуйте `BackgroundTasks` як параметри у *функціях операції шляху* та залежностях, щоб додавати фонові задачі.
Імпортуйте та використовуйте `BackgroundTasks` як параметр у *функціях операції шляху* та залежностях, щоб додавати фонові задачі.

View File

@@ -1,61 +1,60 @@
# Тіло Поля { #body-fields }
# Тіло - Поля
Так само як ви можете оголошувати додаткову валідацію та метадані в параметрах *функції операції шляху* за допомогою `Query`, `Path` та `Body`, ви можете оголошувати валідацію та метадані всередині моделей Pydantic, використовуючи `Field` від Pydantic.
Так само як ви можете визначати додаткову валідацію та метадані у параметрах *функції обробки шляху* за допомогою `Query`, `Path` та `Body`, ви можете визначати валідацію та метадані всередині моделей Pydantic за допомогою `Field` від Pydantic.
## Імпорт `Field` { #import-field }
## Імпорт `Field`
Спочатку вам потрібно імпортувати це:
{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[4] *}
/// warning
/// warning | Попередження
Зверніть увагу, що `Field` імпортується безпосередньо з `pydantic`, а не з `fastapi`, як усе інше (`Query`, `Path`, `Body` тощо).
Зверніть увагу, що `Field` імпортується прямо з `pydantic`, а не з `fastapi`, як всі інші (`Query`, `Path`, `Body` тощо).
///
## Оголошення атрибутів моделі { #declare-model-attributes }
## Оголошення атрибутів моделі
Потім ви можете використовувати `Field` з атрибутами моделі:
Ви можете використовувати `Field` з атрибутами моделі:
{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[11:14] *}
`Field` працює так само, як `Query`, `Path` і `Body`, має ті самі параметри тощо.
`Field` працює так само, як `Query`, `Path` і `Body`, у нього такі самі параметри тощо.
/// note | Технічні деталі
Насправді `Query`, `Path` та інші, які ви побачите далі, створюють об'єкти підкласів спільного класу `Param`, який сам є підкласом класу `FieldInfo` з Pydantic.
Насправді, `Query`, `Path` та інші, що ви побачите далі, створюють об'єкти підкласів загального класу `Param`, котрий сам є підкласом класу `FieldInfo` з Pydantic.
І `Field` від Pydantic також повертає екземпляр `FieldInfo`.
`Body` також безпосередньо повертає об'єкти підкласу `FieldInfo`. І є інші, які ви побачите пізніше, що є підкласами класу `Body`.
`Body` також безпосередньо повертає об'єкти підкласу `FieldInfo`. І є інші підкласи, які ви побачите пізніше, що є підкласами класу Body.
Пам'ятайте, що коли ви імпортуєте `Query`, `Path` та інші з `fastapi`, це фактично функції, які повертають спеціальні класи.
Пам'ятайте, що коли ви імпортуєте 'Query', 'Path' та інше з 'fastapi', вони фактично є функціями, які повертають спеціальні класи.
///
/// tip | Порада
/// tip
Зверніть увагу, що кожен атрибут моделі з типом, значенням за замовчуванням і `Field` має ту саму структуру, що й параметр *функції операції шляху*, з `Field` замість `Path`, `Query` і `Body`.
Зверніть увагу, що кожен атрибут моделі із типом, значенням за замовчуванням та `Field` має ту саму структуру, що й параметр *функції обробки шляху*, з `Field` замість `Path`, `Query` і `Body`.
///
## Додавання додаткової інформації { #add-extra-information }
## Додавання додаткової інформації
Ви можете оголошувати додаткову інформацію в `Field`, `Query`, `Body` тощо. І вона буде включена до згенерованої JSON Schema.
Ви можете визначити додаткову інформацію у `Field`, `Query`, `Body` тощо. І вона буде включена у згенеровану JSON схему.
Ви дізнаєтеся більше про додавання додаткової інформації пізніше в документації, коли вивчатимете, як оголошувати приклади.
Ви дізнаєтеся більше про додавання додаткової інформації пізніше у документації, коли вивчатимете визначення прикладів.
/// warning | Попередження
/// warning
Додаткові ключі, передані в `Field`, також будуть присутні в отриманій схемі OpenAPI для вашого застосунку.
Оскільки ці ключі не обов'язково є частиною специфікації OpenAPI, деякі інструменти OpenAPI, наприклад [валідатор OpenAPI](https://validator.swagger.io/), можуть не працювати з вашою згенерованою схемою.
Додаткові ключі, передані в `Field`, також будуть присутні у згенерованій схемі OpenAPI для вашого додатка.
Оскільки ці ключі не обов'язково можуть бути частиною специфікації OpenAPI, деякі інструменти OpenAPI, наприклад, [OpenAPI валідатор](https://validator.swagger.io/), можуть не працювати з вашою згенерованою схемою.
///
## Підсумок { #recap }
## Підсумок
Ви можете використовувати `Field` від Pydantic, щоб оголошувати додаткову валідацію та метадані для атрибутів моделі.
Ви можете використовувати `Field` з Pydantic для визначення додаткових перевірок та метаданих для атрибутів моделі.
Ви також можете використовувати додаткові keyword arguments, щоб передавати додаткові метадані JSON Schema.
Ви також можете використовувати додаткові іменовані аргументи для передачі додаткових метаданих JSON схеми.

View File

@@ -1,24 +1,24 @@
# Тіло - Декілька параметрів { #body-multiple-parameters }
# Тіло запиту - Декілька параметрів
Тепер, коли ми побачили, як використовувати `Path` і `Query`, розгляньмо більш просунуті варіанти оголошення тіла запиту.
Тепер, коли ми розглянули використання `Path` та `Query`, розгляньмо більш просунуті способи оголошення тіла запиту в **FastAPI**.
## Змішування `Path`, `Query` та параметрів тіла { #mix-path-query-and-body-parameters }
## Змішування `Path`, `Query` та параметрів тіла запиту
По-перше, звісно, ви можете вільно змішувати оголошення параметрів `Path`, `Query` та тіла запиту, і **FastAPI** знатиме, що робити.
По-перше, звісно, Ви можете вільно змішувати оголошення параметрів `Path`, `Query` та тіла запиту, і **FastAPI** правильно їх обробить.
Також ви можете оголошувати параметри тіла як необов’язкові, встановивши для них значення за замовчуванням `None`:
Також Ви можете оголосити параметри тіла як необов’язкові, встановивши для них значення за замовчуванням `None`:
{* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *}
/// note | Примітка
Зверніть увагу, що в цьому випадку параметр `item`, який береться з тіла, є необов'язковим. Оскільки має значення за замовчуванням `None`.
Зверніть увагу, що в цьому випадку параметр `item`, який береться з тіла запиту, є необов'язковим, оскільки має значення за замовчуванням `None`.
///
## Декілька параметрів тіла { #multiple-body-parameters }
## Декілька параметрів тіла запиту
У попередньому прикладі *операції шляху* очікували б JSON-тіло з атрибутами `Item`, наприклад:
У попередньому прикладі *операція шляху* очікувала JSON з атрибутами `Item`, наприклад:
```JSON
{
@@ -28,15 +28,13 @@
"tax": 3.2
}
```
Але ви також можете оголосити декілька параметрів тіла, наприклад `item` та `user`:
Але Ви також можете оголосити декілька параметрів тіла, наприклад `item` та `user`:
{* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *}
У цьому випадку **FastAPI** розпізнає, що є кілька параметрів тіла (два параметри є моделями Pydantic).
У цьому випадку **FastAPI** помітить, що у функції є більше ніж один параметр тіла (є два параметри, які є моделями Pydantic).
Тож він використає назви параметрів як ключі (назви полів) у тілі та очікуватиме тіло такого вигляду:
Тому він використає назви параметрів як ключі (назви полів) у тілі запиту, очікуючи:
```JSON
{
@@ -55,28 +53,27 @@
/// note | Примітка
Зверніть увагу, що хоча `item` оголошено так само, як і раніше, тепер він очікується всередині тіла з ключем `item`.
Зверніть увагу, що хоча `item` оголошено, так само як і раніше, тепер він очікується в тілі під ключем `item`.
///
**FastAPI** виконає автоматичне перетворення із запиту, щоб параметр `item` отримав свій конкретний вміст, і те ж саме для `user`.
**FastAPI** автоматично конвертує дані із запиту таким чином, щоб параметр `item` отримав свій вміст, і те ж саме стосується `user`.
Він виконає валідацію складених даних і задокументує це таким чином у схемі OpenAPI та в автоматичній документації.
Він виконає валідацію складених даних і задокументує їх відповідним чином у схемі OpenAPI та в автоматичній документації.
## Одиничні значення в тілі { #singular-values-in-body }
## Одиничні значення в тілі запиту
Так само як є `Query` і `Path` для визначення додаткових даних для параметрів запиту та шляху, **FastAPI** надає еквівалентний `Body`.
Наприклад, розширивши попередню модель, ви можете вирішити додати ще один ключ `importance` у те саме тіло, окрім `item` і `user`.
Наприклад, розширюючи попередню модель, Ви можете вирішити додати ще один ключ `importance` в те ж саме тіло запиту разом із `item` і `user`.
Якщо оголосити його як є, оскільки це одиничне значення, **FastAPI** припустить, що це параметр запиту.
Якщо Ви оголосите його як є, то, оскільки це одиничне значення, **FastAPI** припускатиме, що це параметр запиту (query parameter).
Але ви можете вказати **FastAPI** обробляти його як інший ключ тіла, використовуючи `Body`:
Але Ви можете вказати **FastAPI** обробляти його як інший ключ тіла (body key), використовуючи `Body`:
{* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *}
У цьому випадку **FastAPI** очікуватиме тіло такого вигляду:
У цьому випадку **FastAPI** очікуватиме тіло запиту у такому вигляді:
```JSON
{
@@ -93,20 +90,19 @@
"importance": 5
}
```
Знову ж таки, **FastAPI** конвертуватиме типи даних, перевірятиме їх, створюватиме документацію тощо.
Знову ж таки, він перетворюватиме типи даних, перевірятиме, документуватиме тощо.
## Декілька body та query параметрів
## Декілька параметрів тіла та query { #multiple-body-params-and-query }
Звісно, Ви можете оголошувати додаткові query параметри запиту, коли це необхідно, на додаток до будь-яких параметрів тіла запиту.
Звісно, ви також можете оголошувати додаткові query параметри щоразу, коли це потрібно, додатково до будь-яких параметрів тіла.
Оскільки за замовчуванням одиничні значення інтерпретуються як параметри запиту, вам не потрібно явно додавати `Query`, ви можете просто зробити:
Оскільки за замовчуванням окремі значення інтерпретуються як параметри запиту, Вам не потрібно явно додавати `Query`, можна просто використати:
```Python
q: Union[str, None] = None
```
Або в Python 3.10 і вище:
Або в Python 3.10 та вище:
```Python
q: str | None = None
@@ -119,17 +115,17 @@ q: str | None = None
/// info | Інформація
`Body` також має всі ті самі додаткові параметри валідації та метаданих, що й `Query`, `Path` та інші, які ви побачите пізніше.
`Body` також має ті самі додаткові параметри валідації та метаданих, що й `Query`, `Path` та інші, які Ви побачите пізніше.
///
## Вбудувати один параметр тіла { #embed-a-single-body-parameter }
## Вкладений поодинокий параметр тіла запиту
Скажімо, у вас є лише один параметр тіла `item` з моделі Pydantic `Item`.
Припустимо, у вас є лише один параметр тіла запиту `item` з моделі Pydantic `Item`.
За замовчуванням **FastAPI** очікуватиме його тіло безпосередньо.
За замовчуванням **FastAPI** очікуватиме, що тіло запиту міститиме вміст безпосередньо.
Але якщо ви хочете, щоб він очікував JSON з ключем `item`, а всередині нього — вміст моделі, як це відбувається, коли ви оголошуєте додаткові параметри тіла, ви можете використати спеціальний параметр `Body``embed`:
Але якщо Ви хочете, щоб він очікував JSON з ключем `item`, а всередині — вміст моделі (так, як це відбувається при оголошенні додаткових параметрів тіла), Ви можете використати спеціальний параметр `Body``embed`:
```Python
item: Item = Body(embed=True)
@@ -139,8 +135,7 @@ item: Item = Body(embed=True)
{* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *}
У цьому випадку **FastAPI** очікуватиме тіло такого вигляду:
У цьому випадку **FastAPI** очікуватиме тіло запиту такого вигляду:
```JSON hl_lines="2"
{
@@ -164,12 +159,12 @@ item: Item = Body(embed=True)
}
```
## Підсумок { #recap }
## Підсумок
Ви можете додавати кілька параметрів тіла до вашої *функції операції шляху*, навіть якщо запит може мати лише одне тіло.
Ви можете додавати кілька параметрів тіла до Вашої *функції операції шляху* (*path operation function*), навіть якщо запит може мати лише одне тіло.
Але **FastAPI** обробить це, надасть вам правильні дані у функції та перевірить і задокументує правильну схему в *операції шляху*.
Але **FastAPI** обробить це, надасть Вам потрібні дані у функції, перевірить їх та задокументує коректну схему в *операції шляху*.
Також ви можете оголошувати одиничні значення, щоб отримувати їх як частину тіла.
Також Ви можете оголошувати окремі значення, які будуть отримані як частина тіла запиту.
І ви можете вказати **FastAPI** вбудовувати тіло в ключ, навіть коли оголошено лише один параметр.
Крім того, Ви можете вказати **FastAPI** вбудовувати тіло в ключ, навіть якщо оголошено лише один параметр.

View File

@@ -1,8 +1,8 @@
# Тіло - Вкладені моделі { #body-nested-models }
# Тіло запиту - Вкладені моделі
З **FastAPI** ви можете визначати, перевіряти, документувати та використовувати моделі, які можуть бути вкладені на будь-яку глибину (завдяки Pydantic).
З **FastAPI** Ви можете визначати, перевіряти, документувати та використовувати моделі, які можуть бути вкладені на будь-яку глибину (завдяки Pydantic).
## Поля списку { #list-fields }
## Поля списку
Ви можете визначити атрибут як підтип. Наприклад, Python-список (`list`):
@@ -10,28 +10,47 @@
Це зробить `tags` списком, хоча не визначається тип елементів списку.
## Поля списку з параметром типу { #list-fields-with-type-parameter }
## Поля списку з параметром типу
Але Python має специфічний спосіб оголошення списків з внутрішніми типами або «параметрами типу»:
Але Python має специфічний спосіб оголошення списків з внутрішніми типами або "параметрами типу":
### Імпортуємо `List` з модуля typing
### Оголошення `list` з параметром типу { #declare-a-list-with-a-type-parameter }
У Python 3.9 і вище можна використовувати стандартний `list` для оголошення таких типів, як ми побачимо нижче. 💡
Щоб оголосити типи з параметрами типу (внутрішніми типами), такими як `list`, `dict`, `tuple`,
передайте внутрішні тип(и) як «параметри типу», використовуючи квадратні дужки: `[` and `]`
Але в Python версії до 3.9 (від 3.6 і вище) спочатку потрібно імпортувати `List` з модуля стандартної бібліотеки Python `typing`:
{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
### Оголошення `list` з параметром типу
Щоб оголосити типи з параметрами типу (внутрішніми типами), такими як `list`, `dict`, `tuple`:
* Якщо Ви використовуєте версію Python до 3.9, імпортуйте їх відповідну версію з модуля `typing`.
* Передайте внутрішні типи як "параметри типу", використовуючи квадратні дужки: `[` and `]`.
У Python 3.9 це буде виглядати так:
```Python
my_list: list[str]
```
У версіях Python до 3.9 це виглядає так:
```Python
from typing import List
my_list: List[str]
```
Це стандартний синтаксис Python для оголошення типів.
Використовуйте той самий стандартний синтаксис для атрибутів моделей з внутрішніми типами.
Отже, у нашому прикладі, ми можемо зробити `tags` саме «списком рядків»:
Отже, у нашому прикладі, ми можемо зробити `tags` саме "списком рядків":
{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *}
## Типи множин { #set-types }
## Типи множин
Але потім ми подумали, що теги не повинні повторюватися, вони, ймовірно, повинні бути унікальними рядками.
@@ -41,29 +60,29 @@ my_list: list[str]
{* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *}
Навіть якщо ви отримаєте запит з дубльованими даними, він буде перетворений у множину унікальних елементів.
Навіть якщо Ви отримаєте запит з дубльованими даними, він буде перетворений у множину унікальних елементів.
І коли ви будете виводити ці дані, навіть якщо джерело містить дублікати, вони будуть виведені як множина унікальних елементів.
І коли Ви будете виводити ці дані, навіть якщо джерело містить дублікати, вони будуть виведені як множина унікальних елементів.
І це буде анотовано/документовано відповідно.
## Вкладені моделі { #nested-models }
## Вкладені моделі
Кожен атрибут моделі Pydantic має тип.
Але цей тип сам може бути іншою моделлю Pydantic.
Отже, ви можете оголосити глибоко вкладені JSON «об'єкти» з конкретними іменами атрибутів, типами та перевірками.
Отже, Ви можете оголосити глибоко вкладені JSON "об'єкти" з конкретними іменами атрибутів, типами та перевірками.
Усе це, вкладене без обмежень.
### Визначення підмоделі { #define-a-submodel }
### Визначення підмоделі
Наприклад, ми можемо визначити модель `Image`:
{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *}
### Використання підмоделі як типу { #use-the-submodel-as-a-type }
### Використання підмоделі як типу
А потім ми можемо використовувати її як тип атрибута:
@@ -85,16 +104,16 @@ my_list: list[str]
}
```
Завдяки такій декларації у **FastAPI** ви отримуєте:
Завдяки такій декларації у **FastAPI** Ви отримуєте:
* Підтримку в редакторі (автозавершення тощо), навіть для вкладених моделей
* Конвертацію даних
* Валідацію даних
* Автоматичну документацію
## Спеціальні типи та валідація { #special-types-and-validation }
## Спеціальні типи та валідація
Окрім звичайних типів, таких як `str`, `int`, `float`, та ін. ви можете використовувати складніші типи, які наслідують `str`.
Окрім звичайних типів, таких як `str`, `int`, `float`, та ін. Ви можете використовувати складніші типи, які наслідують `str`.
Щоб побачити всі доступні варіанти, ознайомтеся з оглядом <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">типів у Pydantic</a>. Деякі приклади будуть у наступних розділах.
@@ -104,9 +123,9 @@ my_list: list[str]
Рядок буде перевірено як дійсну URL-адресу і задокументовано в JSON Schema / OpenAPI як URL.
## Атрибути зі списками підмоделей { #attributes-with-lists-of-submodels }
## Атрибути зі списками підмоделей
У Pydantic ви можете використовувати моделі як підтипи для `list`, `set` тощо:
У Pydantic Ви можете використовувати моделі як підтипи для `list`, `set` тощо:
{* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *}
@@ -142,7 +161,7 @@ my_list: list[str]
///
## Глибоко вкладені моделі { #deeply-nested-models }
## Глибоко вкладені моделі
Ви можете визначати вкладені моделі довільної глибини:
@@ -154,9 +173,14 @@ my_list: list[str]
///
## Тіла запитів, що складаються зі списків { #bodies-of-pure-lists }
## Тіла запитів, що складаються зі списків
Якщо верхній рівень JSON тіла, яке ви очікуєте, є JSON `масивом` (у Python — `list`), ви можете оголосити тип у параметрі функції, як і в моделях Pydantic:
Якщо верхній рівень JSON тіла, яке Ви очікуєте, є JSON `масивом` (у Python — `list`), Ви можете оголосити тип у параметрі функції, як і в моделях Pydantic:
```Python
images: List[Image]
```
або в Python 3.9 і вище:
```Python
images: list[Image]
@@ -166,7 +190,7 @@ images: list[Image]
{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *}
## Підтримка в редакторі всюди { #editor-support-everywhere }
## Підтримка в редакторі всюди
Ви отримаєте підтримку в редакторі всюди.
@@ -176,23 +200,23 @@ images: list[Image]
Ви не змогли б отримати таку підтримку в редакторі, якби працювали напряму зі `dict`, а не з моделями Pydantic.
Але вам не потрібно турбуватися про це: вхідні dict'и автоматично конвертуються, а вихідні дані автоматично перетворюються в JSON.
Але Вам не потрібно турбуватися про це: вхідні dict'и автоматично конвертуються, а вихідні дані автоматично перетворюються в JSON.
## Тіла з довільними `dict` { #bodies-of-arbitrary-dicts }
## Тіла з довільними `dict`
Ви також можете оголосити тіло як `dict` з ключами одного типу та значеннями іншого типу.
Це корисно, якщо ви не знаєте наперед, які імена полів будуть дійсними (як у випадку з моделями Pydantic).
Це корисно, якщо Ви не знаєте наперед, які імена полів будуть дійсними (як у випадку з моделями Pydantic).
Це буде корисно, якщо ви хочете приймати ключі, які заздалегідь невідомі.
Це буде корисно, якщо Ви хочете приймати ключі, які заздалегідь невідомі.
---
Це також зручно, якщо ви хочете мати ключі іншого типу (наприклад, `int`).
Це також зручно, якщо Ви хочете мати ключі іншого типу (наприклад, `int`).
Ось що ми розглянемо далі.
У цьому випадку ви можете приймати будь-який `dict`, якщо його ключі — це `int`, а значення — `float`:
У цьому випадку Ви можете приймати будь-який `dict`, якщо його ключі — це `int`, а значення — `float`:
{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *}
@@ -204,18 +228,18 @@ images: list[Image]
Це означає, що навіть якщо клієнти вашого API надсилатимуть ключі у вигляді рядків, якщо вони містять цілі числа, Pydantic конвертує їх і проведе валідацію.
Тобто `dict`, який ви отримаєте як `weights`, матиме ключі типу `int` та значення типу `float`.
Тобто `dict`, який Ви отримаєте як `weights`, матиме ключі типу `int` та значення типу `float`.
///
## Підсумок { #recap }
## Підсумок
З **FastAPI** ви маєте максимальну гнучкість завдяки моделям Pydantic, зберігаючи при цьому код простим, коротким та елегантним.
З **FastAPI** Ви маєте максимальну гнучкість завдяки моделям Pydantic, зберігаючи при цьому код простим, коротким та елегантним.
А також отримуєте всі переваги:
* Підтримка в редакторі (автодоповнення всюди!)
* Конвертація даних (парсинг/сериалізація)
* Валідацію даних
* Валідація даних
* Документація схем
* Автоматичне створення документації

View File

@@ -1,8 +1,8 @@
# Тіло Оновлення { #body-updates }
# Тіло Оновлення
## Оновлення із заміною за допомогою `PUT` { #update-replacing-with-put }
## Оновлення з використанням `PUT`
Щоб оновити елемент, ви можете використати <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT" class="external-link" target="_blank">HTTP `PUT`</a> операцію.
Щоб оновити елемент, Ви можете використати <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT" class="external-link" target="_blank">HTTP `PUT`</a> операцію.
Ви можете використати `jsonable_encoder`, щоб перетворити вхідні дані на такі, які можна зберігати як JSON (наприклад, у NoSQL базі даних). Наприклад, перетворюючи `datetime` у `str`.
@@ -10,7 +10,7 @@
`PUT` використовується для отримання даних, які мають замінити чинні дані.
### Попередження про заміну { #warning-about-replacing }
### Попередження про заміну
Це означає, що якщо Ви хочете оновити елемент `bar`, використовуючи `PUT` з тілом:
@@ -26,7 +26,7 @@
І дані будуть збережені з цим "новим" значенням `tax` = `10.5`.
## Часткові оновлення з `PATCH` { #partial-updates-with-patch }
## Часткові оновлення з `PATCH`
Ви також можете використовувати операцію <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> для *часткового* оновлення даних.
@@ -34,37 +34,53 @@
/// note | Примітка
`PATCH` менш поширений і менш відомий, ніж `PUT`.
`PATCH` менш відомий і рідше використовується, ніж `PUT`.
І багато команд використовують лише `PUT`, навіть для часткових оновлень.
Ви **вільні** використовувати їх так, як хочете, **FastAPI** не накладає жодних обмежень.
Ви **вільні** використовувати їх так, як хочете, **FastAPI** не накладає обмежень.
Але цей посібник показує вам, більш-менш, як їх задумано використовувати.
Але цей посібник показує Вам більш-менш як їх задумано використовувати.
///
### Використання параметра `exclude_unset` у Pydantic { #using-pydantics-exclude-unset-parameter }
### Використання параметра `exclude_unset` у Pydantic
Якщо Ви хочете отримувати часткові оновлення, дуже корисно використовувати параметр `exclude_unset` у `.model_dump()` моделі Pydantic.
Якщо Ви хочете отримати часткові оновлення, дуже зручно використовувати параметр `exclude_unset` у методі `.model_dump()` моделі Pydantic.
Наприклад: `item.model_dump(exclude_unset=True)`.
Це згенерує `dict` лише з тими даними, які були встановлені під час створення моделі `item`, виключаючи значення за замовчуванням.
/// info | Інформація
Тоді Ви можете використовувати це, щоб згенерувати `dict` лише з даними, які були встановлені (надіслані у запиті), пропускаючи значення за замовчуванням:
У Pydantic v1 цей метод називався `.dict()`, він був застарілий (але все ще підтримується) у Pydantic v2, і був перейменований у `.model_dump()`.
Приклади тут використовують `.dict()` для сумісності з Pydantic v1, але Вам слід використовувати `.model_dump()`, якщо можете використовувати Pydantic v2.
///
Це створить `dict` лише з тими даними, які були явно встановлені під час створення моделі `item`, виключаючи значення за замовчуванням.
Тоді Ви можете використовувати це, щоб створити `dict` лише з даними, які були встановлені (надіслані у запиті), пропускаючи значення за замовчуванням:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *}
### Використання параметра `update` у Pydantic { #using-pydantics-update-parameter }
### Використання параметра `update` у Pydantic
Тепер Ви можете створити копію наявної моделі за допомогою `.model_copy()`, і передати параметр `update` з `dict`, який містить дані для оновлення.
Тепер Ви можете створити копію наявної моделі за допомогою `.model_copy()`, і передати параметр `update` з `dict` , який містить дані для оновлення.
/// info | Інформація
У Pydantic v1 метод називався `.copy()`, він був застарілий (але все ще підтримується) у Pydantic v2, і був перейменований у `.model_copy()`.
Приклади тут використовують `.copy()` для сумісності з Pydantic v1, але якщо Ви можете використовувати Pydantic v2 — Вам слід використовувати `.model_copy()` замість цього.
///
Наприклад: `stored_item_model.model_copy(update=update_data)`:
{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *}
### Підсумок часткових оновлень { #partial-updates-recap }
### Підсумок часткових оновлень
У підсумку, щоб застосувати часткові оновлення, Ви:
@@ -85,7 +101,7 @@
Насправді Ви можете використовувати цю саму техніку і з операцією HTTP `PUT`.
Але приклад тут використовує `PATCH`, тому що він був створений для таких випадків.
Але приклад тут використовує `PATCH`, тому що він був створений саме для таких випадків.
///
@@ -93,7 +109,7 @@
Зверніть увагу, що модель запиту все ще проходить валідацію.
Тож, якщо Ви хочете отримувати часткові оновлення, які можуть пропускати всі атрибути, Вам потрібно мати модель, де всі атрибути позначені як необов’язкові (зі значеннями за замовчуванням або `None`).
Тож, якщо Ви хочете отримувати часткові оновлення, які можуть не містити жодного атрибута, Вам потрібно мати модель, де всі атрибути позначені як необов’язкові (зі значеннями за замовчуванням або `None`).
Щоб розрізняти моделі з усіма необов’язковими значеннями для **оновлення** і моделі з обов’язковими значеннями для **створення**, Ви можете скористатись ідеями, описаними у [Додаткові моделі](extra-models.md){.internal-link target=_blank}.

View File

@@ -1,14 +1,14 @@
# Тіло запиту { #request-body }
# Тіло запиту
Коли вам потрібно надіслати дані з клієнта (скажімо, браузера) до вашого API, ви надсилаєте їх як **тіло запиту**.
Тіло **запиту** — це дані, надіслані клієнтом до вашого API. Тіло **відповіді** — це дані, які ваш API надсилає клієнту.
Ваш API майже завжди має надсилати тіло **відповіді**. Але клієнтам не обов’язково потрібно постійно надсилати тіла **запитів** — інколи вони лише запитують шлях, можливо з деякими параметрами запиту, але не надсилають тіло.
Ваш API майже завжди має надсилати тіло **відповіді**. Але клієнтам не обов’язково потрібно постійно надсилати тіла **запитів**.
Щоб оголосити тіло **запиту**, ви використовуєте <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> моделі з усією їх потужністю та перевагами.
/// info | Інформація
/// info
Щоб надіслати дані, ви повинні використовувати один із: `POST` (більш поширений), `PUT`, `DELETE` або `PATCH`.
@@ -18,22 +18,21 @@
///
## Імпортуйте `BaseModel` від Pydantic { #import-pydantics-basemodel }
## Імпортуйте `BaseModel` від Pydantic
Спочатку вам потрібно імпортувати `BaseModel` з `pydantic`:
{* ../../docs_src/body/tutorial001_py310.py hl[2] *}
{* ../../docs_src/body/tutorial001.py hl[4] *}
## Створіть свою модель даних { #create-your-data-model }
## Створіть свою модель даних
Потім ви оголошуєте свою модель даних як клас, який успадковується від `BaseModel`.
Використовуйте стандартні типи Python для всіх атрибутів:
{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *}
{* ../../docs_src/body/tutorial001.py hl[7:11] *}
Так само, як і при оголошенні параметрів запиту, коли атрибут моделі має значення за замовчуванням, він не є обов’язковим. В іншому випадку це потрібно. Використовуйте `None`, щоб зробити його просто необов'язковим.
Так само, як і при оголошенні параметрів запиту, коли атрибут моделі має значення за замовчуванням, він не є обов’язковим. В іншому випадку це потрібно. Використовуйте `None`, щоб зробити його необов'язковим.
Наприклад, ця модель вище оголошує JSON "`об'єкт`" (або Python `dict`), як:
@@ -55,15 +54,15 @@
}
```
## Оголосіть її як параметр { #declare-it-as-a-parameter }
## Оголоси її як параметр
Щоб додати модель даних до вашої *операції шляху*, оголосіть її так само, як ви оголосили параметри шляху та запиту:
{* ../../docs_src/body/tutorial001_py310.py hl[16] *}
{* ../../docs_src/body/tutorial001.py hl[18] *}
...і вкажіть її тип як модель, яку ви створили, `Item`.
## Результати { #results }
## Результати
Лише з цим оголошенням типу Python **FastAPI** буде:
@@ -74,9 +73,9 @@
* Надавати отримані дані у параметрі `item`.
* Оскільки ви оголосили його у функції як тип `Item`, ви також матимете всю підтримку редактора (автозаповнення, тощо) для всіх атрибутів та їх типів.
* Генерувати <a href="https://json-schema.org" class="external-link" target="_blank">JSON Schema</a> визначення для вашої моделі, ви також можете використовувати їх де завгодно, якщо це має сенс для вашого проекту.
* Ці схеми будуть частиною згенерованої схеми OpenAPI і використовуватимуться автоматичною документацією <abbr title="User Interfaces Інтерфейси користувача">UIs</abbr>.
* Ці схеми будуть частиною згенерованої схеми OpenAPI і використовуватимуться автоматичною документацією інтерфейсу користувача.
## Автоматична документація { #automatic-docs }
## Автоматична документація
Схеми JSON ваших моделей будуть частиною вашої схеми, згенерованої OpenAPI, і будуть показані в інтерактивній API документації:
@@ -86,7 +85,7 @@
<img src="/img/tutorial/body/image02.png">
## Підтримка редактора { #editor-support }
## Підтримка редактора
У вашому редакторі, всередині вашої функції, ви будете отримувати підказки типу та завершення скрізь (це б не сталося, якби ви отримали `dict` замість моделі Pydantic):
@@ -108,7 +107,7 @@
<img src="/img/tutorial/body/image05.png">
/// tip | Порада
/// tip
Якщо ви використовуєте <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> як ваш редактор, ви можете використати <a href="https://github.com/koxudaxi/pydantic-pycharm-plugin/" class="external-link" target="_blank">Pydantic PyCharm Plugin</a>.
@@ -122,45 +121,42 @@
///
## Використовуйте модель { #use-the-model }
## Використовуйте модель
Усередині функції ви можете отримати прямий доступ до всіх атрибутів об’єкта моделі:
{* ../../docs_src/body/tutorial002_py310.py *}
{* ../../docs_src/body/tutorial002.py hl[21] *}
## Тіло запиту + параметри шляху { #request-body-path-parameters }
## Тіло запиту + параметри шляху
Ви можете одночасно оголошувати параметри шляху та тіло запиту.
**FastAPI** розпізнає, що параметри функції, які відповідають параметрам шляху, мають бути **взяті з шляху**, а параметри функції, які оголошуються як моделі Pydantic, **взяті з тіла запиту**.
{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *}
{* ../../docs_src/body/tutorial003.py hl[17:18] *}
## Тіло запиту + шлях + параметри запиту { #request-body-path-query-parameters }
## Тіло запиту + шлях + параметри запиту
Ви також можете оголосити параметри **тіло**, **шлях** і **запит** одночасно.
**FastAPI** розпізнає кожен з них і візьме дані з потрібного місця.
{* ../../docs_src/body/tutorial004_py310.py hl[16] *}
{* ../../docs_src/body/tutorial004.py hl[18] *}
Параметри функції будуть розпізнаватися наступним чином:
* Якщо параметр також оголошено в **шляху**, він використовуватиметься як параметр шляху.
* Якщо параметр має **сингулярний тип** (наприклад, `int`, `float`, `str`, `bool` тощо), він буде інтерпретуватися як параметр **запиту**.
* Якщо параметр оголошується як тип **Pydantic моделі**, він інтерпретується як **тіло** **запиту**.
* Якщо параметр оголошується як тип **Pydantic моделі**, він інтерпретується як **тіло** запиту.
/// note | Примітка
/// note
FastAPI буде знати, що значення `q` не є обов'язковим через значення за замовчуванням `= None`.
FastAPI буде знати, що значення "q" не є обов'язковим через значення за замовчуванням "= None".
`str | None` (Python 3.10+) або `Union` у `Union[str, None]` (Python 3.9+) FastAPI не використовує, щоб визначити, що значення не є обов’язковим — він знатиме, що воно не є обов’язковим, тому що має значення за замовчуванням `= None`.
Але додавання анотацій типів дозволить вашому редактору надати вам кращу підтримку та виявляти помилки.
`Optional` у `Optional[str]` не використовується FastAPI, але дозволить вашому редактору надати вам кращу підтримку та виявляти помилки.
///
## Без Pydantic { #without-pydantic }
## Без Pydantic
Якщо ви не хочете використовувати моделі Pydantic, ви також можете використовувати параметри **Body**. Перегляньте документацію для [Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.
Якщо ви не хочете використовувати моделі Pydantic, ви також можете використовувати параметри **Body**. Перегляньте документацію для [Тіло Кілька параметрів: сингулярні значення в тілі](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}.

View File

@@ -1,32 +1,32 @@
# Моделі параметрів Cookie { #cookie-parameter-models }
# Моделі для Cookie-параметрів
Якщо у Вас є група **cookies**, які пов'язані між собою, Ви можете створити **Pydantic-модель**, щоб оголосити їх. 🍪
Якщо у Вас є група **cookies** параметрів, які пов'язані між собою, Ви можете створити **Pydantic-модель**, щоб оголосити їх. 🍪
Це дозволить Вам повторно **використовувати модель** у **різних місцях**, а також оголосити валідацію та метадані для всіх параметрів одночасно. 😎
/// note | Примітка
/// note | Нотатки
Це підтримується з версії FastAPI `0.115.0`. 🤓
Це підтримується з версії FastAPI `0.115.0`. 🤓
///
/// tip | Порада
Ця ж техніка застосовується до `Query`, `Cookie` та `Header`. 😎
Ця ж техніка застосовується до `Query`, `Cookie`, та `Header`. 😎
///
## Cookie з Pydantic-моделлю { #cookies-with-a-pydantic-model }
## Cookie з Pydantic-моделлю
Оголосіть **cookie**-параметри, які Вам потрібні, у **Pydantic-моделі**, а потім оголосіть параметр як `Cookie`:
Оголосіть **cookie-параметри**, які Вам потрібні, у **Pydantic-моделі**, а потім оголосіть параметр як `Cookie`:
{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *}
**FastAPI** буде **витягувати** дані для **кожного поля** з **cookies**, отриманих у запиті, і передавати Вам Pydantic-модель, яку Ви визначили.
**FastAPI** буде **витягувати** дані для **кожного поля** з **cookie** параметрів, отриманих у запиті, і передавати Вам Pydantic-модель, яку Ви визначили.
## Перевірка у документації { #check-the-docs }
## Перевірка у документації
Ви можете побачити визначені cookies в інтерфейсі документації за адресою `/docs`:
Ви можете побачити визначені cookie в інтерфейсі документації за адресою `/docs`:
<div class="screenshot">
<img src="/img/tutorial/cookie-param-models/image01.png">
@@ -34,29 +34,29 @@
/// info | Інформація
Майте на увазі, що оскільки **браузери обробляють cookies** особливим чином і «за лаштунками», вони **не** дозволяють **JavaScript** легко з ними працювати.
Майте на увазі, що оскільки **браузери обробляють cookie** особливим чином і "за лаштунками", вони **не** дозволяють **JavaScript** легко з ними працювати.
Якщо Ви зайдете до **інтерфейсу документації API** за адресою `/docs`, Ви зможете побачити **документацію** для cookies у Ваших *операціях шляху*.
Якщо Ви зайдете до **інтерфейсу документації API** за адресою `/docs`, Ви зможете побачити **документацію** для cookie у Ваших **операціях шляху**.
Але навіть якщо Ви заповните дані й натиснете "Execute", оскільки інтерфейс документації працює з **JavaScript**, cookies не будуть відправлені, і Ви побачите **помилку**, ніби Ви не ввели жодних значень.
Але навіть якщо Ви заповните дані й натиснете "Execute", оскільки інтерфейс документації працює з **JavaScript**, cookie не будуть відправлені, і Ви побачите **помилку**, ніби Ви не ввели жодних значень.
///
## Заборона додаткових cookie { #forbid-extra-cookies }
## Заборона додаткових cookie
У деяких спеціальних випадках (ймовірно, не дуже поширених) Ви можете захотіти **обмежити** cookies, які хочете отримувати.
У деяких спеціальних випадках (ймовірно, не дуже поширених) Ви можете захотіти **обмежити** список cookie, які хочете отримувати.
Ваша API тепер має можливість контролювати власну <abbr title="This is a joke, just in case. It has nothing to do with cookie consents, but it's funny that even the API can now reject the poor cookies. Have a cookie. 🍪">згоду на cookie</abbr>. 🤪🍪
Ваша API тепер має можливість контролювати власну <abbr title="Це жарт, якщо що. Це не має нічого спільного зі згодою на використання cookie, але це кумедно, що навіть API тепер може відхиляти бідні cookie. Ловіть печиво. 🍪">згоду на cookie</abbr>. 🤪🍪
Ви можете використовувати налаштування моделі Pydantic, щоб `forbid` будь-які `extra` поля:
Ви можете використовувати налаштування моделі Pydantic, щоб `заборонити` будь-які `додаткові` поля:
{* ../../docs_src/cookie_param_models/tutorial002_an_py310.py hl[10] *}
{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *}
Якщо клієнт спробує надіслати якісь **додаткові cookies**, він отримає відповідь з **помилкою**.
Якщо клієнт спробує надіслати якісь **додаткові cookie**, він отримає відповідь з **помилкою**.
Бідні банери cookie, які так старанно намагаються отримати Вашу згоду, щоб <abbr title="This is another joke. Don't pay attention to me. Have some coffee for your cookie. ☕">API її відхилила</abbr>. 🍪
Бідні банери cookie, які так старанно намагаються отримати Вашу згоду, щоб <abbr title="Це ще один жарт. Не звертайте уваги. Візьміть каву для свого печива. ☕">API її відхилила</abbr>. 🍪
Наприклад, якщо клієнт спробує надіслати cookie `santa_tracker` зі значенням `good-list-please`, він отримає відповідь з помилкою, яка повідомить, що `santa_tracker` <abbr title="Santa disapproves the lack of cookies. 🎅 Okay, no more cookie jokes.">cookie не дозволено</abbr>:
Наприклад, якщо клієнт спробує надіслати cookie `santa_tracker` зі значенням `good-list-please`, він отримає відповідь з помилкою, яка повідомить, що <abbr title="Санта не схвалює відсутність cookie. 🎅 Гаразд, більше жартів не буде.">cookie `santa_tracker` не дозволено</abbr>:
```json
{
@@ -71,6 +71,6 @@
}
```
## Підсумок { #summary }
## Підсумок
Ви можете використовувати **Pydantic-моделі** для оголошення <abbr title="Have a last cookie before you go. 🍪">**cookies**</abbr> у **FastAPI**. 😎
Ви можете використовувати **Pydantic-моделі** для оголошення <abbr title="Отримайте останнє печиво перед тим, як піти. 🍪">cookie</abbr> у FastAPI. 😎

View File

@@ -1,25 +1,24 @@
# Параметри Cookie { #cookie-parameters }
# Параметри Cookie
Ви можете визначати параметри Cookie таким же чином, як визначаються параметри `Query` і `Path`.
Ви можете визначити параметри Cookie таким же чином, як визначаються параметри `Query` і `Path`.
## Імпорт `Cookie` { #import-cookie }
## Імпорт `Cookie`
Спочатку імпортуйте `Cookie`:
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *}
## Визначення параметрів `Cookie` { #declare-cookie-parameters }
## Визначення параметрів `Cookie`
Потім визначте параметри cookie, використовуючи таку ж конструкцію як для `Path` і `Query`.
Ви можете визначити значення за замовчуванням, а також усі додаткові параметри валідації чи анотації:
Перше значення це значення за замовчуванням, ви можете також передати всі додаткові параметри валідації чи анотації:
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *}
/// note | Технічні деталі
`Cookie` це "сестра" класів `Path` і `Query`. Вони також наслідуються від одного спільного класу `Param`.
/// note | Технічні Деталі
`Cookie` це "сестра" класів `Path` і `Query`. Вони наслідуються від одного батьківського класу `Param`.
Але пам'ятайте, що коли ви імпортуєте `Query`, `Path`, `Cookie` та інше з `fastapi`, це фактично функції, що повертають спеціальні класи.
///
@@ -30,16 +29,6 @@
///
/// info
Майте на увазі, що оскільки **браузери обробляють cookies** спеціальним чином і за лаштунками, вони **не** дозволяють **JavaScript** легко взаємодіяти з ними.
Якщо ви перейдете до **інтерфейсу документації API** за адресою `/docs`, ви зможете побачити **документацію** для cookies для ваших *операцій шляху*.
Але навіть якщо ви **заповните дані** і натиснете "Execute", оскільки інтерфейс документації працює з **JavaScript**, cookies не буде надіслано, і ви побачите повідомлення про **помилку**, ніби ви не ввели жодних значень.
///
## Підсумки { #recap }
## Підсумки
Визначайте cookies за допомогою `Cookie`, використовуючи той же спільний шаблон, що і `Query` та `Path`.

View File

@@ -1,8 +1,8 @@
# CORS (Обмін ресурсами між різними джерелами) { #cors-cross-origin-resource-sharing }
# CORS (Обмін ресурсами між різними джерелами)
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">CORS або "Cross-Origin Resource Sharing"</a> є ситуація, коли фронтенд, що працює в браузері, містить JavaScript-код, який взаємодіє з бекендом, розташованим в іншому "джерелі" (origin).
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">CORS або "Обмін ресурсами між різними джерелами"</a> є ситуація, коли фронтенд, що працює в браузері, містить JavaScript-код, який взаємодіє з бекендом, розташованим в іншому "джерелі" (origin).
## Джерело (Origin) { #origin }
## Джерело (Origin)
Джерело визначається комбінацією протоколу (`http`, `https`), домену (`myapp.com`, `localhost`, `localhost.tiangolo.com`), порту (`80`, `443`, `8080`).
@@ -15,7 +15,7 @@
Навіть якщо вони всі містять `localhost`, вони мають різні протоколи або порти, що робить їх окремими "джерелами".
## Кроки { #steps }
## Кроки
Припустимо, що Ваш фронтенд працює в браузері на `http://localhost:8080`, а його JavaScript намагається відправити запит до бекенду, який працює на `http://localhost` (Оскільки ми не вказуємо порт, браузер за замовчуванням припускає порт `80`).
@@ -25,15 +25,15 @@
У цьому випадку список має містити `http://localhost:8080`, щоб фронтенд на порту `:8080` працював коректно.
## Символьне підставляння { #wildcards }
## Символьне підставляння
Можна також оголосити список як `"*"` ("символьне підставляння"), що означає дозвіл для всіх джерел.
Однак це дозволить лише певні типи комунікації, виключаючи все, що пов'язане з обліковими даними: Cookies, заголовки авторизації, такі як ті, що використовуються з Bearer Tokens, тощо.
Однак це дозволить лише певні типи комунікації, виключаючи все, що пов'язане з обліковими даними: Cookies, заголовки авторизації, такі як ті, що використовуються з Bearer токенами тощо.
Тому для коректної роботи краще явно вказувати дозволені джерела.
## Використання `CORSMiddleware` { #use-corsmiddleware }
## Використання `CORSMiddleware`
Ви можете налаштувати це у Вашому додатку **FastAPI** за допомогою `CORSMiddleware`.
@@ -44,44 +44,41 @@
Також можна вказати, чи дозволяє Ваш бекенд:
* Облікові дані (заголовки авторизації, Cookies, тощо).
* Облікові дані (заголовки авторизації, сookies, тощо).
* Конкретні HTTP-методи (`POST`, `PUT`) або всі за допомогою `"*"`
* Конкретні HTTP-заголовки або всі за допомогою `"*"`.
{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *}
{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *}
Параметри за замовчуванням у `CORSMiddleware` є досить обмеженими, тому Вам потрібно явно вказати конкретні джерела, методи або заголовки, щоб браузери могли використовувати їх у контексті запитів між різними доменами.
Параметри за замовчуванням у реалізації `CORSMiddleware` є досить обмеженими, тому Вам потрібно явно увімкнути конкретні джерела, методи або заголовки, щоб браузерам було дозволено використовувати їх у міждоменному контексті.
Підтримуються такі аргументи:
* `allow_origins` - Список джерел, яким дозволено здійснювати міждоменні запити. Наприклад `['https://example.org', 'https://www.example.org']`. Ви можете використовувати `['*']`, щоб дозволити будь-яке джерело.
* `allow_origins` - Список джерел, яким дозволено здійснювати міждоменні запити. Наприклад `['https://example.org', 'https://www.example.org']`. Ви можете використовувати ['*'], щоб дозволити всі джерела.
* `allow_origin_regex` - Рядок регулярного виразу для відповідності джерелам, яким дозволено здійснювати міждоменні запити. Наприклад, `'https://.*\.example\.org'`.
* `allow_methods` - Список HTTP-методів, дозволених для міждоменних запитів. За замовчуванням `['GET']`. Ви можете використовувати `['*']`, щоб дозволити всі стандартні методи.
* `allow_headers` - Список HTTP-заголовків запиту, які підтримуються для міждоменних запитів. За замовчуванням `[]`. Ви можете використовувати `['*']`, щоб дозволити всі заголовки. Заголовки `Accept`, `Accept-Language`, `Content-Language` і `Content-Type` завжди дозволені для <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests" class="external-link" rel="noopener" target="_blank">простих CORS-запитів</a>.
* `allow_credentials` - Визначає, чи повинні підтримуватися cookies для міждоменних запитів. За замовчуванням `False`.
Жоден із параметрів `allow_origins`, `allow_methods` і `allow_headers` не можна встановлювати як `['*']`, якщо `allow_credentials` встановлено як `True`. Усі вони мають бути <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#credentialed_requests_and_wildcards" class="external-link" rel="noopener" target="_blank">явно вказані</a>.
* `allow_headers` - Список HTTP-заголовків, які підтримуються для міждоменних запитів. За замовчуванням `[]`. Ви можете використовувати `['*']`, щоб дозволити всі заголовки. Заголовки `Accept`, `Accept-Language`, `Content-Language` і `Content-Type` завжди дозволені для <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests" class="external-link" rel="noopener" target="_blank">простих CORS-запитів</a>.
* `allow_credentials` - Визначає, чи підтримуються файли cookie для міждоменних запитів. За замовчуванням `False`. Також, якщо потрібно дозволити обмін обліковими даними (`allow_credentials = True`), параметр `allow_origins` не може бути встановлений як `['*']`, необхідно вказати конкретні джерела.
* `expose_headers` - Вказує, які заголовки відповіді повинні бути доступні для браузера. За замовчуванням `[]`.
* `max_age` - Встановлює максимальний час (у секундах) для кешування CORS-відповідей у браузерах. За замовчуванням `600`.
Цей middleware обробляє два типи HTTP-запитів...
### Попередні CORS-запити { #cors-preflight-requests }
### Попередні CORS-запити (preflight requests)
Це будь-які `OPTIONS` - запити, що містять заголовки `Origin` та `Access-Control-Request-Method`.
У такому випадку middleware перехопить вхідний запит і відповість відповідними CORS-заголовками, повертаючи або `200`, або `400` для інформаційних цілей.
### Прості запити { #simple-requests }
### Прості запити
Будь-які запити із заголовком `Origin`. У цьому випадку middleware пропустить запит як звичайний, але додасть відповідні CORS-заголовки у відповідь.
## Додаткова інформація { #more-info }
## Додаткова інформація
Більше про <abbr title="Cross-Origin Resource Sharing">CORS</abbr> можна дізнатися в <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">документації Mozilla про CORS</a>.
Більше про <abbr title="Cross-Origin Resource Sharing">CORS</abbr> можна дізнатися в <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">документації Mozilla</a>.
/// note | Технічні деталі

View File

@@ -1,16 +1,16 @@
# Налагодження { #debugging }
# Налагодження (Debugging)
Ви можете під'єднати дебагер у вашому редакторі коду, наприклад, у Visual Studio Code або PyCharm.
Ви можете під'єднати дебагер у Вашому редакторі коду, наприклад, у Visual Studio Code або PyCharm.
## Виклик `uvicorn` { #call-uvicorn }
## Виклик `uvicorn`
У вашому FastAPI-додатку імпортуйте та запустіть `uvicorn` безпосередньо:
У Вашому FastAPI-додатку імпортуйте та запустіть `uvicorn` безпосередньо:
{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *}
{* ../../docs_src/debugging/tutorial001.py hl[1,15] *}
### Про `__name__ == "__main__"` { #about-name-main }
### Про `__name__ == "__main__"`
Головна мета використання `__name__ == "__main__"` — це забезпечення виконання певного коду лише тоді, коли ваш файл запускається так:
Головна мета використання `__name__ == "__main__"` — це забезпечення виконання певного коду тільки тоді, коли файл запускається безпосередньо:
<div class="termy">
@@ -20,17 +20,17 @@ $ python myapp.py
</div>
але не виконується, коли інший файл імпортує його, наприклад:
але не виконується при його імпорті в інший файл, наприклад:
```Python
from myapp import app
```
#### Детальніше { #more-details }
#### Детальніше
Припустимо, ваш файл називається `myapp.py`.
Припустимо, Ваш файл називається `myapp.py`.
Якщо ви запустите його так:
Якщо Ви запустите його так:
<div class="termy">
@@ -40,7 +40,7 @@ $ python myapp.py
</div>
тоді внутрішня змінна `__name__` у вашому файлі, яка створюється автоматично Python, матиме значення рядка `"__main__"`.
тоді внутрішня змінна `__name__`, яка створюється автоматично Python, матиме значення `"__main__"`.
Отже, цей блок коду:
@@ -52,17 +52,17 @@ $ python myapp.py
---
Це не станеться, якщо ви імпортуєте цей модуль (файл).
Це не станеться, якщо Ви імпортуєте цей модуль (файл).
Отже, якщо у вас є інший файл `importer.py` з:
Якщо у Вас є інший файл, наприклад `importer.py`, з наступним кодом:
```Python
from myapp import app
# Some more code
# Додатковий код
```
у цьому випадку автоматично створена змінна `__name__` всередині `myapp.py` не матиме значення `"__main__"`.
У цьому випадку автоматично створена змінна у файлі `myapp.py` не матиме значення змінної `__name__` як `"__main__"`.
Отже, рядок:
@@ -74,39 +74,38 @@ from myapp import app
/// info | Інформація
Для отримання додаткової інформації дивіться <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">офіційну документацію Python</a>.
Більш детальну інформацію можна знайти в <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">офіційній документації Python</a>.
///
## Запуск коду з вашим дебагером { #run-your-code-with-your-debugger }
## Запуск коду з вашим дебагером
Оскільки ви запускаєте сервер Uvicorn безпосередньо з вашого коду, ви можете запустити вашу Python програму (ваш FastAPI-додаток) безпосередньо з дебагера.
Оскільки Ви запускаєте сервер Uvicorn безпосередньо з Вашого коду, Ви можете запустити вашу Python програму (ваш FastAPI додаток) безпосередньо з дебагера.
---
Наприклад, у Visual Studio Code ви можете:
Наприклад, у Visual Studio Code Ви можете:
* Перейдіть на панель «Debug».
* «Add configuration...».
* Виберіть «Python»
* Перейдіть на вкладку "Debug".
* Натисніть "Add configuration...".
* Виберіть "Python"
* Запустіть дебагер з опцією "`Python: Current File (Integrated Terminal)`".
Після цього він запустить сервер з вашим кодом **FastAPI**, зупиниться на точках зупину тощо.
Це запустить сервер з Вашим **FastAPI** кодом, зупиниться на точках зупину тощо.
Ось як це може виглядати:
<img src="/img/tutorial/debugging/image01.png">
---
Якщо Ви використовуєте PyCharm, ви можете:
Якщо ви використовуєте PyCharm, ви можете:
* Відкрити меню «Run».
* Вибрати опцію «Debug...».
* Відкрити меню "Run".
* Вибрати опцію "Debug...".
* Потім з'явиться контекстне меню.
* Вибрати файл для налагодження (у цьому випадку, `main.py`).
Після цього він запустить сервер з вашим кодом **FastAPI**, зупиниться на точках зупину тощо.
Це запустить сервер з Вашим **FastAPI** кодом, зупиниться на точках зупину тощо.
Ось як це може виглядати:

View File

@@ -1,32 +1,32 @@
# JSON-сумісний кодувальник { #json-compatible-encoder }
# JSON Compatible Encoder
Існують випадки, коли вам може знадобитися перетворити тип даних (наприклад, модель Pydantic) на щось сумісне з JSON (наприклад, `dict`, `list` тощо).
Існують випадки, коли вам може знадобитися перетворити тип даних (наприклад, модель Pydantic) в щось сумісне з JSON (наприклад, `dict`, `list`, і т. д.).
Наприклад, якщо вам потрібно зберегти це в базі даних.
Для цього **FastAPI** надає функцію `jsonable_encoder()`.
Для цього, **FastAPI** надає `jsonable_encoder()` функцію.
## Використання `jsonable_encoder` { #using-the-jsonable-encoder }
## Використання `jsonable_encoder`
Давайте уявимо, що у вас є база даних `fake_db`, яка приймає лише дані, сумісні з JSON.
Наприклад, вона не приймає об'єкти типу `datetime`, оскільки вони не сумісні з JSON.
Отже, об'єкт типу `datetime` потрібно перетворити на `str`, який містить дані в <a href="https://en.wikipedia.org/wiki/ISO_8601" class="external-link" target="_blank">форматі ISO</a>.
Отже, об'єкт типу `datetime` потрібно перетворити в рядок `str`, який містить дані в <a href="https://en.wikipedia.org/wiki/ISO_8601" class="external-link" target="_blank">ISO форматі</a>.
Так само ця база даних не прийматиме модель Pydantic (об'єкт з атрибутами), а лише `dict`.
Тим самим способом ця база даних не прийматиме об'єкт типу Pydantic model (об'єкт з атрибутами), а лише `dict`.
Ви можете використовувати `jsonable_encoder` для цього.
Вона приймає об'єкт, такий як модель Pydantic, і повертає його версію, сумісну з JSON:
Вона приймає об'єкт, такий як Pydantic model, і повертає його версію, сумісну з JSON:
{* ../../docs_src/encoder/tutorial001_py310.py hl[4,21] *}
У цьому прикладі вона конвертує модель Pydantic у `dict`, а `datetime` у `str`.
У цьому прикладі вона конвертує Pydantic model у `dict`, а `datetime` у `str`.
Результат виклику цієї функції це щось, що можна кодувати з використанням стандарту Python <a href="https://docs.python.org/3/library/json.html#json.dumps" class="external-link" target="_blank">`json.dumps()`</a>.
Результат виклику цієї функції - це щось, що можна кодувати з використанням стандарту Python <a href="https://docs.python.org/3/library/json.html#json.dumps" class="external-link" target="_blank">`json.dumps()`</a>.
Вона не повертає великий `str`, який містить дані у форматі JSON (як рядок). Вона повертає стандартну структуру даних Python (наприклад, `dict`) зі значеннями та підзначеннями, які є сумісними з JSON.
Вона не повертає велику строку `str`, яка містить дані у форматі JSON (як строка). Вона повертає стандартну структуру даних Python (наприклад `dict`) із значеннями та підзначеннями, які є сумісними з JSON.
/// note | Примітка

View File

@@ -1,13 +1,13 @@
# Додаткові типи даних { #extra-data-types }
# Додаткові типи даних
До цього часу ви використовували загальнопоширені типи даних, такі як:
До цього часу, ви використовували загальнопоширені типи даних, такі як:
* `int`
* `float`
* `str`
* `bool`
Але ви також можете використовувати більш складні типи даних.
Але можна також використовувати більш складні типи даних.
І ви все ще матимете ті ж можливості, які були показані до цього:
@@ -17,30 +17,30 @@
* Валідація даних.
* Автоматична анотація та документація.
## Інші типи даних { #other-data-types }
## Інші типи даних
Ось додаткові типи даних для використання:
* `UUID`:
* Стандартний "Універсальний унікальний ідентифікатор", який часто використовується як ID у багатьох базах даних та системах.
* Стандартний "Універсальний Унікальний Ідентифікатор", який часто використовується як ідентифікатор у багатьох базах даних та системах.
* У запитах та відповідях буде представлений як `str`.
* `datetime.datetime`:
* Пайтонівський `datetime.datetime`.
* У запитах та відповідях буде представлений як `str` у форматі ISO 8601, як: `2008-09-15T15:53:00+05:00`.
* У запитах та відповідях буде представлений як `str` в форматі ISO 8601, як: `2008-09-15T15:53:00+05:00`.
* `datetime.date`:
* Пайтонівський `datetime.date`.
* У запитах та відповідях буде представлений як `str` у форматі ISO 8601, як: `2008-09-15`.
* У запитах та відповідях буде представлений як `str` в форматі ISO 8601, як: `2008-09-15`.
* `datetime.time`:
* Пайтонівський `datetime.time`.
* У запитах та відповідях буде представлений як `str` у форматі ISO 8601, як: `14:23:55.003`.
* У запитах та відповідях буде представлений як `str` в форматі ISO 8601, як: `14:23:55.003`.
* `datetime.timedelta`:
* Пайтонівський `datetime.timedelta`.
* У запитах та відповідях буде представлений як `float` загальної кількості секунд.
* Pydantic також дозволяє представляти це як "ISO 8601 time diff encoding", <a href="https://docs.pydantic.dev/latest/concepts/serialization/#custom-serializers" class="external-link" target="_blank">дивіться документацію для отримання додаткової інформації</a>.
* Pydantic також дозволяє представляти це як "ISO 8601 time diff encoding", <a href="https://docs.pydantic.dev/latest/concepts/serialization/#json_encoders" class="external-link" target="_blank">більше інформації дивись у документації</a>.
* `frozenset`:
* У запитах і відповідях це буде оброблено так само, як і `set`:
* У запитах список буде зчитано, дублікати буде видалено, і його буде перетворено на `set`.
* У відповідях `set` буде перетворено на `list`.
* У запитах список буде зчитано, дублікати будуть видалені та він буде перетворений на `set`.
* У відповідях, `set` буде перетворений на `list`.
* Згенерована схема буде вказувати, що значення `set` є унікальними (з використанням JSON Schema's `uniqueItems`).
* `bytes`:
* Стандартний Пайтонівський `bytes`.
@@ -49,11 +49,11 @@
* `Decimal`:
* Стандартний Пайтонівський `Decimal`.
* У запитах і відповідях це буде оброблено так само, як і `float`.
* Ви можете перевірити всі дійсні типи даних Pydantic тут: <a href="https://docs.pydantic.dev/latest/usage/types/types/" class="external-link" target="_blank">типи даних Pydantic</a>.
* Ви можете перевірити всі дійсні типи даних Pydantic тут: <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">типи даних Pydantic</a>.
## Приклад { #example }
## Приклад
Ось приклад *операції шляху* з параметрами, використовуючи деякі з вищезазначених типів.
Ось приклад *path operation* з параметрами, використовуючи деякі з вищезазначених типів.
{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[1,3,12:16] *}

View File

@@ -1,118 +1,126 @@
# Перші кроки { #first-steps }
# Перші кроки
Найпростіший файл FastAPI може виглядати так:
{* ../../docs_src/first_steps/tutorial001_py39.py *}
{* ../../docs_src/first_steps/tutorial001.py *}
Скопіюйте це до файлу `main.py`.
Запустіть live-сервер:
Запустіть сервер:
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:single">main.py</u>
<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font>
<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font>
<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files
<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
╭─ <font color="#8AE234"><b>Python module file</b></font> ─╮
│ │
│ 🐍 main.py │
│ │
╰──────────────────────╯
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>
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font>
<font color="#3465A4">INFO </font> Found importable FastAPI app
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
╭─ <font color="#8AE234"><b>Importable FastAPI app</b></font> ─╮
│ │
│ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> │
│ │
╰──────────────────────────╯
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
the following code:
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font>
<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:#C4A000"><font color="#2E3436">╭────────── FastAPI CLI - Development mode ───────────╮</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ Serving at: http://127.0.0.1:8000 │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ API docs: http://127.0.0.1:8000/docs │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ Running in development mode, for production use: │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ </font></span><span style="background-color:#C4A000"><font color="#555753"><b>fastapi run</b></font></span><span style="background-color:#C4A000"><font color="#2E3436"> │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">╰─────────────────────────────────────────────────────╯</font></span>
<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://127.0.0.1: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://127.0.0.1:8000/docs</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
<b>fastapi run</b>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
<b>[</b><font color="#4E9A06">&apos;/home/user/code/awesomeapp&apos;</font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</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.
<font color="#4E9A06">INFO</font>: Will watch for changes in these directories: [&apos;/home/user/code/awesomeapp&apos;]
<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://127.0.0.1:8000</b> (Press CTRL+C to quit)
<font color="#4E9A06">INFO</font>: Started reloader process [<font color="#34E2E2"><b>2265862</b></font>] using <font color="#34E2E2"><b>WatchFiles</b></font>
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">2265873</font>]
<font color="#4E9A06">INFO</font>: Waiting for application startup.
<font color="#4E9A06">INFO</font>: Application startup complete.
```
</div>
У виводі є рядок приблизно такого змісту:
У консолі буде рядок приблизно такого змісту:
```hl_lines="4"
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
Цей рядок показує URL, за яким ваш застосунок обслуговується на вашій локальній машині.
Цей рядок показує URL, за яким додаток запускається на вашій локальній машині.
### Перевірте { #check-it }
### Перевірте
Відкрийте браузер та введіть адресу <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>.
Ви побачите JSON-відповідь:
Ви побачите у відповідь таке повідомлення у форматі JSON:
```JSON
{"message": "Hello World"}
```
### Інтерактивна API документація { #interactive-api-docs }
### Інтерактивна API документація
Тепер перейдіть сюди <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Перейдемо сюди <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Ви побачите автоматичну інтерактивну API документацію (надається <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
Ви побачите автоматичну інтерактивну API документацію (створену завдяки <a href="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 }
### Альтернативна API документація
А тепер перейдіть сюди <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
Тепер перейдемо сюди <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
Ви побачите альтернативну автоматичну документацію (надається <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
Ви побачите альтернативну автоматичну документацію (створену завдяки <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)
### OpenAPI { #openapi }
### OpenAPI
**FastAPI** генерує «схему» з усім вашим API, використовуючи стандарт **OpenAPI** для визначення API.
**FastAPI** генерує "схему" з усім вашим API, використовуючи стандарт **OpenAPI** для визначення API.
#### «Схема» { #schema }
#### "Схема"
«Схема» — це визначення або опис чогось. Це не код, який його реалізує, а просто абстрактний опис.
"Схема" - це визначення або опис чогось. Це не код, який його реалізує, а просто абстрактний опис.
#### API «схема» { #api-schema }
#### API "схема"
У цьому випадку, <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> є специфікацією, яка визначає, як описати схему вашого API.
Це визначення схеми включає шляхи (paths) вашого API, можливі параметри, які вони приймають, тощо.
Це визначення схеми включає шляхи (paths) вашого API, можливі параметри, які вони приймають тощо.
#### «Схема» даних { #data-schema }
#### "Схема" даних
Термін «схема» також може відноситися до форми деяких даних, наприклад, вмісту JSON.
Термін "схема" також може відноситися до структури даних, наприклад, JSON.
У цьому випадку це означає атрибути JSON і типи даних, які вони мають, тощо.
У цьому випадку це означає - атрибути JSON і типи даних, які вони мають тощо.
#### OpenAPI і JSON Schema { #openapi-and-json-schema }
#### OpenAPI і JSON Schema
OpenAPI описує схему API для вашого API. І ця схема включає визначення (або «схеми») даних, що надсилаються та отримуються вашим API, за допомогою **JSON Schema**, стандарту для схем даних JSON.
OpenAPI описує схему для вашого API. І ця схема включає визначення (або "схеми") даних, що надсилаються та отримуються вашим API за допомогою **JSON Schema**, стандарту для схем даних JSON.
#### Перевірте `openapi.json` { #check-the-openapi-json }
#### Розглянемо `openapi.json`
Якщо вас цікавить, як виглядає «сирий» OpenAPI schema, FastAPI автоматично генерує JSON (schema) з описами всього вашого API.
Якщо вас цікавить, як виглядає вихідна схема OpenAPI, то FastAPI автоматично генерує JSON-схему з усіма описами API.
Ви можете побачити це напряму тут: <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>.
Ознайомитися можна за посиланням: <a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a>.
Ви побачите JSON, що починається приблизно так:
Ви побачите приблизно такий JSON:
```JSON
{
@@ -135,79 +143,42 @@ OpenAPI описує схему API для вашого API. І ця схема
...
```
#### Для чого потрібний OpenAPI { #what-is-openapi-for }
#### Для чого потрібний OpenAPI
OpenAPI schema — це те, на чому працюють дві включені системи інтерактивної документації.
Схема OpenAPI є основою для обох систем інтерактивної документації.
Також існують десятки альтернатив, і всі вони засновані на OpenAPI. Ви можете легко додати будь-яку з цих альтернатив до вашого застосунку, створеного з **FastAPI**.
Існують десятки альтернативних інструментів, заснованих на OpenAPI. Ви можете легко додати будь-який з них до **FastAPI** додатку.
Ви також можете використовувати його для автоматичної генерації коду для клієнтів, які взаємодіють з вашим API. Наприклад, для фронтенд-, мобільних або IoT-застосунків.
Ви також можете використовувати OpenAPI для автоматичної генерації коду для клієнтів, які взаємодіють з API. Наприклад, для фронтенд-, мобільних або IoT-додатків
### Розгорніть ваш застосунок (необовʼязково) { #deploy-your-app-optional }
## А тепер крок за кроком
За бажанням ви можете розгорнути ваш FastAPI-застосунок у <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>, перейдіть і приєднайтеся до списку очікування, якщо ви цього ще не зробили. 🚀
### Крок 1: імпортуємо `FastAPI`
Якщо у вас вже є обліковий запис **FastAPI Cloud** (ми запросили вас зі списку очікування 😉), ви можете розгорнути ваш застосунок однією командою.
{* ../../docs_src/first_steps/tutorial001.py hl[1] *}
Перед розгортанням переконайтеся, що ви увійшли:
<div class="termy">
```console
$ fastapi login
You are logged in to FastAPI Cloud 🚀
```
</div>
Потім розгорніть ваш застосунок:
<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. ✨
## Підібʼємо підсумки, крок за кроком { #recap-step-by-step }
### Крок 1: імпортуємо `FastAPI` { #step-1-import-fastapi }
{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *}
`FastAPI` — це клас у Python, який надає всю функціональність для вашого API.
`FastAPI` це клас у Python, який надає всю функціональність для API.
/// note | Технічні деталі
`FastAPI` це клас, який успадковується безпосередньо від `Starlette`.
`FastAPI` це клас, який успадковується безпосередньо від `Starlette`.
Ви також можете використовувати всю функціональність <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> у `FastAPI`.
///
### Крок 2: створюємо «екземпляр» `FastAPI` { #step-2-create-a-fastapi-instance }
### Крок 2: створюємо екземпляр `FastAPI`
{* ../../docs_src/first_steps/tutorial001_py39.py hl[3] *}
{* ../../docs_src/first_steps/tutorial001.py hl[3] *}
Змінна `app` є екземпляром класу `FastAPI`.
Тут змінна `app` буде «екземпляром» класу `FastAPI`.
Це буде головна точка для створення і взаємодії з API.
Це буде головна точка взаємодії для створення всього вашого API.
### Крок 3: визначте операцію шляху (path operation)
### Крок 3: створіть *операцію шляху* { #step-3-create-a-path-operation }
#### Шлях (path)
#### Шлях { #path }
«Шлях» тут означає останню частину URL, починаючи з першого `/`.
"Шлях" це частина URL, яка йде одразу після символу `/`.
Отже, у такому URL, як:
@@ -221,17 +192,16 @@ https://example.com/items/foo
/items/foo
```
/// info | Інформація
/// info | Додаткова інформація
«Шлях» також зазвичай називають «ендпоінтом» або «маршрутом».
"Шлях" (path) також зазвичай називають "ендпоінтом" (endpoint) або "маршрутом" (route).
///
Під час створення API «шлях» є основним способом розділити «завдання» і «ресурси».
При створенні API, "шлях" є основним способом розділення "завдань" і "ресурсів".
#### Operation
#### Операція { #operation }
«Операція» тут означає один з HTTP «методів».
"Операція" (operation) тут означає один з "методів" HTTP.
Один з:
@@ -247,47 +217,46 @@ https://example.com/items/foo
* `PATCH`
* `TRACE`
У протоколі HTTP ви можете спілкуватися з кожним шляхом, використовуючи один (або кілька) з цих «методів».
У HTTP-протоколі можна спілкуватися з кожним шляхом, використовуючи один (або кілька) з цих "методів".
---
Під час створення API зазвичай використовують ці конкретні HTTP методи, щоб виконати певну дію.
При створенні API зазвичай використовуються конкретні методи HTTP для виконання певних дій.
Зазвичай використовують:
Як правило, використовують:
* `POST`: щоб створити дані.
* `GET`: щоб читати дані.
* `PUT`: щоб оновити дані.
* `DELETE`: щоб видалити дані.
* `POST`: для створення даних.
* `GET`: для читання даних.
* `PUT`: для оновлення даних.
* `DELETE`: для видалення даних.
Отже, в OpenAPI кожен з HTTP методів називається «операцією».
В OpenAPI кожен HTTP метод називається "операція".
Ми також будемо називати їх «**операціями**».
Ми також будемо дотримуватися цього терміна.
#### Визначте *декоратор операції шляху* { #define-a-path-operation-decorator }
#### Визначте декоратор операції шляху (path operation decorator)
{* ../../docs_src/first_steps/tutorial001_py39.py hl[6] *}
{* ../../docs_src/first_steps/tutorial001.py hl[6] *}
Декоратор `@app.get("/")` вказує **FastAPI**, що функція нижче, відповідає за обробку запитів, які надходять до неї:
Декоратор `@app.get("/")` повідомляє **FastAPI**, що функція одразу нижче відповідає за обробку запитів, які надходять до:
* шляху `/`
* шлях `/`
* використовуючи <abbr title="an HTTP GET method"><code>get</code> операцію</abbr>
/// info | `@decorator` Інформація
/// info | `@decorator` Додаткова інформація
Синтаксис `@something` у Python називається «декоратором».
Синтаксис `@something` у Python називається "декоратором".
Ви розташовуєте його над функцією. Як гарний декоративний капелюх (мабуть, звідти походить термін).
«Декоратор» бере функцію нижче і виконує з нею якусь дію.
"Декоратор" приймає функцію нижче і виконує з нею якусь дію.
У нашому випадку, цей декоратор повідомляє **FastAPI**, що функція нижче відповідає **шляху** `/` і **операції** `get`.
Це і є «**декоратор операції шляху**».
Це і є "декоратор операції шляху (path operation decorator)".
///
Можна також використовувати інші операції:
Можна також використовувати операції:
* `@app.post()`
* `@app.put()`
@@ -302,79 +271,58 @@ https://example.com/items/foo
/// tip | Порада
Ви можете використовувати кожну операцію (HTTP-метод) як забажаєте.
Ви можете використовувати кожну операцію (HTTP-метод) на свій розсуд.
**FastAPI** не навʼязує жодного конкретного значення.
**FastAPI** не нав'язує жодного певного значення для кожного методу.
Наведена тут інформація подана як настанова, а не вимога.
Наведена тут інформація є рекомендацією, а не обов'язковою вимогою.
Наприклад, під час використання GraphQL ви зазвичай виконуєте всі дії, використовуючи лише `POST` операції.
Наприклад, під час використання GraphQL зазвичай усі дії виконуються тільки за допомогою `POST` операцій.
///
### Крок 4: визначте **функцію операції шляху** { #step-4-define-the-path-operation-function }
### Крок 4: визначте **функцію операції шляху (path operation function)**
Ось наша «**функція операції шляху**»:
Ось "**функція операції шляху**":
* **шлях**: це `/`.
* **операція**: це `get`.
* **функція**: це функція нижче «декоратора» (нижче `@app.get("/")`).
* **функція**: це функція, яка знаходиться нижче "декоратора" (нижче `@app.get("/")`).
{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *}
{* ../../docs_src/first_steps/tutorial001.py hl[7] *}
Це функція Python.
Це звичайна функція Python.
**FastAPI** викликатиме її щоразу, коли отримає запит до URL «`/, використовуючи операцію `GET`.
FastAPI викликатиме її щоразу, коли отримає запит до URL із шляхом "/", використовуючи операцію `GET`.
У цьому випадку це `async` функція.
У даному випадку це асинхронна функція.
---
Ви також можете визначити її як звичайну функцію замість `async def`:
{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *}
{* ../../docs_src/first_steps/tutorial003.py hl[7] *}
/// note | Примітка
Якщо ви не знаєте різницю, подивіться [Асинхронність: *«Поспішаєте?»*](../async.md#in-a-hurry){.internal-link target=_blank}.
Якщо не знаєте в чому різниця, подивіться [Конкурентність: *"Поспішаєш?"*](../async.md#in-a-hurry){.internal-link target=_blank}.
///
### Крок 5: поверніть вміст { #step-5-return-the-content }
### Крок 5: поверніть результат
{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *}
{* ../../docs_src/first_steps/tutorial001.py hl[8] *}
Ви можете повернути `dict`, `list`, а також окремі значення `str`, `int` тощо.
Ви можете повернути `dict`, `list`, а також окремі значення `str`, `int`, ітд.
Також можна повернути моделі Pydantic (про це ви дізнаєтесь пізніше).
Існує багато інших обʼєктів і моделей, які будуть автоматично конвертовані в JSON (зокрема ORM тощо). Спробуйте використати свої улюблені велика ймовірність, що вони вже підтримуються.
Існує багато інших об'єктів і моделей, які будуть автоматично конвертовані в JSON (зокрема ORM тощо). Спробуйте використати свої улюблені, велика ймовірність, що вони вже підтримуються.
### Крок 6: розгорніть його { #step-6-deploy-it }
## Підіб'ємо підсумки
Розгорніть ваш застосунок у **<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** однією командою: `fastapi deploy`. 🎉
#### Про FastAPI Cloud { #about-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*. ✨
#### Розгортання в інших хмарних провайдерах { #deploy-to-other-cloud-providers }
FastAPI — це open source і базується на стандартах. Ви можете розгортати FastAPI-застосунки у будь-якого хмарного провайдера на ваш вибір.
Дотримуйтеся інструкцій вашого хмарного провайдера, щоб розгорнути FastAPI-застосунки з їхньою допомогою. 🤓
## Підібʼємо підсумки { #recap }
* Імпортуйте `FastAPI`.
* Створіть екземпляр `app`.
* Напишіть **декоратор операції шляху**, використовуючи декоратори на кшталт `@app.get("/")`.
* Визначте **функцію операції шляху**; наприклад, `def root(): ...`.
* Запустіть сервер розробки командою `fastapi dev`.
* За бажанням розгорніть ваш застосунок за допомогою `fastapi deploy`.
* Імпортуємо `FastAPI`.
* Створюємо екземпляр `app`.
* Пишемо **декоратор операції шляху** як `@app.get("/")`.
* Пишемо **функцію операції шляху**; наприклад, `def root(): ...`.
* Запускаємо сервер у режимі розробки `fastapi dev`.

View File

@@ -1,10 +1,10 @@
# Обробка помилок { #handling-errors }
# Обробка Помилок
Є багато ситуацій, коли вам потрібно повідомити про помилку клієнта, який використовує ваш API.
Є багато ситуацій, коли потрібно повідомити клієнта, який використовує Ваш API, про помилку.
Цим клієнтом може бути браузер із фронтендом, код іншого розробника, IoT-пристрій тощо.
Можливо, вам потрібно повідомити клієнта, що:
Можливо, Вам потрібно повідомити клієнта, що:
* У нього недостатньо прав для виконання цієї операції.
* Він не має доступу до цього ресурсу.
@@ -13,37 +13,37 @@
У таких випадках зазвичай повертається **HTTP статус-код** в діапазоні **400** (від 400 до 499).
Це схоже на HTTP статус-коди 200 (від 200 до 299). Ці «200» статус-коди означають, що якимось чином запит був «успішним».
Це схоже на HTTP статус-коди 200 (від 200 до 299). Ці "200" статус-коди означають, що запит пройшов успішно.
Статус-коди в діапазоні 400 означають, що сталася помилка з боку клієнта.
Пам'ятаєте всі ці помилки **«404 Not Found»** (і жарти про них)?
Пам'ятаєте всі ці помилки **404 Not Found** (і жарти про них)?
## Використання `HTTPException` { #use-httpexception }
## Використання `HTTPException`
Щоб повернути HTTP-відповіді з помилками клієнту, використовуйте `HTTPException`.
### Імпорт `HTTPException` { #import-httpexception }
### Імпорт `HTTPException`
{* ../../docs_src/handling_errors/tutorial001_py39.py hl[1] *}
{* ../../docs_src/handling_errors/tutorial001.py hl[1] *}
### Згенеруйте `HTTPException` у своєму коді { #raise-an-httpexception-in-your-code }
### Використання `HTTPException` у коді
`HTTPException` — це звичайна помилка Python із додатковими даними, які стосуються API.
Оскільки це помилка Python, ви не `return` її, а `raise` її.
Оскільки це помилка Python, Ви не `повертаєте` його, а `генеруєте` (генеруєте помилку).
Це також означає, що якщо ви перебуваєте всередині допоміжної функції, яку викликаєте всередині своєї *функції операції шляху*, і там згенеруєте `HTTPException` всередині цієї допоміжної функції, то решта коду в *функції операції шляху* не буде виконана. Запит одразу завершиться, і HTTP-помилка з `HTTPException` буде надіслана клієнту.
Це також означає, що якщо Ви перебуваєте всередині допоміжної функції, яку викликаєте всередині своєї *функції операції шляху*, і там генеруєте `HTTPException`, всередині цієї допоміжної функції, то решта коду в *функції операції шляху* не буде виконана. Запит одразу завершиться, і HTTP-помилка з `HTTPException` буде надіслана клієнту.
Перевага генерації виключення замість повернення значення стане більш очевидною в розділі про залежності та безпеку.
Перевага використання `генерації` (raise) помилки замість `повернення` значення (return) стане більш очевидним в розділі про Залежності та Безпеку.
У цьому прикладі, коли клієнт запитує елемент за ID, якого не існує, згенеруйте виключення зі статус-кодом `404`:
У цьому прикладі, якщо клієнт запитує елемент за ID, якого не існує, буде згенеровано помилку зі статус-кодом `404`:
{* ../../docs_src/handling_errors/tutorial001_py39.py hl[11] *}
{* ../../docs_src/handling_errors/tutorial001.py hl[11] *}
### Отримана відповідь { #the-resulting-response }
### Отримана відповідь
Якщо клієнт робить запит за шляхом `http://example.com/items/foo` (де `item_id` `"foo"`), він отримає HTTP статус-код 200 і JSON відповідь:
Якщо клієнт робить запит за шляхом `http://example.com/items/foo` (де `item_id` `"foo"`), він отримає статус-код 200 і JSON відповідь:
```JSON
{
@@ -51,7 +51,7 @@
}
```
Але якщо клієнт робить запит на `http://example.com/items/bar` (де `item_id` має не існуюче значення `"bar"`), то отримає HTTP статус-код 404 (помилка «не знайдено») та JSON відповідь:
Але якщо клієнт робить запит на `http://example.com/items/bar` (де `item_id` має не існуюче значення `"bar"`), то отримає статус-код 404 (помилка "не знайдено") та відповідь:
```JSON
{
@@ -61,7 +61,7 @@
/// tip | Порада
Під час генерації `HTTPException` ви можете передати будь-яке значення, яке може бути перетворене в JSON, як параметр `detail`, а не лише `str`.
Під час виклику `HTTPException` Ви можете передати будь-яке значення, яке може бути перетворене в JSON, як параметр `detail`, а не лише рядок (`str`).
Ви можете передати `dict`, `list` тощо.
@@ -69,33 +69,33 @@
///
## Додавання власних заголовків { #add-custom-headers }
## Додавання власних заголовків
Є деякі ситуації, коли корисно мати можливість додавати власні заголовки до HTTP-помилки. Наприклад, для деяких типів безпеки.
Іноді потрібно додати власні заголовки до HTTP-помилки, наприклад, для певних типів безпеки.
Ймовірно, вам не доведеться використовувати це безпосередньо у своєму коді.
Ймовірно, Вам не доведеться використовувати це безпосередньо у своєму коді.
Але якщо вам знадобиться це для складного сценарію, ви можете додати власні заголовки:
Але якщо Вам знадобиться це для складного сценарію, Ви можете додати власні заголовки:
{* ../../docs_src/handling_errors/tutorial002_py39.py hl[14] *}
{* ../../docs_src/handling_errors/tutorial002.py hl[14] *}
## Встановлення власних обробників виключень { #install-custom-exception-handlers }
## Встановлення власних обробників помилок
Ви можете додати власні обробники виключень за допомогою <a href="https://www.starlette.dev/exceptions/" class="external-link" target="_blank">тих самих утиліт для виключень зі Starlette</a>.
Ви можете додати власні обробники помилок за допомогою <a href="https://www.starlette.dev/exceptions/" class="external-link" target="_blank">тих самих утиліт обробки помилок зі Starlette</a>.
Припустімо, у вас є власне виключення `UnicornException`, яке ви (або бібліотека, яку ви використовуєте) можете `raise`.
Припустимо, у Вас є власний обʼєкт помилки `UnicornException`, яке Ви (або бібліотека, яку Ви використовуєте) може `згенерувати` (`raise`).
І ви хочете обробляти це виключення глобально за допомогою FastAPI.
І Ви хочете обробляти це виключення глобально за допомогою FastAPI.
Ви можете додати власний обробник виключень за допомогою `@app.exception_handler()`:
{* ../../docs_src/handling_errors/tutorial003_py39.py hl[5:7,13:18,24] *}
{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *}
Тут, якщо ви звернетеся до `/unicorns/yolo`, *операція шляху* згенерує (`raise`) `UnicornException`.
Тут, якщо Ви звернетеся до `/unicorns/yolo`, то згенерується помилка `UnicornException`.
Але вона буде оброблена функцією-обробником `unicorn_exception_handler`.
Отже, ви отримаєте зрозумілу помилку зі HTTP-статусом `418` і JSON-вмістом:
Отже, Ви отримаєте зрозумілу помилку зі HTTP-статусом `418` і JSON-відповіддю:
```JSON
{"message": "Oops! yolo did something. There goes a rainbow..."}
@@ -105,31 +105,31 @@
Ви також можете використовувати `from starlette.requests import Request` і `from starlette.responses import JSONResponse`.
**FastAPI** надає ті самі `starlette.responses`, що й `fastapi.responses`, просто для зручності для вас, розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette. Те ж саме з `Request`.
**FastAPI** надає ті самі `starlette.responses`, що й `fastapi.responses`, просто для зручності розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette. Те ж саме стосується і `Request`.
///
## Перевизначення обробників виключень за замовчуванням { #override-the-default-exception-handlers }
## Перевизначення обробників помилок за замовчуванням
**FastAPI** має кілька обробників виключень за замовчуванням.
**FastAPI** має кілька обробників помилок за замовчуванням.
Ці обробники відповідають за повернення стандартних JSON-відповідей, коли ви `raise` `HTTPException`, а також коли запит містить некоректні дані.
Ці обробники відповідають за повернення стандартних JSON-відповідей, коли Ви `генеруєте` (`raise`) `HTTPException`, а також коли запит містить некоректні дані.
Ви можете перевизначити ці обробники виключень власними.
Ви можете перевизначити ці обробники, створивши власні.
### Перевизначення виключень валідації запиту { #override-request-validation-exceptions }
### Перевизначення помилок валідації запиту
Коли запит містить некоректні дані, **FastAPI** внутрішньо генерує `RequestValidationError`.
Коли запит містить некоректні дані, **FastAPI** генерує `RequestValidationError`.
І також включає обробник виключень за замовчуванням для нього.
І також включає обробник помилок за замовчуванням для нього.
Щоб перевизначити його, імпортуйте `RequestValidationError` і використовуйте його з `@app.exception_handler(RequestValidationError)` для декорування обробника виключень.
Щоб перевизначити його, імпортуйте `RequestValidationError` і використовуйте його з `@app.exception_handler(RequestValidationError)` для декорування обробника помилок.
Обробник виключень отримає `Request` і саме виключення.
Обробник помилок отримує `Request` і саму помилку.
{* ../../docs_src/handling_errors/tutorial004_py39.py hl[2,14:19] *}
{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *}
Тепер, якщо ви перейдете за посиланням `/items/foo`, замість того, щоб отримати стандартну JSON-помилку:
Тепер, якщо Ви перейдете за посиланням `/items/foo`, замість того, щоб отримати стандартну JSON-помилку:
```JSON
{
@@ -146,44 +146,55 @@
}
```
ви отримаєте текстову версію:
Ви отримаєте текстову версію:
```
Validation errors:
Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer
1 validation error
path -> item_id
value is not a valid integer (type=type_error.integer)
```
### Перевизначення обробника помилок `HTTPException` { #override-the-httpexception-error-handler }
#### `RequestValidationError` проти `ValidationError`
Аналогічно, ви можете перевизначити обробник `HTTPException`.
/// warning | Увага
Наприклад, ви можете захотіти повернути відповідь у вигляді простого тексту замість JSON для цих помилок:
Це технічні деталі, які Ви можете пропустити, якщо вони зараз не важливі для Вас.
{* ../../docs_src/handling_errors/tutorial004_py39.py hl[3:4,9:11,25] *}
///
`RequestValidationError` є підкласом Pydantic <a href="https://docs.pydantic.dev/latest/concepts/models/#error-handling" class="external-link" target="_blank">`ValidationError`</a>.
**FastAPI** використовує його для того, якщо Ви використовуєте модель Pydantic у `response_model` і у ваших даних є помилка, Ви побачили помилку у своєму журналі.
Але клієнт/користувач не побачить її. Натомість клієнт отримає "Internal Server Error" зі статусом HTTP `500`.
Так має бути, якщо у Вас виникла `ValidationError` Pydantic у *відповіді* або деінде у вашому коді (не у *запиті* клієнта), це насправді є помилкою у Вашому коді.
І поки Ви її виправляєте, клієнти/користувачі не повинні мати доступу до внутрішньої інформації про помилку, оскільки це може призвести до вразливості безпеки.
### Перевизначення обробника помилок `HTTPException`
Аналогічно, Ви можете перевизначити обробник `HTTPException`.
Наприклад, Ви можете захотіти повернути текстову відповідь замість JSON для цих помилок:
{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *}
/// note | Технічні деталі
Ви також можете використовувати `from starlette.responses import PlainTextResponse`.
**FastAPI** надає ті самі `starlette.responses`, що й `fastapi.responses`, просто для зручності для вас, розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette.
**FastAPI** надає ті самі `starlette.responses`, що й `fastapi.responses`, просто для зручності розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette.
///
/// warning | Попередження
Пам’ятайте, що `RequestValidationError` містить інформацію про назву файлу та рядок, де сталася помилка валідації, щоб за потреби ви могли показати це у своїх логах із відповідною інформацією.
Але це означає, що якщо ви просто перетворите це на рядок і повернете цю інформацію напряму, ви можете розкрити трохи інформації про вашу систему, тому тут код витягає та показує кожну помилку незалежно.
///
### Використання тіла `RequestValidationError` { #use-the-requestvalidationerror-body }
### Використання тіла `RequestValidationError`
`RequestValidationError` містить `body`, який він отримав із некоректними даними.
Ви можете використовувати це під час розробки свого додатка, щоб логувати тіло запиту та налагоджувати його, повертати користувачеві тощо.
{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *}
{* ../../docs_src/handling_errors/tutorial005.py hl[14] *}
Тепер спробуйте надіслати некоректний елемент, наприклад:
@@ -193,8 +204,8 @@ Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to pa
"size": "XL"
}
```
Ви отримаєте відповідь, яка повідомить Вам, які саме дані є некоректні у вашому тілі запиту:
Ви отримаєте відповідь, яка повідомить вам, що дані є некоректними, і міститиме отримане тіло запиту:
```JSON hl_lines="12-15"
{
@@ -215,30 +226,30 @@ Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to pa
}
```
#### `HTTPException` FastAPI проти `HTTPException` Starlette { #fastapis-httpexception-vs-starlettes-httpexception }
#### `HTTPException` FastAPI проти `HTTPException` Starlette
**FastAPI** має власний `HTTPException`.
І клас помилки `HTTPException` в **FastAPI** успадковується від класу помилки `HTTPException` у Starlette.
І клас помилки `HTTPException` в **FastAPI** успадковується від класу помилки `HTTPException` в Starlette.
Єдина різниця полягає в тому, що `HTTPException` в **FastAPI** приймає будь-які дані, які можна перетворити на JSON, для поля `detail`, тоді як `HTTPException` у Starlette приймає тільки рядки.
Отже, ви можете продовжувати генерувати `HTTPException` **FastAPI** як зазвичай у своєму коді.
Отже, Ви можете продовжувати використовувати `HTTPException` в **FastAPI** як зазвичай у своєму коді.
Але коли ви реєструєте обробник виключень, слід реєструвати його для `HTTPException` зі Starlette.
Але коли Ви реєструєте обробник виключень, слід реєструвати його для `HTTPException` зі Starlette.
Таким чином, якщо будь-яка частина внутрішнього коду Starlette або розширення чи плагін Starlette згенерує Starlette `HTTPException`, ваш обробник зможе перехопити та обробити її.
Таким чином, якщо будь-яка частина внутрішнього коду Starlette або розширення чи плагін Starlette згенерує (raise) `HTTPException`, Ваш обробник зможе перехопити та обробити її.
У цьому прикладі, щоб мати можливість використовувати обидва `HTTPException` в одному коді, виключення Starlette перейменовується на `StarletteHTTPException`:
У цьому прикладі, щоб мати можливість використовувати обидва `HTTPException` в одному коді, помилка Starlette перейменовується на `StarletteHTTPException`:
```Python
from starlette.exceptions import HTTPException as StarletteHTTPException
```
### Повторне використання обробників виключень **FastAPI** { #reuse-fastapis-exception-handlers }
### Повторне використання обробників помилок **FastAPI**
Якщо ви хочете використовувати виключення разом із такими ж обробниками виключень за замовчуванням, як у **FastAPI**, ви можете імпортувати та повторно використати обробники виключень за замовчуванням із `fastapi.exception_handlers`:
Якщо Ви хочете використовувати помилки разом із такими ж обробниками помилок за замовчуванням, як у **FastAPI**, Ви можете імпортувати та повторно використовувати їх із `fastapi.exception_handlers`:
{* ../../docs_src/handling_errors/tutorial006_py39.py hl[2:5,15,21] *}
{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *}
У цьому прикладі ви просто друкуєте помилку з дуже виразним повідомленням, але ви зрозуміли основну ідею. Ви можете використовувати виключення, а потім просто повторно використати обробники виключень за замовчуванням.
У цьому прикладі Ви просто використовуєте `print` для виведення дуже інформативного повідомлення, але Ви зрозуміли основну ідею. Ви можете обробити помилку та повторно використовувати обробники помилок за замовчуванням.

View File

@@ -1,24 +1,26 @@
# Моделі параметрів заголовків { #header-parameter-models }
# Моделі Параметрів Заголовків
Якщо у Вас є група пов’язаних **параметрів заголовків**, Ви можете створити **Pydantic модель** для їх оголошення.
Якщо у Вас є група пов’язаних параметрів заголовків, Ви можете створити **Pydantic модель** для їх оголошення.
Це дозволить Вам повторно **використовувати модель** в **різних місцях**, а також оголосити валідації та метадані для всіх параметрів одночасно. 😎
/// note | Примітка
/// note | Нотатки
Ця можливість підтримується починаючи з версії FastAPI `0.115.0`. 🤓
///
## Параметри заголовків з використанням Pydantic моделі { #header-parameters-with-a-pydantic-model }
## Параметри Заголовків з Використанням Pydantic Model
Оголосіть потрібні **параметри заголовків** у **Pydantic моделі**, а потім оголосіть параметр як `Header`:
{* ../../docs_src/header_param_models/tutorial001_an_py310.py hl[9:14,18] *}
FastAPI буде витягувати дані для кожного поля з заголовків у запиті та передавати їх у створену Вами Pydantic модель.
**FastAPI** буде **витягувати** дані для **кожного поля** з **заголовків** у запиті та передавати їх у створену Вами Pydantic модель.
## Перевірка в документації { #check-the-docs }
## Перевірка в Документації
Ви можете побачити необхідні заголовки в інтерфейсі документації за адресою `/docs`:
@@ -26,7 +28,7 @@
<img src="/img/tutorial/header-param-models/image01.png">
</div>
## Заборонити додаткові заголовки { #forbid-extra-headers }
## Заборона Додаткових Заголовків
У деяких особливих випадках (ймовірно, не дуже поширених) Ви можете захотіти **обмежити** заголовки, які хочете отримати.
@@ -36,7 +38,7 @@
Якщо клієнт спробує надіслати **додаткові заголовки**, він отримає **помилку** у відповіді.
Наприклад, якщо клієнт спробує надіслати заголовок `tool` зі значенням `plumbus`, він отримає **помилку** у відповіді з повідомленням про те, що параметр заголовка `tool` не дозволений:
Наприклад, якщо клієнт спробує надіслати заголовок `tool` зі значенням `plumbus`, він отримає **помилку** з повідомленням про те, що параметр заголовка `tool` не дозволений:
```json
{
@@ -51,22 +53,6 @@
}
```
## Вимкнути перетворення підкреслень { #disable-convert-underscores }
Так само, як і зі звичайними параметрами заголовків, коли у назвах параметрів є символи підкреслення, вони **автоматично перетворюються на дефіси**.
Наприклад, якщо у коді у Вас є параметр заголовка `save_data`, очікуваний HTTP-заголовок буде `save-data`, і він так само відображатиметься в документації.
Якщо з якоїсь причини Вам потрібно вимкнути це автоматичне перетворення, Ви також можете зробити це для Pydantic моделей для параметрів заголовків.
{* ../../docs_src/header_param_models/tutorial003_an_py310.py hl[19] *}
/// warning | Попередження
Перш ніж встановлювати `convert_underscores` у значення `False`, пам’ятайте, що деякі HTTP проксі та сервери забороняють використання заголовків із підкресленнями.
///
## Підсумок { #summary }
## Підсумок
Ви можете використовувати **Pydantic моделі** для оголошення **заголовків** у **FastAPI**. 😎

View File

@@ -1,14 +1,14 @@
# Параметри заголовків { #header-parameters }
# Header-параметри
Ви можете визначати параметри заголовків так само, як визначаєте параметри `Query`, `Path` і `Cookie`.
Ви можете визначати параметри заголовків, так само як визначаєте `Query`, `Path` і `Cookie` параметри.
## Імпорт `Header` { #import-header }
## Імпорт `Header`
Спочатку імпортуйте `Header`:
{* ../../docs_src/header_params/tutorial001_an_py310.py hl[3] *}
## Оголошення параметрів `Header` { #declare-header-parameters }
## Оголошення параметрів `Header`
Потім оголосіть параметри заголовків, використовуючи ту ж структуру, що й для `Path`, `Query` та `Cookie`.
@@ -18,9 +18,9 @@
/// note | Технічні деталі
`Header` є «сестринським» класом для `Path`, `Query` і `Cookie`. Він також успадковується від того ж спільного класу `Param`.
`Header`є "сестринським" класом для `Path`, `Query` і `Cookie`. Він також успадковується від загального класу `Param`.
Але пам’ятайте, що коли ви імпортуєте `Query`, `Path`, `Header` та інші з `fastapi`, то насправді це функції, які повертають спеціальні класи.
Але пам’ятайте, що при імпорті `Query`, `Path`, `Header` та інших із `fastapi`, то насправді вони є функціями, які повертають спеціальні класи.
///
@@ -30,43 +30,43 @@
///
## Автоматичне перетворення { #automatic-conversion }
## Автоматичне перетворення
`Header` має трохи додаткової функціональності порівняно з тим, що надають `Path`, `Query` та `Cookie`.
`Header` має додатковий функціонал порівняно з `Path`, `Query` та `Cookie`.
Більшість стандартних заголовків розділяються символом «дефіс», також відомим як «символ мінуса» (`-`).
Більшість стандартних заголовків розділяються символом «дефіс», також відомим як «мінус» (`-`).
Але змінна, така як `user-agent`, є недійсною в Python.
Тому, за замовчуванням, `Header` перетворюватиме символи підкреслення (`_`) у назвах параметрів на дефіси (`-`), щоб отримувати та документувати заголовки.
Тому, за замовчуванням, `Header` автоматично перетворює символи підкреслення (`_`) на дефіси (`-`) для отримання та документування заголовків.
Також заголовки HTTP не чутливі до регістру, тож ви можете оголошувати їх у стандартному стилі Python (також відомому як «snake_case»).
Оскільки заголовки HTTP не чутливі до регістру, Ви можете використовувати стандартний стиль Python ("snake_case").
Тому ви можете використовувати `user_agent`, як зазвичай у коді Python, замість того щоб потрібно було писати з великої літери перші літери, як `User_Agent` або щось подібне.
Тому Ви можете використовувати `user_agent`, як зазвичай у коді Python, замість того щоб писати з великої літери, як `User_Agent` або щось подібне.
Якщо з якоїсь причини вам потрібно вимкнути автоматичне перетворення підкреслень на дефіси, встановіть параметр `convert_underscores` у `Header` в `False`:
Якщо Вам потрібно вимкнути автоматичне перетворення підкреслень у дефіси, встановіть `convert_underscores` в `Header` значення `False`:
{* ../../docs_src/header_params/tutorial002_an_py310.py hl[10] *}
/// warning | Попередження
/// warning | Увага
Перед тим як встановити `convert_underscores` в `False`, пам’ятайте, що деякі HTTP-проксі та сервери забороняють використання заголовків із підкресленнями.
Перед тим як встановити значення `False` для `convert_underscores` пам’ятайте, що деякі HTTP-проксі та сервери не підтримують заголовки з підкресленнями.
///
## Дубльовані заголовки { #duplicate-headers }
## Дубльовані заголовки
Можливо отримати дубльовані заголовки. Тобто один і той самий заголовок із кількома значеннями.
Можливо отримати дубльовані заголовки, тобто той самий заголовок із кількома значеннями.
Ви можете визначити такі випадки, використовуючи список у типізації.
Це можна визначити, використовуючи список у типізації параметра.
Ви отримаєте всі значення дубльованого заголовка у вигляді Python-`list`.
Ви отримаєте всі значення дубльованого заголовка у вигляді `list` у Python.
Наприклад, щоб оголосити заголовок `X-Token`, який може з’являтися більше ніж один раз, ви можете написати:
Наприклад, щоб оголосити заголовок `X-Token`, який може з’являтися більше ніж один раз:
{* ../../docs_src/header_params/tutorial003_an_py310.py hl[9] *}
Якщо ви взаємодієте з цією *операцією шляху*, надсилаючи два HTTP-заголовки, наприклад:
Якщо Ви взаємодієте з цією операцією шляху, надсилаючи два HTTP-заголовки, наприклад:
```
X-Token: foo
@@ -84,8 +84,8 @@ X-Token: bar
}
```
## Підсумок { #recap }
## Підсумок
Оголошуйте заголовки за допомогою `Header`, використовуючи той самий загальний підхід, що й для `Query`, `Path` та `Cookie`.
Оголошуйте заголовки за допомогою `Header`, використовуючи той самий підхід, що й для `Query`, `Path` та `Cookie`.
І не хвилюйтеся про підкреслення у ваших змінних — **FastAPI** подбає про їх перетворення.
Не хвилюйтеся про підкреслення у змінних — **FastAPI** автоматично конвертує їх.

View File

@@ -1,53 +1,29 @@
# Туторіал - Посібник користувача { #tutorial-user-guide }
# Туторіал - Посібник користувача
У цьому посібнику показано, як користуватися **FastAPI** з більшістю його функцій, крок за кроком.
Кожен розділ поступово надбудовується на попередні, але він структурований на окремі теми, щоб ви могли перейти безпосередньо до будь-якої конкретної, щоб вирішити ваші конкретні потреби API.
Також він створений як довідник для роботи у майбутньому, тож ви можете повернутися і побачити саме те, що вам потрібно.
Він також створений як довідник для роботи у майбутньому.
## Запустіть код { #run-the-code }
Тож ви можете повернутися і побачити саме те, що вам потрібно.
## Запустіть код
Усі блоки коду можна скопіювати та використовувати безпосередньо (це фактично перевірені файли Python).
Щоб запустити будь-який із прикладів, скопіюйте код у файл `main.py` і запустіть `fastapi dev` за допомогою:
Щоб запустити будь-який із прикладів, скопіюйте код у файл `main.py` і запустіть `uvicorn` за допомогою:
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
$ uvicorn main:app --reload
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development 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://127.0.0.1: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://127.0.0.1:8000/docs</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
<b>fastapi run</b>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
<b>[</b><font color="#4E9A06">&apos;/home/user/code/awesomeapp&apos;</font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</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="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
<span style="color: green;">INFO</span>: Started reloader process [28720]
<span style="color: green;">INFO</span>: Started server process [28722]
<span style="color: green;">INFO</span>: Waiting for application startup.
<span style="color: green;">INFO</span>: Application startup complete.
```
</div>
@@ -58,33 +34,45 @@ $ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid
---
## Встановлення FastAPI { #install-fastapi }
## Встановлення FastAPI
Першим кроком є встановлення FastAPI.
Переконайтеся, що ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім **встановіть FastAPI**:
Для туторіалу ви можете встановити його з усіма необов’язковими залежностями та функціями:
<div class="termy">
```console
$ pip install "fastapi[standard]"
$ pip install "fastapi[all]"
---> 100%
```
</div>
/// note | Примітка
...який також включає `uvicorn`, який ви можете використовувати як сервер, який запускає ваш код.
Коли ви встановлюєте через `pip install "fastapi[standard]"`, він постачається з деякими типовими необов’язковими стандартними залежностями, включно з `fastapi-cloud-cli`, який дозволяє розгортати в <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>.
/// note
Якщо ви не хочете мати ці необов’язкові залежності, натомість можете встановити `pip install fastapi`.
Ви також можете встановити його частина за частиною.
Якщо ви хочете встановити стандартні залежності, але без `fastapi-cloud-cli`, ви можете встановити через `pip install "fastapi[standard-no-fastapi-cloud-cli]"`.
Це те, що ви, ймовірно, зробили б, коли захочете розгорнути свою програму у виробничому середовищі:
```
pip install fastapi
```
Також встановіть `uvicorn`, щоб він працював як сервер:
```
pip install "uvicorn[standard]"
```
І те саме для кожної з опціональних залежностей, які ви хочете використовувати.
///
## Розширений посібник користувача { #advanced-user-guide }
## Розширений посібник користувача
Існує також **Розширений посібник користувача**, який ви зможете прочитати пізніше після цього **Туторіал - Посібник користувача**.
@@ -92,4 +80,4 @@ $ pip install "fastapi[standard]"
Але вам слід спочатку прочитати **Туторіал - Посібник користувача** (те, що ви зараз читаєте).
Він розроблений таким чином, що ви можете створити повну програму лише за допомогою **Туторіал - Посібник користувача**, а потім розширити її різними способами, залежно від ваших потреб, використовуючи деякі з додаткових ідей з **Розширеного посібника користувача**.
Він розроблений таким чином, що ви можете створити повну програму лише за допомогою **Туторіал - Посібник користувача**, а потім розширити її різними способами, залежно від ваших потреб, використовуючи деякі з додаткових ідей з **Розширеного посібника користувача** .

View File

@@ -1,26 +1,26 @@
# Метадані та URL-адреси документації { #metadata-and-docs-urls }
# Метадані та URL-адреси документації
Ви можете налаштувати кілька конфігурацій метаданих у Вашому додатку **FastAPI**.
## Метадані для API { #metadata-for-api }
## Метадані для API
Ви можете встановити такі поля, які використовуються в специфікації OpenAPI та в автоматично згенерованих інтерфейсах документації API:
| Параметр | Тип | Опис |
|------------|------|-------------|
| `title` | `str` | Назва API. |
| `summary` | `str` | Короткий підсумок API. <small>Доступно з OpenAPI 3.1.0, FastAPI 0.99.0.</small> |
| `description` | `str` | Короткий опис API. Може використовувати Markdown. |
| `summary` | `str` | Короткий опис API. <small>Доступно з OpenAPI 3.1.0, FastAPI 0.99.0.</small> |
| `description` | `str` | Більш детальний опис API. Може використовувати Markdown. |
| `version` | `string` | Версія API. Це версія Вашого додатка, а не OpenAPI. Наприклад, `2.5.0`. |
| `terms_of_service` | `str` | URL до умов використання API. Якщо вказано, має бути у форматі URL. |
| `contact` | `dict` | Інформація для контакту з опублікованим API. Може містити кілька полів. <details><summary><code>contact</code> поля</summary><table><thead><tr><th>Параметр</th><th>Тип</th><th>Опис</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td>Ідентифікаційне ім'я контактної особи або організації.</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL, що вказує на контактну інформацію. <strong>МАЄ</strong> бути у форматі URL.</td></tr><tr><td><code>email</code></td><td><code>str</code></td><td>Адреса електронної пошти контактної особи або організації. <strong>МАЄ</strong> бути у форматі адреси електронної пошти.</td></tr></tbody></table></details> |
| `license_info` | `dict` | Інформація про ліцензію для опублікованого API. Може містити кілька полів. <details><summary><code>license_info</code> поля</summary><table><thead><tr><th>Параметр</th><th>Тип</th><th>Опис</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>ОБОВ'ЯЗКОВО</strong> (якщо встановлено <code>license_info</code>). Назва ліцензії для API.</td></tr><tr><td><code>identifier</code></td><td><code>str</code></td><td>Ліцензійний вираз за <a href="https://spdx.org/licenses/" class="external-link" target="_blank">SPDX</a> для API. Поле <code>identifier</code> взаємовиключне з полем <code>url</code>. <small>Доступно з OpenAPI 3.1.0, FastAPI 0.99.0.</small></td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL до ліцензії, яка використовується для API. <strong>МАЄ</strong> бути у форматі URL.</td></tr></tbody></table></details> |
| `terms_of_service` | `str` | URL до умов використання API. Якщо вказано, має бути у форматі URL. |
| `contact` | `dict` | Інформація для контакту з API. Може містити кілька полів. <details><summary><code>contact</code> поля</summary><table><thead><tr><th>Параметр</th><th>Тип</th><th>Опис</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td>Ім'я контактної особи або організації.</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL з інформацією для контакту. Повинен бути у форматі URL.</td></tr><tr><td><code>email</code></td><td><code>str</code></td><td>Email контактної особи або організації. Повинен бути у форматі електронної пошти.</td></tr></tbody></table></details> |
| `license_info` | `dict` | Інформація про ліцензію для API. Може містити кілька полів. <details><summary><code>license_info</code> поля</summary><table><thead><tr><th>Параметр</th><th>Тип</th><th>Опис</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>ОБОВ'ЯЗКОВО</strong> (якщо встановлено <code>license_info</code>). Назва ліцензії для API.</td></tr><tr><td><code>identifier</code></td><td><code>str</code></td><td>Ліцензійний вираз за <a href="https://spdx.org/licenses/" class="external-link" target="_blank">SPDX</a> для API. Поле <code>identifier</code> взаємовиключне з полем <code>url</code>. <small>Доступно з OpenAPI 3.1.0, FastAPI 0.99.0.</small></td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>URL до ліцензії, яка використовується для API. Повинен бути у форматі URL.</td></tr></tbody></table></details> |
Ви можете налаштувати їх наступним чином:
{* ../../docs_src/metadata/tutorial001_py39.py hl[3:16, 19:32] *}
{* ../../docs_src/metadata/tutorial001.py hl[3:16, 19:32] *}
/// tip | Порада
/// tip | Підказка
У полі `description` можна використовувати Markdown, і він буде відображатися у результаті.
@@ -30,15 +30,15 @@
<img src="/img/tutorial/metadata/image01.png">
## Ідентифікатор ліцензії { #license-identifier }
## Ідентифікатор ліцензії
З початку використання OpenAPI 3.1.0 та FastAPI 0.99.0 Ви також можете налаштувати `license_info` за допомогою `identifier` замість `url`.
Наприклад:
{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *}
{* ../../docs_src/metadata/tutorial001_1.py hl[31] *}
## Метадані для тегів { #metadata-for-tags }
## Метадані для тегів
Ви також можете додати додаткові метадані для різних тегів, які використовуються для групування операцій шляхів, за допомогою параметра `openapi_tags`.
@@ -46,53 +46,53 @@
Кожен словник може містити:
* `name` (**обов'язково**): `str` з тією ж назвою тегу, яку Ви використовуєте у параметрі `tags` у Ваших *операціях шляху* та `APIRouter`s.
* `description`: `str` з коротким описом тегу. Може містити Markdown і буде показано в інтерфейсі документації.
* `externalDocs`: `dict`, який описує зовнішню документацію з такими полями:
* `name` (**обов'язково**): `str` з тією ж назвою тегу, яку Ви використовуєте у параметрі `tags` у Ваших *операціях шляху* та `APIRouter`s.
* `description`: `str` з коротким описом тегу. Може містити Markdown і буде відображено в інтерфейсі документації.
* `externalDocs`: `dict` який описує зовнішню документацію з такими полями:
* `description`: `str` з коротким описом зовнішньої документації.
* `url` (**обов'язково**): `str` з URL-адресою зовнішньої документації.
* `url` (**обов'язково**): `str`з URL-адресою зовнішньої документації.
### Створення метаданих для тегів { #create-metadata-for-tags }
### Створення метаданих для тегів
Спробуймо це на прикладі з тегами для `users` та `items`.
Створіть метадані для своїх тегів і передайте їх у параметр `openapi_tags`:
Створіть метадані для своїх тегів і передайте їх у параметр `openapi_tags`:
{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *}
{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *}
Зверніть увагу, що в описах можна використовувати Markdown, наприклад, "login" буде показано жирним шрифтом (**login**), а "fancy" буде показано курсивом (_fancy_).
/// tip | Порада
Вам не потрібно додавати метадані для всіх тегів, які Ви використовуєте.
Не обов'язково додавати метадані для всіх тегів, які Ви використовуєте.
///
### Використовуйте свої теги { #use-your-tags }
### Використання тегів
Використовуйте параметр `tags` зі своїми *операціями шляху* (і `APIRouter`s), щоб призначити їх до різних тегів:
Використовуйте параметр `tags` зі своїми *операціями шляху* (і `APIRouter`) для призначення їх до різних тегів:
{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *}
{* ../../docs_src/metadata/tutorial004.py hl[21,26] *}
/// info | Інформація
Детальніше про теги читайте в розділі [Конфігурація операції шляху](path-operation-configuration.md#tags){.internal-link target=_blank}.
Детальніше про теги читайте в розділі [Конфігурація шляхів операцій](path-operation-configuration.md#tags){.internal-link target=_blank}.
///
### Перевірте документацію { #check-the-docs }
### Перевірка документації
Тепер, якщо Ви перевірите документацію, вона покаже всі додаткові метадані:
Якщо Ви зараз перевірите документацію, вона покаже всі додаткові метадані:
<img src="/img/tutorial/metadata/image02.png">
### Порядок тегів { #order-of-tags }
### Порядок тегів
Порядок кожного словника метаданих тегу також визначає порядок відображення в інтерфейсі документації.
Наприклад, хоча `users` мав би йти після `items` в алфавітному порядку, він відображається перед ними, оскільки ми додали їхні метадані як перший словник у списку.
Наприклад, хоча `users` мав би йти після `items` в алфавітному порядку, він відображається перед ними, оскільки ми додали його метадані як перший словник у списку.
## URL для OpenAPI { #openapi-url }
## URL для OpenAPI
За замовчуванням схема OpenAPI надається за адресою `/openapi.json`.
@@ -100,11 +100,11 @@
Наприклад, щоб налаштувати його на `/api/v1/openapi.json`:
{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *}
{* ../../docs_src/metadata/tutorial002.py hl[3] *}
Якщо Ви хочете повністю вимкнути схему OpenAPI, Ви можете встановити `openapi_url=None`, це також вимкне інтерфейси документації, які її використовують.
## URL-адреси документації { #docs-urls }
## URL-адреси документації
Ви можете налаштувати два інтерфейси користувача для документації, які включені:
@@ -117,4 +117,4 @@
Наприклад, щоб налаштувати Swagger UI на `/documentation` і вимкнути ReDoc:
{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *}
{* ../../docs_src/metadata/tutorial003.py hl[3] *}

View File

@@ -1,43 +1,45 @@
# Middleware { #middleware }
# Middleware (Проміжний шар)
У **FastAPI** можна додавати middleware.
У **FastAPI** можна додавати middleware (проміжний шар).
«Middleware» — це функція, яка працює з кожним **запитом** перед його обробкою будь-якою конкретною *операцією шляху*. А також з кожною **відповіддю** перед її поверненням.
"Middleware" — це функція, яка працює з кожним **запитом** перед його обробкою будь-якою конкретною *операцією шляху* (*path operation*), а також з кожною **відповіддю** перед її поверненням.
* Вона отримує кожен **запит**, що надходить до вашого застосунку.
* Потім вона може виконати певні дії із цим **запитом** або запустити необхідний код.
* Далі вона передає **запит** для обробки рештою застосунку (якоюсь *операцією шляху*).
* Потім вона отримує **відповідь**, сформовану застосунком (якоюсь *операцією шляху*).
* Вона може виконати певні дії із цією **відповіддю** або запустити необхідний код.
* Потім вона повертає **відповідь**.
* Middleware отримує кожен **запит**, що надходить до Вашого застосунку.
* Може виконати певні дії із цим **запитом** або запустити необхідний код.
* Далі передає **запит** для обробки основним застосунком (*операцією шляху*).
* Отримує **відповідь**, сформовану застосунком (*операцією шляху*).
* Може змінити цю **відповідь** або виконати додатковий код.
* Повертає **відповідь** клієнту.
/// note | Технічні деталі
Якщо у вас є залежності з `yield`, код виходу виконається *після* middleware.
Якщо у Вас є залежності з `yield`, код виходу виконається *після* middleware.
Якщо були заплановані фонові задачі (розглянуто в розділі [Background Tasks](background-tasks.md){.internal-link target=_blank}, ви побачите це пізніше), вони виконаються *після* всіх middleware.
Якщо були заплановані фонові задачі (background tasks - розглянуто далі), вони виконаються *після* всіх middleware.
///
## Створення middleware { #create-a-middleware }
## Створення middleware
Щоб створити middleware, ви використовуєте декоратор `@app.middleware("http")` над функцією.
Щоб створити middleware, Ви використовуєте декоратор `@app.middleware("http")` на функції.
Функція middleware отримує:
* `request`.
* Функцію `call_next`, яка отримає `request` як параметр.
* Ця функція передасть `request` відповідній *операції шляху*.
* Потім вона поверне `response`, згенеровану відповідною *операцією шляху*.
* Потім ви можете додатково змінити `response` перед тим, як повернути її.
* `Запит`.
* Функцію `call_next`, яка приймає `запит` як параметр.
* Ця функція передає `запит` відповідній *операції шляху*.
* Потім вона повертає `відповідь`, згенеровану цією *операцією шляху*.
{* ../../docs_src/middleware/tutorial001_py39.py hl[8:9,11,14] *}
* Ви можете ще змінити `відповідь` перед тим, як повернути її.
{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *}
/// tip | Порада
Пам’ятайте, що власні пропрієтарні заголовки можна додавати <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">використовуючи префікс `X-`</a>.
Не забувайте, що власні заголовки можна додавати, використовуючи <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">префікс 'X-'</a>.
Але якщо у вас є власні заголовки, які ви хочете, щоб клієнт у браузері міг побачити, потрібно додати їх до ваших конфігурацій CORS ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) за допомогою параметра `expose_headers`, описаного в <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">документації Starlette по CORS</a>.
Але якщо у Вас є власні заголовки, які Ви хочете, щоб браузерний клієнт міг побачити, потрібно додати їх до Вашої конфігурації CORS (див. [CORS (Обмін ресурсами між різними джерелами)](cors.md){.internal-link target=_blank} за допомогою параметра `expose_headers`, описаного в <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">документації Starlette по CORS</a>.
///
@@ -45,50 +47,28 @@
Ви також можете використати `from starlette.requests import Request`.
**FastAPI** надає це для вашої зручності як розробника. Але воно походить безпосередньо зі Starlette.
**FastAPI** надає це для Вашої зручності як розробника. Але він походить безпосередньо зі Starlette.
///
### До і після `response` { #before-and-after-the-response }
### До і після `response`(`відповіді`)
Ви можете додати код, який буде виконуватися з `request`, до того, як його отримає будь-яка *операція шляху*.
Ви можете додати код, який буде виконуватися з `запитом` (`request`), до того, як його обробить будь-яка *операція шляху* (*path operation*).
Також ви можете додати код, який буде виконуватися після того, як `response` буде згенеровано, перед тим як її повернути.
Також Ви можете додати код, який буде виконуватися після того, як `відповідь` (`response`) буде згенеровано, перед тим як його повернути.
Наприклад, ви можете додати власний заголовок `X-Process-Time`, який міститиме час у секундах, який витратився на обробку запиту та генерацію відповіді:
Наприклад, Ви можете додати власний заголовок `X-Process-Time`, який міститиме час у секундах, який витратився на обробку запиту та генерацію відповіді:
{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *}
{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *}
/// tip | Порада
/// tip | Підказка
Тут ми використовуємо <a href="https://docs.python.org/3/library/time.html#time.perf_counter" class="external-link" target="_blank">`time.perf_counter()`</a> замість `time.time()` оскільки він може бути більш точним для таких випадків. 🤓
///
## Порядок виконання кількох middleware { #multiple-middleware-execution-order }
Коли ви додаєте кілька middleware, використовуючи або декоратор `@app.middleware()` або метод `app.add_middleware()`, кожен новий middleware обгортає застосунок, утворюючи стек. Останній доданий middleware є *зовнішнім*, а перший — *внутрішнім*.
На шляху запиту першим виконується *зовнішній* middleware.
На шляху відповіді він виконується останнім.
Наприклад:
```Python
app.add_middleware(MiddlewareA)
app.add_middleware(MiddlewareB)
```
Це призводить до такого порядку виконання:
* **Запит**: MiddlewareB → MiddlewareA → route
* **Відповідь**: route → MiddlewareA → MiddlewareB
Така поведінка стеку гарантує, що middleware виконуються у передбачуваному та керованому порядку.
## Інші middlewares { #other-middlewares }
## Інші middlewares
Ви можете пізніше прочитати більше про інші middlewares в [Advanced User Guide: Advanced Middleware](../advanced/middleware.md){.internal-link target=_blank}.

View File

@@ -1,8 +1,8 @@
# Параметри шляху та валідація числових даних { #path-parameters-and-numeric-validations }
# Path Параметри та валідація числових даних
Так само як ви можете оголошувати додаткові перевірки та метадані для query параметрів за допомогою `Query`, ви можете оголошувати той самий тип перевірок і метаданих для параметрів шляху за допомогою `Path`.
Так само як Ви можете оголошувати додаткові перевірки та метадані для query параметрів за допомогою `Query`, Ви можете оголошувати той самий тип перевірок і метаданих для параметрів шляху за допомогою `Path`.
## Імпорт `Path` { #import-path }
## Імпорт Path
Спочатку імпортуйте `Path` з `fastapi` і імпортуйте `Annotated`:
@@ -10,69 +10,70 @@
/// info | Інформація
FastAPI додав підтримку `Annotated` (і почав рекомендувати його використання) у версії 0.95.0.
FastAPI додав підтримку `Annotated` (і почав рекомендувати його використання) у версії 0.95.0.
Якщо у вас стара версія, при спробі використати `Annotated` можуть виникати помилки.
Якщо у Вас стара версія, при спробі використати `Annotated` можуть виникати помилки.
Переконайтеся, що ви [оновили версію FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} принаймні до версії 0.95.1 перед використанням `Annotated`.
Переконайтеся, що Ви [оновили версію FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} принаймні до версії 0.95.1 перед використанням `Annotated`.
///
## Оголошення метаданих { #declare-metadata }
## Оголошення метаданих
Ви можете оголошувати всі ті ж параметри, що і для `Query`.
Наприклад, щоб оголосити значення метаданих `title` для параметра шляху `item_id`, ви можете написати:
Наприклад, щоб оголосити значення метаданих `title` для параметра шляху `item_id`, Ви можете написати:
{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *}
/// note | Примітка
Параметр шляху завжди є обов’язковим, оскільки він має бути частиною шляху. Навіть якщо ви оголосите його зі значенням `None` або встановите значення за замовчуванням — це ні на що не вплине, він все одно завжди буде обов’язковим.
Параметр шляху завжди є обов’язковим, оскільки він має бути частиною шляху. Навіть якщо Ви оголосите його зі значенням `None` або встановите значення за замовчуванням — він все одно залишатиметься обов’язковим.
///
## Упорядковуйте параметри, як вам потрібно { #order-the-parameters-as-you-need }
## Упорядковуйте параметри, як Вам потрібно
/// tip | Порада
/// tip | Підказка
Це, мабуть, не настільки важливо або необхідно, якщо ви використовуєте `Annotated`.
Це, мабуть, не настільки важливо або необхідно, якщо Ви використовуєте `Annotated`.
///
Припустимо, ви хочете оголосити параметр запиту `q` як обов’язковий `str`.
Припустимо, Ви хочете оголосити параметр запиту `q` як обов’язковий `str`.
І вам не потрібно оголошувати нічого іншого для цього параметра, тому вам насправді не потрібно використовувати `Query`.
І Вам не потрібно оголошувати нічого іншого для цього параметра, тому немає потреби використовувати `Query`.
Але вам все одно потрібно використовувати `Path` для параметра шляху `item_id`. І з певних причин ви не хочете використовувати `Annotated`.
Але Вам все одно потрібно використовувати `Path` для параметра шляху `item_id`. І з певних причин Ви не хочете використовувати `Annotated`.
Python видасть помилку, якщо розмістити значення з "default" перед значенням, яке не має "default".
Але ви можете змінити порядок і розмістити значення без значення за замовчуванням (параметр запиту `q`) першим.
Але Ви можете змінити порядок і розмістити значення без значення за замовчуванням (параметр запиту `q`) першим.
Для **FastAPI** порядок не має значення. Він визначає параметри за їхніми іменами, типами та оголошеннями за замовчуванням (`Query`, `Path` тощо) і не звертає уваги на порядок.
Тому ви можете оголосити вашу функцію так:
Для **FastAPI** порядок не має значення. Він визначає параметри за їх іменами, типами та значеннями за замовчуванням (`Query`, `Path` тощо) і не звертає уваги на порядок.
{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *}
Тому Ви можете оголосити Вашу функцію так:
Але майте на увазі, що якщо ви використовуєте `Annotated`, цієї проблеми не буде, це не матиме значення, оскільки ви не використовуєте значення за замовчуванням параметрів функції для `Query()` або `Path()`.
{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *}
Але майте на увазі, що якщо Ви використовуєте `Annotated`, ця проблема не виникне, оскільки Ви не використовуєте значення за замовчуванням для параметрів `Query()` або `Path()`.
{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *}
## Упорядковуйте параметри, як вам потрібно: хитрощі { #order-the-parameters-as-you-need-tricks }
## Упорядковуйте параметри за потребою, хитрощі
/// tip | Порада
/// tip | Підказка
Це, мабуть, не настільки важливо або необхідно, якщо ви використовуєте `Annotated`.
Це, мабуть, не настільки важливо або необхідно, якщо Ви використовуєте `Annotated`.
///
Ось **невелика хитрість**, яка може стати в пригоді, хоча вона рідко знадобиться.
Якщо ви хочете:
Якщо Ви хочете:
* оголосити параметр запиту `q` без `Query` і без жодного значення за замовчуванням
* оголосити параметр запиту `q` без використання `Query` або значення за замовчуванням
* оголосити параметр шляху `item_id`, використовуючи `Path`
* розмістити їх у різному порядку
* не використовувати `Annotated`
@@ -83,72 +84,72 @@ Python видасть помилку, якщо розмістити значен
Python нічого не зробить із цією `*`, але розпізнає, що всі наступні параметри слід викликати як аргументи за ключовим словом (пари ключ-значення), також відомі як <abbr title="From: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>. Навіть якщо вони не мають значення за замовчуванням.
{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *}
{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
### Краще з `Annotated` { #better-with-annotated }
### Краще з `Annotated`
Майте на увазі, що якщо ви використовуєте `Annotated`, оскільки ви не використовуєте значення за замовчуванням параметрів функції, цієї проблеми не буде, і, ймовірно, вам не потрібно буде використовувати `*`.
Майте на увазі, якщо Ви використовуєте `Annotated`, оскільки Ви не використовуєте значення за замовчуванням для параметрів функції, цієї проблеми не виникне, і, швидше за все, Вам не потрібно буде використовувати `*`.
{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *}
## Валідація числових даних: більше або дорівнює { #number-validations-greater-than-or-equal }
## Валідація числових даних: більше або дорівнює
За допомогою `Query` і `Path` (та інших, які ви побачите пізніше) можна оголошувати числові обмеження.
За допомогою `Query` і `Path` (та інших, які Ви побачите пізніше) можна оголошувати числові обмеження.
Тут, завдяки `ge=1`, `item_id` має бути цілим числом, яке "`g`reater than or `e`qual" (більше або дорівнює) `1`.
{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *}
## Валідація числових даних: більше ніж і менше або дорівнює { #number-validations-greater-than-and-less-than-or-equal }
## Валідація числових даних: більше ніж і менше або дорівнює
Те саме застосовується до:
* `gt`: `g`reater `t`han
* `le`: `l`ess than or `e`qual
* `gt`: `g`reater `t`han (більше ніж)
* `le`: `l`ess than or `e`qual (менше або дорівнює)
{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *}
## Валідація числових даних: float, більше ніж і менше ніж { #number-validations-floats-greater-than-and-less-than }
## Валідація числових даних: float, більше ніж і менше ніж
Валідація чисел також працює для значень типу `float`.
Ось де стає важливо мати можливість оголошувати <abbr title="greater than"><code>gt</code></abbr>, а не тільки <abbr title="greater than or equal"><code>ge</code></abbr>. Це дозволяє, наприклад, вимагати, щоб значення було більше `0`, навіть якщо воно менше `1`.
Ось де стає важливо мати можливість оголошувати <abbr title="greater than (більше ніж)"><code>gt</code></abbr>, а не тільки <abbr title="greater than or equal (більше або дорівнює)"><code>ge</code></abbr>. Це дозволяє, наприклад, вимагати, щоб значення було більше `0`, навіть якщо воно менше `1`.
Таким чином, значення `0.5` буде допустимим. Але `0.0` або `0` — ні.
Те саме стосується <abbr title="less than"><code>lt</code></abbr>.
Те саме стосується <abbr title="less than (менше ніж)"><code>lt</code></abbr>.
{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *}
## Підсумок { #recap }
## Підсумок
За допомогою `Query`, `Path` (і інших параметрів, які ви ще не бачили) можна оголошувати метадані та перевірки рядків так само як у [Query параметри та валідація рядків](query-params-str-validations.md){.internal-link target=_blank}.
За допомогою `Query`, `Path` (і інших параметрів, які Ви ще не бачили) можна оголошувати метадані та перевірки рядків, так само як у [Query параметри та валідація рядків](query-params-str-validations.md){.internal-link target=_blank}.
Також можна оголошувати числові перевірки:
* `gt`: `g`reater `t`han
* `ge`: `g`reater than or `e`qual
* `lt`: `l`ess `t`han
* `le`: `l`ess than or `e`qual
* `gt`: `g`reater `t`han (більше ніж)
* `ge`: `g`reater than or `e`qual (більше або дорівнює)
* `lt`: `l`ess `t`han (менше ніж)
* `le`: `l`ess than or `e`qual (менше або дорівнює)
/// info | Інформація
`Query`, `Path` та інші класи, які ви побачите пізніше, є підкласами спільного класу `Param`.
`Query`, `Path` та інші класи, які Ви побачите пізніше, є підкласами спільного класу `Param`.
Всі вони мають однакові параметри для додаткових перевірок і метаданих, які ви вже бачили.
Всі вони мають однакові параметри для додаткових перевірок і метаданих, які Ви вже бачили.
///
/// note | Технічні деталі
Коли ви імпортуєте `Query`, `Path` та інші з `fastapi`, насправді це функції.
Коли Ви імпортуєте `Query`, `Path` та інші з `fastapi`, насправді це функції.
При виклику вони повертають екземпляри класів з такими ж іменами.
Тобто ви імпортуєте `Query`, яка є функцією. А коли ви її викликаєте, вона повертає екземпляр класу, який теж називається `Query`.
Тобто Ви імпортуєте `Query`, яка є функцією. А коли Ви її викликаєте, вона повертає екземпляр класу, який теж називається `Query`.
Ці функції створені таким чином (замість використання класів напряму), щоб ваш редактор не відзначав їхні типи як помилки.
Ці функції створені таким чином (замість використання класів напряму), щоб Ваш редактор не відзначав їхні типи як помилки.
Таким чином, ви можете користуватися своїм звичайним редактором і інструментами для програмування без додаткових налаштувань для ігнорування таких помилок.
Таким чином, Ви можете користуватися своїм звичайним редактором і інструментами для програмування без додаткових налаштувань для ігнорування таких помилок.
///

View File

@@ -1,34 +1,34 @@
# Параметри шляху { #path-parameters }
# Path Параметри
Ви можете оголосити «параметри» або «змінні» шляху, використовуючи той самий синтаксис, що й у форматованих рядках Python:
Ви можете визначити "параметри" або "змінні" шляху, використовуючи синтаксис форматованих рядків:
{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *}
{* ../../docs_src/path_params/tutorial001.py hl[6:7] *}
Значення параметра шляху `item_id` буде передано у вашу функцію як аргумент `item_id`.
Значення параметра шляху `item_id` передається у функцію як аргумент `item_id`.
Отже, якщо ви запустите цей приклад і перейдете за посиланням <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, то побачите відповідь:
Якщо запустити цей приклад та перейти за посиланням <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, то отримаємо таку відповідь:
```JSON
{"item_id":"foo"}
```
## Параметри шляху з типами { #path-parameters-with-types }
## Path параметри з типами
Ви можете оголосити тип параметра шляху у функції, використовуючи стандартні анотації типів Python:
Ви можете визначити тип параметра шляху у функції, використовуючи стандартні анотації типів Python:
{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *}
{* ../../docs_src/path_params/tutorial002.py hl[7] *}
У цьому випадку `item_id` оголошено як `int`.
У такому випадку `item_id` визначається як `int`.
/// check | Примітка
Це дасть вам підтримку редактора всередині функції з перевірками помилок, автодоповненням тощо.
Це дасть можливість підтримки редактора всередині функції з перевірками помилок, автодоповнення тощо.
///
## <abbr title="also known as: serialization, parsing, marshalling також відомо як: серіалізація, парсинг, маршалізація">Перетворення</abbr> даних { #data-conversion }
## <abbr title="або: серіалізація, парсинг, маршалізація">Перетворення</abbr> даних
Якщо ви запустите цей приклад і відкриєте у браузері <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>, то побачите відповідь:
Якщо запустити цей приклад і перейти за посиланням <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>, то отримаєте таку відповідь:
```JSON
{"item_id":3}
@@ -36,15 +36,15 @@
/// check | Примітка
Зверніть увагу, що значення, яке отримала (і повернула) ваша функція, — це `3`, як Python `int`, а не рядок `"3"`.
Зверніть увагу, що значення, яке отримала (і повернула) ваша функція, — це `3`. Це Python `int`, а не рядок `"3"`.
Отже, з таким оголошенням типу **FastAPI** надає вам автоматичний <abbr title="converting the string that comes from an HTTP request into Python data перетворення рядка, що надходить із HTTP-запиту, у дані Python">«parsing»</abbr> запиту.
Отже, з таким оголошенням типу **FastAPI** автоматично виконує <abbr title="перетворення рядка, що надходить із HTTP-запиту, у типи даних Python">"парсинг"</abbr> запитів.
///
## Валідація даних { #data-validation }
## <abbr title="Або валідація">Перевірка</abbr> даних
Але якщо ви перейдете у браузері за посиланням <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, ви побачите гарну HTTP-помилку:
Якщо ж відкрити у браузері посилання <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>, то побачимо цікаву HTTP-помилку:
```JSON
{
@@ -61,136 +61,144 @@
]
}
```
тому що параметр шляху має значення `"foo"`, яке не є типом `int`.
тому що параметр шляху `item_id` мав значення `"foo"`, яке не є `int`.
Та сама помилка з’явиться, якщо ви передасте `float` замість `int`, як у: <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2</a>
Таку саму помилку отримаємо, якщо передати `float` замість `int`, як бачимо, у цьому прикладі: <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2</a>
/// check | Примітка
Отже, з тим самим оголошенням типу в Python **FastAPI** надає вам валідацію даних.
Отже, **FastAPI** надає перевірку типів з таким самим оголошенням типу в Python.
Зверніть увагу, що помилка також чітко вказує саме місце, де валідація не пройшла.
Зверніть увагу, що помилка також чітко вказує саме на те місце, де валідація не пройшла.
Це неймовірно корисно під час розробки та налагодження коду, що взаємодіє з вашим API.
Це неймовірно корисно під час розробки та дебагінгу коду, що взаємодіє з вашим API.
///
## Документація { #documentation }
## Документація
А коли ви відкриєте у браузері <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>, ви побачите автоматичну, інтерактивну, API-документацію на кшталт:
Тепер коли відкриєте свій браузер за посиланням <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>, то побачите автоматично згенеровану, інтерактивну API-документацію:
<img src="/img/tutorial/path-params/image01.png">
/// check | Примітка
Знову ж таки, лише з тим самим оголошенням типу в Python **FastAPI** надає вам автоматичну, інтерактивну документацію (з інтеграцією Swagger UI).
Знову ж таки, лише з цим самим оголошенням типу в Python, FastAPI надає вам автоматичну, інтерактивну документацію (з інтеграцією Swagger UI).
Зверніть увагу, що параметр шляху оголошений як ціле число.
Зверніть увагу, що параметр шляху оголошено як ціле число.
///
## Переваги стандартів, альтернативна документація { #standards-based-benefits-alternative-documentation }
## Переваги стандартизації, альтернативна документація
І оскільки згенерована схема відповідає стандарту <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md" class="external-link" target="_blank">OpenAPI</a>, існує багато сумісних інструментів.
Через це **FastAPI** також надає альтернативну API-документацію (використовуючи ReDoc), до якої ви можете отримати доступ за посиланням <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>:
З цієї причини FastAPI також надає альтернативну документацію API (використовуючи ReDoc), до якої можна отримати доступ за посиланням <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>:
<img src="/img/tutorial/path-params/image02.png">
Так само, існує багато сумісних інструментів. Зокрема інструменти генерації коду для багатьох мов.
Таким чином, існує багато сумісних інструментів, включаючи інструменти для генерації коду для багатьох мов.
## Pydantic { #pydantic }
Уся валідація даних виконується за лаштунками за допомогою <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>, тож ви отримуєте всі переваги від його використання. І ви знаєте, що ви в надійних руках.
## Pydantic
Ви можете використовувати ті самі оголошення типів з `str`, `float`, `bool` та багатьма іншими складними типами даних.
Вся валідація даних виконується за лаштунками за допомогою <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a>, тому Ви отримуєте всі переваги від його використання. І можете бути впевнені, що все в надійних руках.
Декілька з них розглядаються в наступних розділах посібника.
Ви можете використовувати ті самі оголошення типів з `str`, `float`, `bool` та багатьма іншими складними типами даних.
## Порядок має значення { #order-matters }
Декілька з них будуть розглянуті в наступних розділах посібника.
Під час створення *операцій шляху* можуть виникати ситуації, коли у вас є фіксований шлях.
## Порядок має значення
Наприклад, `/users/me` — припустімо, це для отримання даних про поточного користувача.
При створенні *операцій шляху* можуть виникати ситуації, коли шлях фіксований.
І тоді у вас також може бути шлях `/users/{user_id}` для отримання даних про конкретного користувача за його ID.
Наприклад, `/users/me`. Припустимо, що це шлях для отримання даних про поточного користувача.
Оскільки *операції шляху* оцінюються по черзі, вам потрібно переконатися, що шлях для `/users/me` оголошено перед шляхом для `/users/{user_id}`:
А також у вас може бути шлях `/users/{user_id}`, щоб отримати дані про конкретного користувача за його ID.
{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *}
Оскільки *операції шляху* оцінюються по черзі, Ви повинні переконатися, що шлях для `/users/me` оголошений перед шляхом для `/users/{user_id}`:
Інакше шлях для `/users/{user_id}` також відповідатиме `/users/me`, «вважаючи», що отримує параметр `user_id` зі значенням `"me"`.
{* ../../docs_src/path_params/tutorial003.py hl[6,11] *}
Так само ви не можете перевизначити операцію шляху:
Інакше шлях для `/users/{user_id}` також буде відповідати для `/users/me`, "вважаючи", що він отримує параметр `user_id` зі значенням `"me"`.
{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *}
Аналогічно, Ви не можете оголосити операцію шляху:
Завжди використовуватиметься перша, оскільки шлях збігається першим.
{* ../../docs_src/path_params/tutorial003b.py hl[6,11] *}
## Попередньо визначені значення { #predefined-values }
Перша операція буде завжди використовуватися, оскільки шлях збігається першим.
## Попередньо визначені значення
Якщо у вас є *операція шляху*, яка отримує *параметр шляху*, але ви хочете, щоб можливі коректні значення *параметра шляху* були попередньо визначені, ви можете використати стандартний Python <abbr title="Enumeration">`Enum`</abbr>.
Якщо у вас є *операція шляху*, яка приймає *параметр шляху*, але Ви хочете, щоб можливі допустимі значення *параметра шляху* були попередньо визначені, Ви можете використати стандартний Python <abbr title="перелічення">Enum</abbr>.
### Створіть клас `Enum` { #create-an-enum-class }
### Створення класу `Enum`
Імпортуйте `Enum` і створіть підклас, що наслідується від `str` та `Enum`.
Завдяки наслідуванню від `str` документація API зможе визначити, що значення повинні бути типу `string`, і зможе коректно їх відобразити.
Наслідуючи від `str`, документація API зможе визначити, що значення повинні бути типу `string`, і правильно їх відобразить.
Після цього створіть атрибути класу з фіксованими значеннями, які будуть доступними коректними значеннями:
Після цього створіть атрибути класу з фіксованими значеннями, які будуть доступними допустимими значеннями:
{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *}
{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *}
/// tip | Порада
/// info | Додаткова інформація
Якщо вам цікаво, «AlexNet», «ResNet» та «LeNet» — це просто назви Machine Learning <abbr title="Technically, Deep Learning model architectures технічно, архітектури моделей Deep Learning">models</abbr>.
<a href="https://docs.python.org/3/library/enum.html" class="external-link" target="_blank">Перелічення (або enums) доступні в Python</a> починаючи з версії 3.4.
///
### Оголосіть *параметр шляху* { #declare-a-path-parameter }
/// tip | Порада
Якщо вам цікаво, "AlexNet", "ResNet" та "LeNet" — це просто назви ML моделей <abbr title="Технічно, архітектури Deep Learning моделей">Machine Learning</abbr>.
///
### Оголосіть *параметр шляху*
Потім створіть *параметр шляху* з анотацією типу, використовуючи створений вами клас enum (`ModelName`):
{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *}
{* ../../docs_src/path_params/tutorial005.py hl[16] *}
### Перевірте документацію { #check-the-docs }
### Перевірка документації
Оскільки доступні значення для *параметра шляху* визначені заздалегідь, інтерактивна документація може красиво їх показати:
Оскільки доступні значення для *параметра шляху* визначені заздалегідь, інтерактивна документація зможе красиво їх відобразити:
<img src="/img/tutorial/path-params/image03.png">
### Робота з Python *переліченнями* { #working-with-python-enumerations }
### Робота з *перелічуваннями* у Python
Значення *параметра шляху* буде *елементом перелічування*.
Значення *параметра шляху* буде елементом *перелічування*.
#### Порівняйте *елементи перелічування* { #compare-enumeration-members }
#### Порівняння *елементів перелічування*
Ви можете порівнювати його з *елементом перелічування* у створеному вами enum `ModelName`:
Ви можете порівнювати його з *елементами перелічування* у створеному вами enum `ModelName`:
{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *}
{* ../../docs_src/path_params/tutorial005.py hl[17] *}
#### Отримайте *значення перелічування* { #get-the-enumeration-value }
#### Отримання *значення перелічування*
Ви можете отримати фактичне значення (у цьому випадку це `str`), використовуючи `model_name.value`, або загалом `your_enum_member.value`:
{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *}
{* ../../docs_src/path_params/tutorial005.py hl[20] *}
/// tip | Порада
Ви також можете отримати доступ до значення `"lenet"` через `ModelName.lenet.value`.
Ви також можете отримати доступ до значення `"lenet"`, використовуючи `ModelName.lenet.value`.
///
#### Поверніть *елементи перелічування* { #return-enumeration-members }
Ви можете повертати *елементи enum* з вашої *операції шляху*, навіть вкладені у JSON-тіло (наприклад, `dict`).
#### Повернення *елементів перелічування*
Ви можете повертати *елементи перелічування* з вашої *операції шляху*, навіть вкладені у JSON-тіло (наприклад, `dict`).
Вони будуть перетворені на відповідні значення (у цьому випадку рядки) перед поверненням клієнту:
{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *}
{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *}
На стороні клієнта ви отримаєте відповідь у форматі JSON, наприклад:
На стороні клієнта Ви отримаєте відповідь у форматі JSON, наприклад:
```JSON
{
@@ -199,35 +207,36 @@
}
```
## Параметри шляху, що містять шляхи { #path-parameters-containing-paths }
## Path-параметри, що містять шляхи
Припустімо, у вас є *операція шляху* зі шляхом `/files/{file_path}`.
Припустимо, у вас є *операція шляху* з маршрутом `/files/{file_path}`.
Але вам потрібно, щоб `file_path` сам містив *шлях*, наприклад `home/johndoe/myfile.txt`.
Але вам потрібно, щоб `file_path` містив *шлях*, наприклад `home/johndoe/myfile.txt`.
Отже, URL для цього файлу виглядатиме приблизно так: `/files/home/johndoe/myfile.txt`.
Отже, URL для цього файлу виглядатиме так: `/files/home/johndoe/myfile.txt`.
### Підтримка OpenAPI { #openapi-support }
OpenAPI не підтримує спосіб оголошення *параметра шляху*, який має містити всередині *шлях*, оскільки це може призвести до сценаріїв, які складно тестувати та визначати.
Проте ви все одно можете зробити це в **FastAPI**, використовуючи один із внутрішніх інструментів Starlette.
### Підтримка OpenAPI
І документація все ще працюватиме, хоча й не додаватиме жодної документації, яка б казала, що параметр має містити шлях.
OpenAPI не підтримує спосіб оголошення *параметра шляху*, що містить *шлях* всередині, оскільки це може призвести до сценаріїв, які складно тестувати та визначати.
### Конвертер шляху { #path-convertor }
Однак (одначе), Ви все одно можете зробити це в **FastAPI**, використовуючи один із внутрішніх інструментів Starlette.
Використовуючи опцію безпосередньо зі Starlette, ви можете оголосити *параметр шляху*, що містить *шлях*, використовуючи URL на кшталт:
Документація все ще працюватиме, хоча й не додаватиме опису про те, що параметр повинен містити шлях.
### Конвертер шляху
Використовуючи опцію безпосередньо зі Starlette, Ви можете оголосити *параметр шляху*, що містить *шлях*, використовуючи URL на кшталт:
```
/files/{file_path:path}
```
У цьому випадку ім'я параметра — `file_path`, а остання частина `:path` вказує на те, що параметр повинен відповідати будь-якому *шляху*.
У цьому випадку ім’я параметра — `file_path`, а остання частина `:path` вказує, що параметр має відповідати будь-якому *шляху*.
Отже, Ви можете використати його так:
Отже, ви можете використати його так:
{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *}
{* ../../docs_src/path_params/tutorial004.py hl[6] *}
/// tip | Порада
@@ -237,15 +246,15 @@ OpenAPI не підтримує спосіб оголошення *параме
///
## Підсумок { #recap }
## Підсумок
З **FastAPI**, використовуючи короткі, інтуїтивно зрозумілі та стандартні оголошення типів Python, ви отримуєте:
З **FastAPI**, використовуючи короткі, інтуїтивно зрозумілі та стандартні оголошення типів Python, Ви отримуєте:
* Підтримку редактора: перевірка помилок, автодоповнення тощо.
* Перетворення даних «<abbr title="converting the string that comes from an HTTP request into Python data перетворення рядка, що надходить з HTTP-запиту, у дані Python">parsing</abbr>»
* Підтримку в редакторі: перевірка помилок, автодоповнення тощо.
* "<abbr title="перетворення рядка, що надходить з HTTP-запиту, у типи даних Python">Парсинг</abbr>" даних
* Валідацію даних
* Анотацію API та автоматичну документацію
І вам потрібно оголосити їх лише один раз.
Це, ймовірно, основна видима перевага **FastAPI** порівняно з альтернативними фреймворками (окрім сирої продуктивності).
Це, ймовірно, основна видима перевага **FastAPI** порівняно з альтернативними фреймворками (окрім високої продуктивності).

View File

@@ -1,4 +1,4 @@
# Моделі параметрів запиту { #query-parameter-models }
# Моделі Query параметрів
Якщо у Вас є група **query параметрів**, які пов’язані між собою, Ви можете створити **Pydantic-модель** для їх оголошення.
@@ -10,7 +10,7 @@
///
## Query параметри з Pydantic-моделлю { #query-parameters-with-a-pydantic-model }
## Query параметри з Pydantic-моделлю
Оголосіть **query параметри**, які Вам потрібні, у **Pydantic-моделі**, а потім оголосіть цей параметр як `Query`:
@@ -18,7 +18,7 @@
**FastAPI** буде **витягувати** дані для **кожного поля** з **query параметрів** у запиті та передавати їх у визначену вами Pydantic-модель.
## Перевірте документацію { #check-the-docs }
## Перевірте документацію
Ви можете побачити параметри запиту в UI документації за `/docs`:
@@ -26,7 +26,7 @@
<img src="/img/tutorial/query-param-models/image01.png">
</div>
## Заборона зайвих Query параметрів { #forbid-extra-query-parameters }
## Заборона зайвих Query параметрів
У деяких особливих випадках (ймовірно, не дуже поширених) Ви можете захотіти **обмежити** query параметри, які дозволено отримувати.
@@ -34,7 +34,7 @@
{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *}
Якщо клієнт спробує надіслати **зайві** дані у **query параметрах**, він отримає **помилку** відповідь.
Якщо клієнт спробує надіслати **зайві** дані у **query параметрах**, він отримає **помилку**.
Наприклад, якщо клієнт спробує надіслати query параметр `tool` зі значенням `plumbus`, як у цьому запиті:
@@ -57,11 +57,11 @@ https://example.com/items/?limit=10&tool=plumbus
}
```
## Підсумок { #summary }
## Підсумок
Ви можете використовувати **Pydantic-моделі** для оголошення **query параметрів** у **FastAPI**. 😎
/// tip | Порада
/// tip | Підказка
Спойлер: Ви також можете використовувати Pydantic-моделі для оголошення cookie та заголовків, але про це Ви дізнаєтеся пізніше в цьому посібнику. 🤫

View File

@@ -1,26 +1,26 @@
# Query параметри та валідація рядків { #query-parameters-and-string-validations }
# Query параметри та валідація рядків
**FastAPI** дозволяє оголошувати додаткову інформацію та виконувати валідацію для ваших параметрів.
**FastAPI** дозволяє оголошувати додаткову інформацію та виконувати валідацію для Ваших параметрів.
Розглянемо цей додаток як приклад:
{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *}
Query параметр `q` має тип `str | None`, що означає, що він має тип `str`, але також може бути `None`, і справді, значення за замовчуванням — `None`, тож FastAPI знатиме, що він не є обов'язковим.
Query параметр `q` має тип `str | None`, що означає, що він може бути як `str`, так і `None`. За замовчуванням він має значення `None`, тому FastAPI розуміє, що цей параметр не є обов'язковим.
/// note | Примітка
FastAPI знатиме, що значення `q` не є обов’язковим, завдяки значенню за замовчуванням `= None`.
FastAPI знає, що `q` не є обов’язковим, завдяки значенню за замовчуванням `= None`.
Використання `str | None` дозволить вашому редактору коду надавати кращу підтримку та виявляти помилки.
Використання `str | None` дозволить Вашому редактору коду надавати кращу підтримку та виявляти помилки.
///
## Додаткова валідація { #additional-validation }
## Додаткова валідація
Ми хочемо, щоб навіть якщо `q` є необов’язковим, коли його передають, **його довжина не перевищувала 50 символів**.
Ми хочемо, щоб навіть якщо `q` є необов’язковим, **його довжина не перевищувала 50 символів**, якщо він все ж буде переданий.
### Імпорт `Query` та `Annotated` { #import-query-and-annotated }
### Імпорт `Query` та `Annotated`
Щоб це зробити, спочатку імпортуємо:
@@ -33,13 +33,13 @@ FastAPI знатиме, що значення `q` не є обов’язков
FastAPI додав підтримку `Annotated` (і почав рекомендувати його) у версії 0.95.0.
Якщо у вас старіша версія, під час використання `Annotated` можуть виникати помилки.
Якщо у Вас старіша версія, під час використання `Annotated` можуть виникати помилки.
Переконайтеся, що ви [оновили версію FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} до принаймні 0.95.1, перш ніж використовувати `Annotated`.
Переконайтеся, що Ви [оновили версію FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} до принаймні 0.95.1, перш ніж використовувати `Annotated`.
///
## Використання `Annotated` у типі параметра `q` { #use-annotated-in-the-type-for-the-q-parameter }
## Використання `Annotated` у типі параметра `q`
Пам’ятаєте, як я раніше розповідав, що `Annotated` можна використовувати для додавання метаданих до параметрів у [Вступі до типів Python](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}?
@@ -55,7 +55,7 @@ q: str | None = None
////
//// tab | Python 3.9+
//// tab | Python 3.8+
```Python
q: Union[str, None] = None
@@ -73,7 +73,7 @@ q: Annotated[str | None] = None
////
//// tab | Python 3.9+
//// tab | Python 3.8+
```Python
q: Annotated[Union[str, None]] = None
@@ -85,33 +85,33 @@ q: Annotated[Union[str, None]] = None
А тепер переходимо до цікавого! 🎉
## Додавання `Query` до `Annotated` у параметр `q` { #add-query-to-annotated-in-the-q-parameter }
## Додавання `Query` до `Annotated` у параметр `q`
Тепер, коли у нас є `Annotated`, де ми можемо додавати додаткову інформацію (у цьому випадку — додаткову валідацію), додамо `Query` всередину `Annotated` і встановимо параметр `max_length` у `50`:
Тепер, коли у нас є `Annotated`, де ми можемо додавати додаткову інформацію (зокрема валідацію), додамо `Query` всередину `Annotated` і встановимо параметр `max_length` у `50`:
{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[9] *}
Зверніть увагу, що значення за замовчуванням усе ще `None`, тому параметр залишається необов'язковим.
Але тепер, додавши `Query(max_length=50)` всередину `Annotated`, ми повідомляємо FastAPI, що хочемо **додаткову валідацію** для цього значення: ми хочемо, щоб воно мало максимум 50 символів. 😎
Але тепер, додавши `Query(max_length=50)` всередину `Annotated`, ми повідомляємо FastAPI, що хочемо **додаткову валідацію** для цього значення — воно має містити максимум 50 символів. 😎
/// tip | Порада
/// tip | Підказка
Тут ми використовуємо `Query()`, оскільки це **query параметр**. Далі ми розглянемо інші варіанти, як-от `Path()`, `Body()`, `Header()` та `Cookie()`, які приймають ті самі аргументи, що й `Query()`.
Ми використовуємо `Query()`, оскільки це **query параметр**. Далі ми розглянемо інші варіанти, як-от `Path()`, `Body()`, `Header()` та `Cookie()`, які приймають ті самі аргументи, що й `Query()`.
///
Тепер FastAPI:
* **Перевірить** дані, щоб переконатися, що їхня максимальна довжина — 50 символів
* **Перевірить** дані, щоб переконатися, що їхня довжина не перевищує 50 символів
* Покажe **чітку помилку** клієнту, якщо дані недійсні
* **Задокументує** параметр в OpenAPI-схемі *операції шляху* (що відобразиться в **автоматично згенерованій документації**)
## Альтернативний (застарілий) метод: `Query` як значення за замовчуванням { #alternative-old-query-as-the-default-value }
## Альтернативний (застарілий) метод: Query як значення за замовчуванням
У попередніх версіях FastAPI (до <abbr title="before 2023-03 до 2023-03">0.95.0</abbr>) потрібно було використовувати `Query` як значення за замовчуванням параметра, замість того, щоб додавати його в `Annotated`. Є висока ймовірність, що ви зустрінете код із таким підходом, тож я поясню його.
У попередніх версіях FastAPI (до <abbr title="до 2023-03">0.95.0</abbr>) `Query` використовувався як значення за замовчуванням для параметра, а не всередині `Annotated`. Ви, ймовірно, побачите код, який використовує цей підхід, тому варто розглянути його.
/// tip | Порада
/// tip | Підказка
Для нового коду та коли це можливо, використовуйте `Annotated`, як показано вище. Це має багато переваг (пояснених нижче) і не має недоліків. 🍰
@@ -121,7 +121,7 @@ q: Annotated[Union[str, None]] = None
{* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *}
Оскільки в цьому випадку (без `Annotated`) нам потрібно замінити значення за замовчуванням `None` у функції на `Query()`, тепер ми повинні встановити значення за замовчуванням через параметр `Query(default=None)`. Це виконує ту саму роль визначення значення за замовчуванням (принаймні для FastAPI).
Оскільки в цьому випадку (без `Annotated`) нам потрібно замінити `None` у функції на `Query()`, тепер ми повинні явно встановити значення за замовчуванням через параметр `Query(default=None)`. Це виконує ту саму роль визначення значення за замовчуванням (принаймні для FastAPI).
Таким чином:
@@ -135,10 +135,9 @@ q: str | None = Query(default=None)
```Python
q: str | None = None
```
Але у версії з `Query` ми явно вказуємо, що це query параметр.
Але у версії з `Query` ми явно вказуємо, що це query параметр.
Далі ми можемо передавати `Query` додаткові параметри. У цьому випадку — параметр `max_length`, який застосовується до рядків:
Далі ми можемо передавати `Query` додаткові параметри, зокрема `max_length`, який застосовується до рядків:
```Python
q: str | None = Query(default=None, max_length=50)
@@ -146,11 +145,11 @@ q: str | None = Query(default=None, max_length=50)
Це забезпечить валідацію даних, виведе зрозумілу помилку у разі недійсних даних і задокументує параметр у схемі OpenAPI *операції шляху*.
### `Query` як значення за замовчуванням або всередині `Annotated` { #query-as-the-default-value-or-in-annotated }
### `Query` як значення за замовчуванням або всередині `Annotated`
Важливо пам’ятати, якщо використовувати `Query` всередині `Annotated`, не можна задавати параметр `default` у `Query`.
Замість цього використовуйте фактичне значення за замовчуванням параметра функції. Інакше це буде непослідовно.
Замість цього використовуйте значення за замовчуванням у самій функції. Інакше це буде нелогічно.
Наприклад, цей варіант є некоректним:
@@ -160,39 +159,39 @@ q: Annotated[str, Query(default="rick")] = "morty"
...тому, що не зрозуміло, яке значення має бути значенням за замовчуванням: `"rick"` чи `"morty"`.
Тож ви будете використовувати (бажано):
Коректні варіанти:
```Python
q: Annotated[str, Query()] = "rick"
```
...або у старих кодових базах ви знайдете:
...або у старих кодових базах Ви знайдете:
```Python
q: str = Query(default="rick")
```
### Переваги використання `Annotated` { #advantages-of-annotated }
### Переваги використання `Annotated`
**Використання `Annotated` є рекомендованим** замість задання значення за замовчуванням у параметрах функції, оскільки воно **краще** з кількох причин. 🤓
Значення **за замовчуванням** параметра **функції** є **фактичним значенням за замовчуванням**, що є більш інтуїтивним у Python загалом. 😌
Значення **за замовчуванням** параметра **функції** є його **фактичним значенням за замовчуванням**, що є більш інтуїтивним у Python загалом. 😌
Ви можете **викликати** ту саму функцію **в інших місцях** без FastAPI, і вона **працюватиме очікувано**. Якщо параметр є **обов’язковим** (без значення за замовчуванням), ваш **редактор** повідомить про помилку, а **Python** також видасть помилку, якщо ви виконаєте функцію без передавання цього параметра.
Ви можете **викликати** ту саму функцію **в інших місцях** без FastAPI, і вона **працюватиме очікувано**. Якщо параметр є **обов’язковим** (без значення за замовчуванням), Ваш **редактор** повідомить про помилку, а **Python** також видасть помилку, якщо Ви виконаєте функцію без передавання цього параметра.
Якщо ви не використовуєте `Annotated`, а використовуєте **(старий) стиль значень за замовчуванням**, то при виклику цієї функції без FastAPI **в інших місцях**, потрібно **пам’ятати** передати їй аргументи, щоб вона працювала коректно, інакше значення будуть відрізнятися від очікуваних (наприклад, ви отримаєте `QueryInfo` або щось подібне замість `str`). І ваш редактор не повідомить про помилку, і Python не скаржитиметься під час запуску цієї функції — лише коли операції всередині завершаться помилкою.
Якщо Ви не використовуєте `Annotated`, а використовуєте **(старий) стиль значень за замовчуванням**, то при виклику цієї функції без FastAPI **в інших місцях**, потрібно **не забути** передати їй аргументи, інакше значення будуть відрізнятися від очікуваних (наприклад, Ви отримаєте `QueryInfo` або подібне замість `str`). Ваш редактор не повідомить про помилку, і Python також не видасть помилку при запуску функції, поки не виникне помилка під час виконання операцій усередині.
Оскільки `Annotated` може містити кілька анотацій метаданих, тепер ви навіть можете використовувати ту саму функцію з іншими інструментами, такими як <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">Typer</a>. 🚀
Оскільки `Annotated` може містити кілька анотацій метаданих, Ви навіть можете використовувати ту саму функцію з іншими інструментами, такими як <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">Typer</a>. 🚀
## Додавання додаткових валідацій { #add-more-validations }
## Додавання додаткових валідацій
Ви також можете додати параметр `min_length`:
{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *}
## Додавання регулярних виразів { #add-regular-expressions }
## Додавання регулярних виразів
Ви можете визначити <abbr title="A regular expression, regex or regexp is a sequence of characters that define a search pattern for strings. Регулярний вираз (regex або regexp) — це послідовність символів, яка визначає шаблон для пошуку в рядках.">regular expression</abbr> `pattern`, якому має відповідати параметр:
Ви можете визначити <abbr title="Регулярний вираз (regex або regexp) — це послідовність символів, яка визначає шаблон для пошуку в рядках.">регулярний вираз</abbr> pattern, якому має відповідати параметр:
{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *}
@@ -202,27 +201,41 @@ q: str = Query(default="rick")
* `fixedquery`: точно відповідає значенню `fixedquery`.
* `$`: закінчується тут, після `fixedquery` немає жодних символів.
Якщо ви почуваєтеся розгублено щодо **«regular expression»**, не хвилюйтеся. Це складна тема для багатьох людей. Ви все одно можете робити багато речей без використання регулярних виразів.
Якщо Ви почуваєтеся розгублено щодо **"регулярних виразів"**, не хвилюйтеся. Вони є складною темою для багатьох людей. Ви все одно можете зробити багато речей без їх використання.
Тепер ви знаєте, що коли вони знадобляться, їх можна застосовувати у **FastAPI**.
Але тепер Ви знаєте, що коли вони знадобляться, їх можна застосовувати у **FastAPI**.
## Значення за замовчуванням { #default-values }
### Pydantic v1 `regex` замість `pattern`
Ви можете, звісно, використовувати значення за замовчуванням, відмінні від `None`.
До версії Pydantic 2 і FastAPI 0.100.0 параметр називався `regex` замість `pattern`, але тепер він застарів.
Припустімо, що ви хочете оголосити query параметр `q` з `min_length` `3` і значенням за замовчуванням `"fixedquery"`:
Ви все ще можете зустріти код, який використовує його:
//// tab | Pydantic v1
{* ../../docs_src/query_params_str_validations/tutorial004_regex_an_py310.py hl[11] *}
////
Але майте на увазі, що він є застарілим і його слід оновити до нового параметра `pattern`. 🤓
## Значення за замовчуванням
Ви можете використовувати значення за замовчуванням, відмінні від `None`.
Наприклад, якщо Ви хочете оголосити параметр запиту `q` з `min_length` `3` і значенням за замовчуванням `"fixedquery"`:
{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *}
/// note | Примітка
/// note | Технічні деталі
Наявність значення за замовчуванням будь-якого типу, включаючи `None`, робить параметр необов’язковим (not required).
///
## Обов’язкові параметри { #required-parameters }
## Обов’язкові параметри
Якщо нам не потрібно оголошувати додаткові валідації або метадані, ми можемо зробити query параметр `q` обов’язковим, просто не вказуючи значення за замовчуванням, наприклад:
Якщо нам не потрібно вказувати додаткові перевірки або метадані, ми можемо зробити параметр `q` обов’язковим, просто не оголошуючи значення за замовчуванням, наприклад:
```Python
q: str
@@ -234,39 +247,43 @@ q: str
q: str | None = None
```
Але тепер ми оголошуємо його з `Query`, наприклад так:
Але тепер ми оголошуємо його з `Query`, наприклад:
//// tab | Annotated
```Python
q: Annotated[str | None, Query(min_length=3)] = None
```
Тому, якщо вам потрібно оголосити значення як обов’язкове під час використання `Query`, просто не вказуйте значення за замовчуванням:
////
Тому, якщо Вам потрібно зробити значення обов’язковим, використовуючи `Query`, просто не вказуйте значення за замовчуванням:
{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *}
### Обов’язковий, може бути `None` { #required-can-be-none }
### Обов’язкове значення, яке може бути `None`
Ви можете вказати, що параметр може приймати `None`, але при цьому залишається обов’язковим. Це змусить клієнтів надіслати значення, навіть якщо значення дорівнює `None`.
Ви можете вказати, що параметр може приймати `None`, але при цьому залишається обов’язковим. Це змусить клієнтів надіслати значення, навіть якщо воно дорівнює `None`.
Щоб зробити це, оголосіть, що `None` є допустимим типом, але просто не вказуйте значення за замовчуванням:
Щоб зробити це, оголосіть, що `None` є допустимим типом, але не вказуйте значення за замовчуванням:
{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *}
## Список query параметрів / кілька значень { #query-parameter-list-multiple-values }
## Список параметрів запиту / кілька значень
Коли ви явно визначаєте query параметр за допомогою `Query`, ви також можете оголосити, що він має приймати список значень, або, іншими словами, кілька значень.
Якщо Ви визначаєте параметр запиту за допомогою `Query`, Ви також можете дозволити отримання списку значень, тобто дозволити отримання кількох значень.
Наприклад, щоб оголосити query параметр `q`, який може зявлятися в URL кілька разів, можна написати:
Наприклад, щоб дозволити параметру запиту `q` з'являтися кілька разів в URL, можна написати:
{* ../../docs_src/query_params_str_validations/tutorial011_an_py310.py hl[9] *}
Тоді, у випадку URL:
Тоді, у випадку запиту за URL:
```
http://localhost:8000/items/?q=foo&q=bar
```
ви отримаєте кілька значень `q` *query параметрів* (`foo` і `bar`) у вигляді Python `list` у вашій *функції операції шляху*, у *параметрі функції* `q`.
Ви отримаєте кілька значень *query параметра* `q` (`foo` і `bar`) у вигляді списку `list` в Python у Вашій *функції обробки шляху*, у *параметрі функції* `q`.
Отже, відповідь на цей URL буде:
@@ -279,9 +296,9 @@ http://localhost:8000/items/?q=foo&q=bar
}
```
/// tip | Порада
/// tip | Підказка
Щоб оголосити query параметр з типом `list`, як у наведеному вище прикладі, потрібно явно використовувати `Query`, інакше він буде інтерпретований як тіло запиту.
Щоб оголосити параметр запиту з типом `list`, як у наведеному вище прикладі, потрібно явно використовувати `Query`, інакше він буде інтерпретований як тіло запиту.
///
@@ -289,19 +306,19 @@ http://localhost:8000/items/?q=foo&q=bar
<img src="/img/tutorial/query-params-str-validations/image02.png">
### Список query параметрів / кілька значень за замовчуванням { #query-parameter-list-multiple-values-with-defaults }
### Список параметрів запиту / кілька значень за замовчуванням
Ви також можете визначити значення за замовчуванням `list`, якщо жодне значення не було передане:
Ви також можете визначити значення за замовчуванням для `list`, якщо жодне значення не було передане:
{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *}
Якщо ви перейдете за посиланням:
Якщо Ви перейдете за посиланням:
```
http://localhost:8000/items/
```
то значення `q` за замовчуванням буде: `["foo", "bar"]`, і ваша відповідь виглядатиме так:
то значення `q` за замовчуванням буде: `["foo", "bar"]`, і Ваша відповідь виглядатиме так:
```JSON
{
@@ -312,35 +329,35 @@ http://localhost:8000/items/
}
```
#### Використання тільки `list` { #using-just-list }
#### Використання тільки `list`
Ви також можете використовувати `list` напряму замість `list[str]`:
Ви також можете використовувати `list` без уточнення типу, замість `list[str]`:
{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *}
/// note | Примітка
/// note | Технічні деталі
Майте на увазі, що в цьому випадку FastAPI не перевірятиме вміст списку.
Наприклад, `list[int]` перевірятиме (і документуватиме), що вміст списку цілі числа. Але `list` без уточнення цього не робитиме.
Наприклад, `list[int]` перевірятиме (і документуватиме), що всі елементи списку є цілими числами. Але `list` без уточнення цього не робитиме.
///
## Оголосити більше метаданих { #declare-more-metadata }
## Додавання додаткових метаданих
Ви можете додати більше інформації про параметр.
Ця інформація буде включена у згенерований OpenAPI та використана інтерфейсами документації та зовнішніми інструментами.
Ця інформація буде включена у згенерований OpenAPI та використана в інтерфейсах документації та зовнішніх інструментах.
/// note | Примітка
/// note | Технічні деталі
Майте на увазі, що різні інструменти можуть мати різний рівень підтримки OpenAPI.
Деякі з них можуть ще не відображати всю додаткову інформацію, хоча в більшості випадків відсутню функцію вже заплановано до реалізації.
Деякі з них можуть ще не відображати всю додаткову інформацію, хоча в більшості випадків ця функція вже запланована для розробки.
///
Ви можете додати `title`:
Ви можете додати `title` :
{* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *}
@@ -348,9 +365,9 @@ http://localhost:8000/items/
{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *}
## Аліаси параметрів { #alias-parameters }
## Аліаси параметрів
Уявіть, що ви хочете, щоб параметр називався `item-query`.
Уявіть, що Ви хочете, щоб параметр називався `item-query`.
Наприклад:
@@ -362,19 +379,19 @@ http://127.0.0.1:8000/items/?item-query=foobaritems
Найближчий допустимий варіант — `item_query`.
Проте вам потрібно, щоб параметр залишався саме `item-query`...
Проте Вам потрібно, щоб параметр залишався саме `item-query`...
У такому випадку можна оголосити `alias`, і саме він буде використовуватися для отримання значення параметра:
{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *}
## Позначення параметрів як застарілих { #deprecating-parameters }
## Виведення параметрів як застарілих
Припустімо, що вам більше не подобається цей параметр.
Припустимо, що Ви більше не хочете використовувати цей параметр.
Вам потрібно залишити його на деякий час, оскільки ним користуються клієнти, але ви хочете, щоб документація чітко показувала, що він є <abbr title="obsolete, recommended not to use it застарілий, не рекомендується до використання">deprecated</abbr>.
Вам потрібно залишити його на деякий час, оскільки ним користуються клієнти, але Ви хочете, щоб документація чітко показувала, що він є <abbr title="застарілий, не рекомендується до використання">застарілим</abbr>.
Тоді передайте параметр `deprecated=True` до `Query`:
Тоді Ви можете передати параметр `deprecated=True` до `Query`:
{* ../../docs_src/query_params_str_validations/tutorial010_an_py310.py hl[19] *}
@@ -382,27 +399,27 @@ http://127.0.0.1:8000/items/?item-query=foobaritems
<img src="/img/tutorial/query-params-str-validations/image01.png">
## Виняток параметрів з OpenAPI { #exclude-parameters-from-openapi }
## Виняток параметрів з OpenAPI
Щоб виключити query параметр зі згенерованої схеми OpenAPI (і, таким чином, з автоматичних систем документації), встановіть параметр `include_in_schema` для `Query` в `False`:
Щоб виключити параметр запиту зі згенерованої схеми OpenAPI (і, таким чином, з автоматичних систем документації), встановіть параметр `include_in_schema` для `Query` в `False`:
{* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *}
## Кастомна валідація { #custom-validation }
## Кастомна валідація
Можуть бути випадки, коли вам потрібно провести **кастомну валідацію**, яку не можна реалізувати за допомогою параметрів, показаних вище.
Можуть бути випадки, коли Вам потрібно провести **кастомну валідацію**, яку не можна реалізувати за допомогою параметрів, показаних вище.
У таких випадках ви можете використати **кастомну функцію-валідатор**, яка буде застосована після звичайної валідації (наприклад, після перевірки, що значення є типом `str`).
У таких випадках ви можете використати **кастомну функцію валідації**, яка буде застосована після звичайної валідації (наприклад, після перевірки, що значення є типом `str`).
Це можна досягти за допомогою <a href="https://docs.pydantic.dev/latest/concepts/validators/#field-after-validator" class="external-link" target="_blank">Pydantic's `AfterValidator`</a> в середині `Annotated`.
/// tip | Порада
/// tip | Підказка
Pydantic також має <a href="https://docs.pydantic.dev/latest/concepts/validators/#field-before-validator" class="external-link" target="_blank">`BeforeValidator`</a> та інші. 🤓
///
Наприклад, цей кастомний валідатор перевіряє, чи починається ID елемента з `isbn-` для номера книги <abbr title="ISBN means International Standard Book Number ISBN означає Міжнародний стандартний номер книги">ISBN</abbr> або з `imdb-` для ID URL фільму на <abbr title="IMDB (Internet Movie Database) is a website with information about movies IMDB (Internet Movie Database) це вебсайт з інформацією про фільми">IMDB</abbr>:
Наприклад, цей кастомний валідатор перевіряє, чи починається ID елемента з `isbn-` для номера книги <abbr title="ISBN означає Міжнародний стандартний номер книги">ISBN</abbr> або з `imdb-` для ID URL фільму на <abbr title="IMDB (Internet Movie Database) це вебсайт з інформацією про фільми">IMDB</abbr>:
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *}
@@ -412,49 +429,49 @@ Pydantic також має <a href="https://docs.pydantic.dev/latest/concepts/va
///
/// tip | Порада
/// tip | Підказка
Якщо вам потрібно виконати будь-яку валідацію, яка вимагає взаємодії з будь-яким **зовнішнім компонентом**, таким як база даних чи інший API, замість цього слід використовувати **FastAPI Dependencies** — ви дізнаєтесь про них пізніше.
Якщо Вам потрібно виконати будь-яку валідацію, яка вимагає взаємодії з будь-яким **зовнішнім компонентом**, таким як база даних чи інший API, ви повинні замість цього використовувати **FastAPI Dependencies**. Ви дізнаєтесь про них пізніше.
Ці кастомні валідатори використовуються для речей, які можна перевірити лише з **тіими самими даними**, що надані в запиті.
Ці кастомні валідатори використовуються для речей, які можна перевірити лише з **тими даними**, що надані в запиті.
///
### Зрозумійте цей код { #understand-that-code }
### Зрозумійте цей код
Головний момент це використання **`AfterValidator` з функцією всередині `Annotated`**. Можете пропустити цю частину, якщо хочете. 🤸
Головний момент це використання **`AfterValidator` з функцією всередині `Annotated`**. Можете пропустити цю частину, якщо хочете. 🤸
---
Але якщо вам цікаво розібратися в цьому конкретному прикладі коду і вам ще не набридло, ось кілька додаткових деталей.
Але якщо Вам цікаво розібратися в цьому конкретному прикладі коду і Вам ще не набридло, ось кілька додаткових деталей.
#### Рядок із `value.startswith()` { #string-with-value-startswith }
#### Рядок із `value.startswith()`
Звернули увагу? Рядок із `value.startswith()` може приймати кортеж, і тоді він перевірятиме кожне значення в кортежі:
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[16:19] hl[17] *}
#### Випадковий елемент { #a-random-item }
#### Випадковий елемент
За допомогою `data.items()` ми отримуємо <abbr title="Something we can iterate on with a for loop, like a list, set, etc. Об'єкт, який можна перебирати в циклі, як-от список чи множину.">iterable object</abbr> із кортежами, що містять ключ і значення для кожного елемента словника.
За допомогою `data.items()` ми отримуємо <abbr title="Об'єкт, який можна перебирати в циклі, як-от список чи множину.">ітерабельний об'єкт</abbr> із кортежами, що містять ключ і значення для кожного елемента словника.
Ми перетворюємо цей iterable object у звичайний `list` за допомогою `list(data.items())`.
Ми перетворюємо цей ітерабельний об'єкт у звичайний `list` за допомогою `list(data.items())`.
Потім, використовуючи `random.choice()`, ми можемо отримати **випадкове значення** зі списку, тобто отримуємо кортеж із `(id, name)`. Це може бути щось на зразок `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`.
Потім, використовуючи `random.choice()`, ми можемо отримати випадкове значення зі списку, тобто отримуємо кортеж із `(id, name)`. Це може бути щось на зразок `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`.
Далі ми **присвоюємо ці два значення** кортежу змінним `id` і `name`.
Тож, якщо користувач не вказав ID елемента, він все одно отримає випадкову рекомендацію.
...ми робимо все це в **одному простому рядку**. 🤯 Хіба ви не любите Python? 🐍
...і все це реалізовано в **одному рядку коду**. 🤯 Хіба не прекрасний Python? 🐍
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *}
## Підсумок { #recap }
## Підсумок
Ви можете оголошувати додаткові валідації та метадані для ваших параметрів.
Ви можете оголошувати додаткові валідації та метаінформацію для своїх параметрів.
Загальні валідації та метадані:
Загальні валідації та метаінформація:
* `alias`
* `title`
@@ -469,6 +486,6 @@ Pydantic також має <a href="https://docs.pydantic.dev/latest/concepts/va
Кастомні валідації за допомогою `AfterValidator`.
У цих прикладах ви побачили, як оголошувати валідації для значень `str`.
У цих прикладах Ви побачили, як оголошувати валідації для значень `str`.
Дивіться наступні розділи, щоб дізнатися, як оголошувати валідації для інших типів, наприклад чисел.

View File

@@ -1,10 +1,10 @@
# Query параметри { #query-parameters }
# Query Параметри
Коли ви оголошуєте інші параметри функції, які не є частиною параметрів шляху, вони автоматично інтерпретуються як параметри «query».
Коли Ви оголошуєте інші параметри функції, які не є частиною параметрів шляху, вони автоматично інтерпретуються як "query" параметри.
{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *}
{* ../../docs_src/query_params/tutorial001.py hl[9] *}
Query — це набір пар ключ-значення, що йдуть після символу `?` в URL, розділені символами `&`.
Query параметри — це набір пар ключ-значення, що йдуть після символу `?` в URL, розділені символами `&`.
Наприклад, в URL:
@@ -12,41 +12,41 @@ Query — це набір пар ключ-значення, що йдуть пі
http://127.0.0.1:8000/items/?skip=0&limit=10
```
...параметрами query є:
...query параметрами є:
* `skip`: зі значенням `0`
* `limit`: зі значенням `10`
Оскільки вони є частиною URL, вони «природно» є рядками.
Оскільки вони є частиною URL, вони "за замовчуванням" є рядками.
Але коли ви оголошуєте їх із типами Python (у наведеному прикладі як `int`), вони перетворюються на цей тип і проходять перевірку відповідності.
Але коли Ви оголошуєте їх із типами Python (у наведеному прикладі як `int`), вони перетворюються на цей тип і проходять перевірку відповідності.
Увесь той самий процес, який застосовується до параметрів шляху, також застосовується до параметрів query:
Увесь той самий процес, який застосовується до параметрів шляху, також застосовується до query параметрів:
* Підтримка в редакторі (очевидно)
* <abbr title="converting the string that comes from an HTTP request into Python data перетворення рядка, що надходить з HTTP-запиту, у дані Python">«parsing»</abbr> даних
* Підтримка в редакторі (автодоповнення, перевірка помилок)
* <abbr title="перетворення рядка, що надходить з HTTP-запиту, у типи даних Python">"Парсинг"</abbr> даних
* Валідація даних
* Автоматична документація
## Значення за замовчуванням { #defaults }
Оскільки параметри query не є фіксованою частиною шляху, вони можуть бути необов’язковими та мати значення за замовчуванням.
## Значення за замовчуванням
Оскільки query параметри не є фіксованою частиною шляху, вони можуть бути необов’язковими та мати значення за замовчуванням.
У наведеному вище прикладі вони мають значення за замовчуванням: `skip=0` і `limit=10`.
Отже, перехід за URL:
Отже, результат переходу за URL:
```
http://127.0.0.1:8000/items/
```
буде таким самим, як і перехід за посиланням:
```
http://127.0.0.1:8000/items/?skip=0&limit=10
```
Але якщо ви перейдете, наприклад, за посиланням:
Але якщо Ви перейдете, наприклад, за посиланням:
```
http://127.0.0.1:8000/items/?skip=20
@@ -54,12 +54,12 @@ http://127.0.0.1:8000/items/?skip=20
Значення параметрів у вашій функції будуть такими:
* `skip=20`: оскільки ви вказали його в URL
* `skip=20`: оскільки Ви вказали його в URL
* `limit=10`: оскільки це значення за замовчуванням
## Необов'язкові параметри { #optional-parameters }
## Необов'язкові параметри
Так само ви можете оголосити необов’язкові параметри query, встановивши для них значення за замовчуванням `None`:
Аналогічно, Ви можете оголосити необов’язкові query параметри, встановивши для них значення за замовчуванням `None`:
{* ../../docs_src/query_params/tutorial002_py310.py hl[7] *}
@@ -67,17 +67,18 @@ http://127.0.0.1:8000/items/?skip=20
/// check | Примітка
Також зверніть увагу, що **FastAPI** достатньо розумний, щоб визначити, що параметр шляху `item_id` є параметром шляху, а `q` — ні, отже, це параметр query.
Також зверніть увагу, що **FastAPI** достатньо розумний, щоб визначити, що параметр шляху `item_id` є параметром шляху, а `q` — ні, отже, це query параметр.
///
## Перетворення типу параметра query { #query-parameter-type-conversion }
## Перетворення типу Query параметра
Ви також можете оголошувати параметри типу `bool`, і вони будуть автоматично конвертовані:
{* ../../docs_src/query_params/tutorial003_py310.py hl[7] *}
У цьому випадку, якщо ви перейдете за:
У цьому випадку, якщо Ви звернетесь до:
```
http://127.0.0.1:8000/items/foo?short=1
@@ -107,38 +108,38 @@ http://127.0.0.1:8000/items/foo?short=on
http://127.0.0.1:8000/items/foo?short=yes
```
або будь-який інший варіант написання (великі літери, перша літера велика тощо), ваша функція побачить параметр `short` зі значенням `True` типу `bool`. В іншому випадку `False`.
або будь-який інший варіант написання (великі літери, перша літера велика тощо), ваша функція побачить параметр `short` зі значенням `True` з типом даних `bool`. В іншому випадку `False`.
## Кілька path і query параметрів
Ви можете одночасно оголошувати кілька path і query параметрів, і **FastAPI** автоматично визначить, який з них до чого належить.
## Кілька path і query параметрів { #multiple-path-and-query-parameters }
Ви можете одночасно оголошувати кілька параметрів шляху та параметрів query, **FastAPI** знає, який з них який.
І вам не потрібно оголошувати їх у якомусь конкретному порядку.
Не потрібно дотримуватись певного порядку їх оголошення.
Вони визначаються за назвою:
{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *}
## Обов’язкові параметри query { #required-query-parameters }
## Обов’язкові Query параметри
Коли ви оголошуєте значення за замовчуванням для не-path-параметрів (поки що ми бачили лише параметри query), тоді вони не є обов’язковими.
Якщо Ви оголошуєте значення за замовчуванням для параметрів, які не є path-параметрами (у цьому розділі ми бачили поки що лише path параметри), тоді вони стають необов’язковими.
Якщо ви не хочете задавати конкретне значення, а просто зробити параметр необов’язковим, задайте `None` як значення за замовчуванням.
Якщо Ви не хочете вказувати конкретні значення, але хочете зробити параметр опціональним, задайте `None` як значення за замовчуванням.
Але якщо ви хочете зробити параметр query обов’язковим, просто не вказуйте для нього значення за замовчуванням:
Але якщо Ви хочете зробити query параметр обов’язковим, просто не вказуйте для нього значення за замовчуванням:
{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *}
{* ../../docs_src/query_params/tutorial005.py hl[6:7] *}
Тут параметр query `needy` обов’язковий параметр query типу `str`.
Тут `needy` обов’язковий query параметр типу `str`.
Якщо ви відкриєте у браузері URL-адресу:
Якщо Ви відкриєте у браузері URL-адресу:
```
http://127.0.0.1:8000/items/foo-item
```
...без додавання обов’язкового параметра `needy`, ви побачите помилку на кшталт:
...без додавання обов’язкового параметра `needy`, Ви побачите помилку:
```JSON
{
@@ -162,7 +163,7 @@ http://127.0.0.1:8000/items/foo-item
http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
```
...це спрацює:
...цей запит поверне:
```JSON
{
@@ -171,18 +172,20 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
}
```
І звісно, ви можете визначити деякі параметри як обов’язкові, деякі — зі значенням за замовчуванням, а деякі — повністю необов’язкові:
Звичайно, Ви можете визначити деякі параметри як обов’язкові, інші зі значенням за замовчуванням, а ще деякі — повністю опціональні:
{* ../../docs_src/query_params/tutorial006_py310.py hl[8] *}
У цьому випадку є 3 параметри query:
У цьому випадку є 3 query параметри:
* `needy`, обов’язковий `str`.
* `skip`, `int` зі значенням за замовчуванням `0`.
* `limit`, необов’язковий `int`.
* `limit`, опціональний `int`.
/// tip | Порада
Ви також можете використовувати `Enum` так само, як і з [Path Parameters](path-params.md#predefined-values){.internal-link target=_blank}.
/// tip | Підказка
Ви також можете використовувати `Enum`-и, так само як і з [Path Parameters](path-params.md#predefined-values){.internal-link target=_blank}.
///

View File

@@ -1,56 +1,56 @@
# Запит файлів { #request-files }
# Запит файлів
Ви можете визначити файли, які будуть завантажуватися клієнтом, використовуючи `File`.
/// info | Інформація
Щоб отримувати завантажені файли, спочатку встановіть <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>.
Щоб отримувати завантажені файли, спочатку встановіть <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">python-multipart</a>.
Переконайтеся, що ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили пакет, наприклад:
Переконайтеся, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його та встановили пакет, наприклад:
```console
$ pip install python-multipart
```
Це необхідно, оскільки завантажені файли передаються у вигляді «form data».
Це необхідно, оскільки завантажені файли передаються у вигляді "форматованих даних форми".
///
## Імпорт `File` { #import-file }
## Імпорт `File`
Імпортуйте `File` та `UploadFile` з `fastapi`:
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *}
## Визначення параметрів `File` { #define-file-parameters }
## Визначення параметрів `File`
Створіть параметри файлів так само як ви б створювали `Body` або `Form`:
Створіть параметри файлів так само як Ви б створювали `Body` або `Form`:
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *}
/// info | Інформація
`File` — це клас, який безпосередньо успадковує `Form`.
`File` — це клас, який безпосередньо успадковує `Form`.
Але пам’ятайте, що коли ви імпортуєте `Query`, `Path`, `File` та інші з `fastapi`, це насправді функції, які повертають спеціальні класи.
Але пам’ятайте, що коли Ви імпортуєте `Query`, `Path`, `File` та інші з `fastapi`, це насправді функції, які повертають спеціальні класи.
///
/// tip | Порада
/// tip | Підказка
Щоб оголосити тіла файлів, вам потрібно використовувати `File`, тому що інакше параметри будуть інтерпретовані як параметри запиту або параметри тіла (JSON).
Щоб оголосити тіла файлів, Вам потрібно використовувати `File`, тому що інакше параметри будуть інтерпретовані як параметри запиту або параметри тіла (JSON).
///
Файли будуть завантажені у вигляді «form data».
Файли будуть завантажені у вигляді "форматованих даних форми".
Якщо ви оголосите тип параметра *функції операції шляху* як `bytes`, **FastAPI** прочитає файл за вас, і ви отримаєте його вміст у вигляді `bytes`.
Якщо Ви оголосите тип параметра функції обробника маршруту як `bytes`, **FastAPI** прочитає файл за Вас, і Ви отримаєте його вміст у вигляді `bytes`.
Майте на увазі, що це означає, що весь вміст буде збережено в пам'яті. Це працюватиме добре для малих файлів.
Однак майте на увазі, що весь вміст буде збережено в пам'яті. Це працюватиме добре для малих файлів.
Але є кілька випадків, у яких вам може бути корисно використовувати `UploadFile`.
Але в деяких випадках Вам може знадобитися `UploadFile`.
## Параметри файлу з `UploadFile` { #file-parameters-with-uploadfile }
## Параметри файлу з `UploadFile`
Визначте параметр файлу з типом `UploadFile`:
@@ -59,39 +59,38 @@ $ pip install python-multipart
Використання `UploadFile` має кілька переваг перед `bytes`:
* Вам не потрібно використовувати `File()` у значенні за замовчуванням параметра.
* Використовується «spooled» файл:
* Файл зберігається в пам'яті до досягнення максимального обмеження розміру, після чого він буде збережений на диску.
* Це означає, що він добре працюватиме для великих файлів, таких як зображення, відео, великі двійкові файли тощо, не споживаючи всю пам'ять.
* Ви можете отримати метадані про завантажений файл.
* Він має <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> `async` інтерфейс.
* Він надає фактичний об'єкт Python <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a>, який можна передавати безпосередньо іншим бібліотекам, що очікують file-like об'єкт.
* Використовується "буферизований" файл:
* Файл зберігається в пам'яті до досягнення певного обмеження, після чого він записується на диск.
* Це означає, що він добре працює для великих файлів, таких як зображення, відео, великі двійкові файли тощо, не споживаючи всю пам'ять.
Ви можете отримати метадані про завантажений файл.
* Він має <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> `асинхронний файловий інтерфейс` interface.
* Він надає фактичний об'єкт Python <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a>, який можна передавати безпосередньо іншим бібліотекам.
### `UploadFile` { #uploadfile }
### `UploadFile`
`UploadFile` має такі атрибути:
* `filename`: Рядок `str` з оригінальною назвою файлу, який був завантажений (наприклад, `myimage.jpg`).
* `content_type`: Рядок `str` з типом вмісту (MIME type / media type) (наприклад, `image/jpeg`).
* `file`: <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> (<a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> об'єкт). Це фактичний файловий об'єкт Python, який ви можете передавати безпосередньо іншим функціям або бібліотекам, що очікують «file-like» об'єкт.
* `content_type`: Рядок `str` з MIME-типом (наприклад, `image/jpeg`).
* `file`: Об'єкт <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">SpooledTemporaryFile</a> (<a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">файлоподібний</a> об'єкт). Це фактичний файловий об'єкт Python, який можна безпосередньо передавати іншим функціям або бібліотекам, що очікують "файлоподібний" об'єкт.
`UploadFile` має такі асинхронні `async` методи. Вони всі викликають відповідні методи файлу під капотом (використовуючи внутрішній `SpooledTemporaryFile`).
`UploadFile` має такі асинхронні `async` методи. Вони викликають відповідні методи файлу під капотом (використовуючи внутрішній `SpooledTemporaryFile`).
* `write(data)`: Записує `data` (`str` або `bytes`) у файл.
* `read(size)`: Читає `size` (`int`) байтів/символів з файлу.
* `seek(offset)`: Переходить до байтової позиції `offset` (`int`) у файлі.
* Наприклад, `await myfile.seek(0)` перейде на початок файлу.
* Це особливо корисно, якщо ви виконаєте `await myfile.read()` один раз, а потім потрібно знову прочитати вміст.
* `seek(offset)`: Переміщується до позиції `offset` (`int`) у файлі.
* Наприклад, `await myfile.seek(0)` поверне курсор на початок файлу.
* This is especially useful if you run `await myfile.read()` once and then need to read the contents again. Це особливо корисно, якщо Ви виконуєте await `await myfile.read()` один раз, а потім потрібно знову прочитати вміст.
* `close()`: Закриває файл.
Оскільки всі ці методи є асинхронними `async` методами, вам потрібно їх «await»-ити.
Оскільки всі ці методи є асинхронними `async`, Вам потрібно використовувати "await":
Наприклад, всередині `async` *функції операції шляху* ви можете отримати вміст за допомогою:
Наприклад, всередині `async` *функції обробки шляху* Ви можете отримати вміст за допомогою:
```Python
contents = await myfile.read()
```
Якщо ви знаходитесь у звичайній `def` *функції операції шляху*, ви можете отримати доступ до `UploadFile.file` безпосередньо, наприклад:
Якщо Ви знаходитесь у звичайній `def` *функції обробки шляху*, Ви можете отримати доступ до `UploadFile.file` безпосередньо, наприклад:
```Python
contents = myfile.file.read()
@@ -99,57 +98,57 @@ contents = myfile.file.read()
/// note | Технічні деталі `async`
Коли ви використовуєте `async` методи, **FastAPI** виконує файлові методи у пулі потоків і очікує на них.
Коли Ви використовуєте `async` методи, **FastAPI** виконує файлові операції у пулі потоків та очікує їх завершення.
///
/// note | Технічні деталі Starlette
`UploadFile` у **FastAPI** успадковується безпосередньо від `UploadFile` у **Starlette**, але додає деякі необхідні частини, щоб зробити його сумісним із **Pydantic** та іншими частинами FastAPI.
`UploadFile` у **FastAPI** успадковується безпосередньо від `UploadFile` у **Starlette**, але додає деякі необхідні частини, щоб зробити його сумісним із **Pydantic** та іншими компонентами FastAPI.
///
## Що таке «Form Data» { #what-is-form-data }
## Що таке "Form Data"
Спосіб, у який HTML-форми (`<form></form>`) надсилають дані на сервер, зазвичай використовує «спеціальне» кодування для цих даних, відмінне від JSON.
Спосіб, у який HTML-форми (`<form></form>`) надсилають дані на сервер, зазвичай використовує "спеціальне" кодування, відмінне від JSON.
**FastAPI** забезпечить зчитування цих даних з правильного місця, а не з JSON.
**FastAPI** забезпечує правильне зчитування цих даних з відповідної частини запиту, а не з JSON.
/// note | Технічні деталі
Дані з форм зазвичай кодуються за допомогою «media type» `application/x-www-form-urlencoded`, якщо вони не містять файлів.
Дані з форм зазвичай кодуються за допомогою "media type" `application/x-www-form-urlencoded`, якщо вони не містять файлів.
Але якщо форма містить файли, вона кодується як `multipart/form-data`. Якщо ви використовуєте `File`, **FastAPI** знатиме, що потрібно отримати файли з правильної частини тіла.
Але якщо форма містить файли, вона кодується у форматі `multipart/form-data`. Якщо Ви використовуєте `File`, **FastAPI** визначить, що потрібно отримати файли з відповідної частини тіла запиту.
Якщо ви хочете дізнатися більше про ці типи кодування та формові поля, ознайомтеся з <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> web docs для <code>POST</code></a>.
Щоб дізнатися більше про ці типи кодування та формові поля, ознайомтеся з <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">документацією MDN</abbr> щодо <code>POST</code></a>.
///
/// warning | Попередження
/// warning | Увага
Ви можете оголосити кілька параметрів `File` і `Form` в *операції шляху*, але ви не можете одночасно оголошувати поля `Body`, які ви очікуєте отримати як JSON, оскільки запит матиме тіло, закодоване як `multipart/form-data`, а не `application/json`.
Ви можете оголосити кілька параметрів `File` і `Form` в *операції шляху*, але Ви не можете одночасно оголошувати поля `Body`, які мають надходити у форматі JSON, оскільки тіло запиту буде закодоване у форматі `multipart/form-data`, а не `application/json`.
Це не обмеження **FastAPI**, а частина протоколу HTTP.
Це не обмеження **FastAPI**, а особливість протоколу HTTP.
///
## Необов’язкове завантаження файлу { #optional-file-upload }
## Опціональне Завантаження Файлів
Ви можете зробити файл необов’язковим, використовуючи стандартні анотації типів і встановивши значення за замовчуванням `None`:
Файл можна зробити необов’язковим, використовуючи стандартні анотації типів і встановлюючи значення за замовчуванням `None`:
{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *}
## `UploadFile` із додатковими метаданими { #uploadfile-with-additional-metadata }
## `UploadFile` із Додатковими Мета Даними
Ви також можете використовувати `File()` разом із `UploadFile`, наприклад, щоб встановити додаткові метадані:
Ви також можете використовувати `File()` разом із `UploadFile`, наприклад, для встановлення додаткових метаданих:
{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *}
## Завантаження кількох файлів { #multiple-file-uploads }
## Завантаження Кількох Файлів
Можна завантажувати кілька файлів одночасно.
Вони будуть пов’язані з одним і тим самим «form field», який передається у вигляді «form data».
Вони будуть пов’язані з одним і тим самим "form field", який передається у вигляді "form data".
Щоб це реалізувати, потрібно оголосити список `bytes` або `UploadFile`:
@@ -161,16 +160,16 @@ contents = myfile.file.read()
Ви також можете використати `from starlette.responses import HTMLResponse`.
**FastAPI** надає ті ж самі `starlette.responses`, що й `fastapi.responses`, просто для зручності для вас, розробника. Але більшість доступних відповідей надходять безпосередньо від Starlette.
**FastAPI** надає ті ж самі `starlette.responses`, що й `fastapi.responses`, для зручності розробників. Однак більшість доступних відповідей надходять безпосередньо від Starlette.
///
### Завантаження кількох файлів із додатковими метаданими { #multiple-file-uploads-with-additional-metadata }
### Завантаження декількох файлів із додатковими метаданими
Так само як і раніше, ви можете використовувати `File()`, щоб встановити додаткові параметри навіть для `UploadFile`:
Так само як і раніше, Ви можете використовувати `File()`, щоб встановити додаткові параметри навіть для `UploadFile`:
{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *}
## Підсумок { #recap }
## Підсумок
Використовуйте `File`, `bytes` та `UploadFile`, щоб оголошувати файли для завантаження в запиті, надіслані у вигляді form data.
Використовуйте `File`, `bytes`та `UploadFile`, щоб оголошувати файли для завантаження у запитах, які надсилаються у вигляді form data.

View File

@@ -1,12 +1,12 @@
# Моделі форм { #form-models }
# Моделі форм (Form Models)
У FastAPI ви можете використовувати **Pydantic-моделі** для оголошення **полів форми**.
У FastAPI Ви можете використовувати **Pydantic-моделі** для оголошення **полів форми**.
/// info | Інформація
Щоб використовувати форми, спочатку встановіть <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>.
Щоб використовувати форми, спочатку встановіть <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">python-multipart</a>.
Переконайтеся, що ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили його, наприклад:
Переконайтеся, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили бібліотеку, наприклад:
```console
$ pip install python-multipart
@@ -14,21 +14,21 @@ $ pip install python-multipart
///
/// note | Примітка
/// note | Підказка
Це підтримується, починаючи з FastAPI версії `0.113.0`. 🤓
Ця функція підтримується, починаючи з FastAPI версії `0.113.0`. 🤓
///
## Pydantic-моделі для форм { #pydantic-models-for-forms }
## Використання Pydantic-моделей для форм
Вам просто потрібно оголосити **Pydantic-модель** з полями, які ви хочете отримати як **поля форми**, а потім оголосити параметр як `Form`:
Вам просто потрібно оголосити **Pydantic-модель** з полями, які Ви хочете отримати як **поля форми**, а потім оголосити параметр як `Form`:
{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *}
**FastAPI** **витягне** дані для **кожного поля** з **формових даних** у запиті та надасть вам Pydantic-модель, яку ви визначили.
**FastAPI** **витягне** дані для **кожного поля** з **формових даних** у запиті та надасть вам Pydantic-модель, яку Ви визначили.
## Перевірте документацію { #check-the-docs }
## Перевірка документації
Ви можете перевірити це в UI документації за `/docs`:
@@ -36,13 +36,13 @@ $ pip install python-multipart
<img src="/img/tutorial/request-form-models/image01.png">
</div>
## Забороніть додаткові поля форми { #forbid-extra-form-fields }
## Заборона додаткових полів форми
У деяких особливих випадках (ймовірно, не дуже поширених) ви можете **обмежити** поля форми лише тими, які були оголошені в Pydantic-моделі. І **заборонити** будь-які **додаткові** поля.
У деяких особливих випадках (ймовірно, рідко) Ви можете **обмежити** форму лише тими полями, які були оголошені в Pydantic-моделі, і **заборонити** будь-які **додаткові** поля.
/// note | Примітка
/// note | Підказка
Це підтримується, починаючи з FastAPI версії `0.114.0`. 🤓
Ця функція підтримується, починаючи з FastAPI версії `0.114.0`. 🤓
///
@@ -52,7 +52,7 @@ $ pip install python-multipart
Якщо клієнт спробує надіслати додаткові дані, він отримає **відповідь з помилкою**.
Наприклад, якщо клієнт спробує надіслати поля форми:
Наприклад, якщо клієнт спробує надіслати наступні поля форми:
* `username`: `Rick`
* `password`: `Portal Gun`
@@ -73,6 +73,6 @@ $ pip install python-multipart
}
```
## Підсумок { #summary }
## Підсумок
У FastAPI ви можете використовувати Pydantic-моделі для оголошення полів форми. 😎
Ви можете використовувати Pydantic-моделі для оголошення полів форми у FastAPI. 😎

View File

@@ -1,10 +1,10 @@
# Запити з формами та файлами { #request-forms-and-files }
# Запити з формами та файлами
Ви можете одночасно визначати файли та поля форми, використовуючи `File` і `Form`.
У FastAPI Ви можете одночасно отримувати файли та поля форми, використовуючи `File` і `Form`.
/// info | Інформація
Щоб отримувати завантажені файли та/або дані форми, спочатку встановіть <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>.
Щоб отримувати завантажені файли та/або дані форми, спочатку встановіть <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">python-multipart</a>.
Переконайтеся, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили бібліотеку, наприклад:
@@ -14,21 +14,21 @@ $ pip install python-multipart
///
## Імпорт `File` та `Form` { #import-file-and-form }
## Імпорт `File` та `Form`
{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *}
## Оголошення параметрів `File` та `Form` { #define-file-and-form-parameters }
## Оголошення параметрів `File` та `Form`
Створіть параметри файлів та форми так само як і для `Body` або `Query`:
{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *}
Файли та поля форми будуть завантажені як формові дані, і Ви отримаєте файли та поля форми.
Файли та поля форми будуть завантажені як формові дані, і Ви отримаєте як файли, так і введені користувачем поля.
Ви також можете оголосити деякі файли як `bytes`, а деякі як `UploadFile`.
/// warning | Попередження
/// warning | Увага
Ви можете оголосити кілька параметрів `File` і `Form` в операції *шляху*, але не можете одночасно оголошувати `Body`-поля, які очікуєте отримати у форматі JSON, оскільки запит матиме тіло, закодоване за допомогою `multipart/form-data`, а не `application/json`.
@@ -36,6 +36,6 @@ $ pip install python-multipart
///
## Підсумок { #recap }
## Підсумок
Використовуйте `File` та `Form` разом, коли вам потрібно отримувати дані та файли в одному запиті.
Використовуйте `File` та `Form` разом, коли вам потрібно отримувати дані форми та файли в одному запиті.

View File

@@ -1,12 +1,12 @@
# Дані форми { #form-data }
# Дані форми
Якщо вам потрібно отримувати поля форми замість JSON, ви можете використовувати `Form`.
Якщо Вам потрібно отримувати поля форми замість JSON, Ви можете використовувати `Form`.
/// info | Інформація
Щоб використовувати форми, спочатку встановіть <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>.
Переконайтеся, що ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, і потім встановили бібліотеку, наприклад:
Переконайтеся, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, і потім встановили бібліотеку, наприклад:
```console
$ pip install python-multipart
@@ -14,23 +14,23 @@ $ pip install python-multipart
///
## Імпорт `Form` { #import-form }
## Імпорт `Form`
Імпортуйте `Form` з `fastapi`:
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *}
## Оголошення параметрів `Form` { #define-form-parameters }
## Оголошення параметрів `Form`
Створюйте параметри форми так само як ви б створювали `Body` або `Query`:
Створюйте параметри форми так само як Ви б створювали `Body` або `Query`:
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *}
Наприклад, один зі способів використання специфікації OAuth2 (так званий "password flow") вимагає надсилати `username` та `password` як поля форми.
<abbr title="specification">spec</abbr> вимагає, щоб ці поля мали точні назви `username` і `password` та надсилалися у вигляді полів форми, а не JSON.
<abbr title="Специфікація">spec</abbr> вимагає, щоб ці поля мали точні назви `username` і `password` та надсилалися у вигляді полів форми, а не JSON.
З `Form` ви можете оголошувати ті ж конфігурації, що і з `Body` (та `Query`, `Path`, `Cookie`), включаючи валідацію, приклади, псевдоніми (наприклад, `user-name` замість `username`) тощо.
З `Form` Ви можете оголошувати ті ж конфігурації, що і з `Body` (та `Query`, `Path`, `Cookie`), включаючи валідацію, приклади, псевдоніми (наприклад, `user-name` замість `username`) тощо.
/// info | Інформація
@@ -44,7 +44,7 @@ $ pip install python-multipart
///
## Про "поля форми" { #about-form-fields }
## Про "поля форми"
HTML-форми (`<form></form>`) надсилають дані на сервер у "спеціальному" кодуванні, яке відрізняється від JSON.
@@ -56,18 +56,18 @@ HTML-форми (`<form></form>`) надсилають дані на серве
Але якщо форма містить файли, вона кодується як `multipart/form-data`. Ви дізнаєтеся про обробку файлів у наступному розділі.
Якщо ви хочете дізнатися більше про ці кодування та поля форм, зверніться до <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> вебдокументації для <code>POST</code></a>.
Якщо Ви хочете дізнатися більше про ці кодування та поля форм, зверніться до <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> вебдокументації для <code>POST</code></a>.
///
/// warning | Попередження
Ви можете оголосити кілька параметрів `Form` в *операції шляху*, але не можете одночасно оголосити поля `Body`, які ви очікуєте отримати у форматі JSON, оскільки запит матиме тіло, закодоване як `application/x-www-form-urlencoded`, а не `application/json`.
Ви можете оголосити кілька параметрів `Form` в *операції шляху*, але не можете одночасно оголосити поля `Body`, які Ви очікуєте отримати у форматі JSON, оскільки тіло запиту буде закодовано у форматі `application/x-www-form-urlencoded`, а не `application/json`.
Це не обмеження **FastAPI**, а частина HTTP-протоколу.
///
## Підсумок { #recap }
## Підсумок
Використовуйте `Form` для оголошення вхідних параметрів у вигляді даних форми.

View File

@@ -1,35 +1,36 @@
# Модель відповіді — Тип, що повертається { #response-model-return-type }
# Модель відповіді — Тип, що повертається
Ви можете оголосити тип, який використовуватиметься у відповіді, анотувавши **тип повернення** *функції операції шляху*.
Ви можете оголосити тип, який використовуватиметься у відповіді, за допомогою *анотації типу, що повертається* *функцією операцією шляху* (path operation)
**Анотації типів** можна використовувати так само, як і для вхідних даних у **параметрах** функції: можна використовувати моделі Pydantic, списки, словники, скалярні значення, як-от цілі числа, булеві значення тощо.
**Анотацію типу** можна вказати так само як і для вхідних **параметрів** функції: це можуть бути моделі Pydantic, списки (lists), словники (dictionaries), скалярні значення, як-от цілі числа (integers), булеві значення (booleans) тощо.
{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *}
FastAPI використовуватиме цей тип повернення, щоб:
FastAPI використовуватиме цей тип, щоб:
* **Перевірити правильність** повернених даних.
* Якщо дані не валідні (наприклад, відсутнє поле), це означає, що *ваш* код застосунку зламаний, не повертає те, що повинен, і буде повернуто помилку сервера замість некоректних даних. Так ви та ваші клієнти можете бути впевнені, що отримаєте дані й очікувану структуру даних.
* Додати **JSON Schema** для відповіді в OpenAPI *операції шляху*.
* Якщо дані не валідні (наприклад, відсутнє поле), це означає, що Ваш код додатку працює некоректно і не повертає те, що повинен. У такому випадку FastAPI поверне помилку сервера, замість того щоб віддати недопустимі дані. Так Ви та Ваші клієнти будете впевнені, що отримуєте очікувані дані у правильному форматі.
* Додати **JSON Schema** відповіді до специфікації OpenAPI в *операціях шляху*.
* Це буде використано в **автоматичній документації**.
* Це також буде використано інструментами, які автоматично генерують клієнтський код.
* А також інструментами, які автоматично генерують клієнтський код.
Але найголовніше:
* Це **обмежить та відфільтрує** вихідні дані до того, що визначено в типі повернення.
* Це особливо важливо для **безпеки**, нижче ми побачимо про це більше.
* FastAPI **обмежить та відфільтрує** вихідні дані відповідно до типу, вказаного у відповіді.
* Це особливо важливо для **безпеки**. Деталі нижче.
## Параметр `response_model` { #response-model-parameter }
## Параметр `response_model`
Є випадки, коли вам потрібно або ви хочете повертати дані, які не зовсім відповідають тому, що оголошено типом.
Іноді Вам потрібно або зручно повертати інші типи даних, ніж ті, що зазначені як тип відповіді.
Наприклад, ви можете захотіти **повертати словник** або об’єкт бази даних, але **оголосити його як модель Pydantic**. Таким чином модель Pydantic виконуватиме всю документацію даних, валідацію тощо для об’єкта, який ви повернули (наприклад, словника або об’єкта бази даних).
Наприклад, Ви можете **повертати словник** або об’єкт бази даних, але **оголосити модель Pydantic** як модель відповіді. Тоді модель Pydantic автоматично оброблятиме валідацію, документацію тощо.
Якщо ви додали анотацію типу повернення, інструменти та редактори скаржитимуться (коректною) помилкою, повідомляючи, що ваша функція повертає тип (наприклад, dict), який відрізняється від того, що ви оголосили (наприклад, модель Pydantic).
Якщо Ви додасте анотацію типу для повернення, редактор коду або mypy можуть поскаржитися, що функція повертає інший тип (наприклад, dict замість Item).
У таких випадках можна скористатися параметром *декоратора операції шляху* `response_model` замість типу повернення.
У таких випадках можна скористатися параметром `response_model` в декораторі маршруту (наприклад, @app.get()).
Ви можете використовувати параметр `response_model` у будь-якій з *операцій шляху*:
Параметр `response_model` працює з будь-яким *оператором шляху*:
* `@app.get()`
* `@app.post()`
@@ -41,33 +42,33 @@ FastAPI використовуватиме цей тип повернення,
/// note | Примітка
Зверніть увагу, що `response_model` є параметром методу «декоратора» (`get`, `post` тощо). А не вашої *функції операції шляху*, як усі параметри та тіло.
Зверніть увагу, що `response_model` є параметром методу-декоратора (`get`, `post`, тощо), а не *функцією операцією шляху* (path operation function), як це робиться з параметрами або тілом запиту.
///
`response_model` приймає такий самий тип, який ви б вказали для поля моделі Pydantic, тобто це може бути модель Pydantic, але також це може бути, наприклад, `list` моделей Pydantic, як-от `List[Item]`.
`response_model` приймає такий самий тип, який Ви б вказали для поля моделі Pydantic. Тобто це може бути як Pydantic-модель, так і, наприклад, `list` із моделей Pydantic `List[Item]`.
FastAPI використовуватиме цей `response_model` для виконання всієї документації даних, валідації тощо, а також для **перетворення та фільтрації вихідних даних** до оголошеного типу.
FastAPI використовуватиме `response_model` для створення документації, валідації даних та — найважливіше — **перетворення та фільтрації вихідних даних** згідно з оголошеним типом.
/// tip | Порада
Якщо у вас увімкнено сувору перевірку типів у редакторі, mypy тощо, ви можете оголосити тип повернення функції як `Any`.
Якщо у Вас увімкнено сувору перевірку типів у редакторі, mypy тощо, Ви можете оголосити тип повернення функції як `Any`.
Таким чином, ви повідомляєте редактору, що свідомо повертаєте будь-що. Але FastAPI усе одно виконуватиме документацію даних, валідацію, фільтрацію тощо за допомогою `response_model`.
Таким чином, Ви повідомляєте редактору, що свідомо повертаєте будь-що. Але FastAPI усе одно виконуватиме створення документації, валідацію, фільтрацію тощо за допомогою параметра `response_model`.
///
### Пріоритет `response_model` { #response-model-priority }
### Пріоритет `response_model`
Якщо ви оголошуєте і тип повернення, і `response_model`, то `response_model` матиме пріоритет і буде використаний FastAPI.
Якщо Ви вказуєте і тип повернення, і `response_model`, то FastAPI використовуватиме `response_model` з пріоритетом.
Таким чином ви можете додати правильні анотації типів до ваших функцій, навіть коли повертаєте тип, відмінний від моделі відповіді, щоб це використовували редактор і інструменти на кшталт mypy. І при цьому FastAPI все одно виконуватиме валідацію даних, документацію тощо, використовуючи `response_model`.
Таким чином, Ви можете додати правильні анотації типів до ваших функцій, навіть якщо вони повертають тип, відмінний від `response_model`. Це буде корисно для редакторів коду та інструментів, таких як mypy. І при цьому FastAPI продовжить виконувати валідацію даних, генерувати документацію тощо на основі `response_model`.
Ви також можете використати `response_model=None`, щоб вимкнути створення моделі відповіді для цієї *операції шляху*; це може знадобитися, якщо ви додаєте анотації типів для речей, які не є валідними полями Pydantic, приклад цього ви побачите в одному з розділів нижче.
Ви також можете використати `response_model=None`, щоб вимкнути створення моделі відповіді для цієї *операції шляху*. Це може знадобитися, якщо Ви додаєте анотації типів до об'єктів, які не є допустимими полями Pydantic приклад цього Ви побачите в одному з наступних розділів.
## Повернути ті самі вхідні дані { #return-the-same-input-data }
## Повернути ті самі вхідні дані
Тут ми оголошуємо модель `UserIn`, вона міститиме пароль у відкритому вигляді:
Тут ми оголошуємо модель `UserIn`, яка містить звичайний текстовий пароль:
{* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *}
@@ -75,7 +76,7 @@ FastAPI використовуватиме цей `response_model` для вик
Щоб використовувати `EmailStr`, спочатку встановіть <a href="https://github.com/JoshData/python-email-validator" class="external-link" target="_blank">`email-validator`</a>.
Переконайтеся, що ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили пакет, наприклад:
Переконайтесь, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили пакет, наприклад:
```console
$ pip install email-validator
@@ -89,29 +90,29 @@ $ pip install "pydantic[email]"
///
І ми використовуємо цю модель, щоб оголосити наші вхідні дані, і цю ж модель, щоб оголосити наші вихідні дані:
І ми використовуємо цю модель, щоб оголосити і вхідні, і вихідні дані:
{* ../../docs_src/response_model/tutorial002_py310.py hl[16] *}
Тепер, щоразу коли браузер створює користувача з паролем, API поверне той самий пароль у відповіді.
Тепер, коли браузер створює користувача з паролем, API поверне той самий пароль у відповіді.
У цьому випадку це може не бути проблемою, адже це той самий користувач надсилає пароль.
У цьому випадку це може не бути проблемою, адже саме користувач надіслав пароль.
Але якщо ми використаємо цю ж модель для іншої *операції шляху*, ми можемо надсилати паролі наших користувачів кожному клієнту.
Але якщо ми використаємо цю ж модель для іншої операції шляху, ми можемо випадково надіслати паролі наших користувачів кожному клієнту.
/// danger | Обережно
Ніколи не зберігайте пароль користувача у відкритому вигляді та не надсилайте його у відповіді таким чином, якщо тільки ви не знаєте всіх застережень і точно розумієте, що робите.
Ніколи не зберігайте пароль користувача у відкритому вигляді та не надсилайте його у відповіді, якщо тільки Ви не знаєте всі ризики і точно розумієте, що робите.
///
## Додати вихідну модель { #add-an-output-model }
## Додайте окрему вихідну модель
Замість цього ми можемо створити вхідну модель з паролем у відкритому вигляді і вихідну модель без нього:
Замість цього ми можемо створити вхідну модель з відкритим паролем і вихідну модель без нього:
{* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *}
Тут, хоча наша *функція операції шляху* повертає того самого вхідного користувача, який містить пароль:
Тут, навіть якщо *функція операції шляху* повертає об'єкт користувача, який містить пароль:
{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *}
@@ -119,107 +120,107 @@ $ pip install "pydantic[email]"
{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *}
Таким чином, **FastAPI** подбає про фільтрацію всіх даних, які не оголошені у вихідній моделі (використовуючи Pydantic).
Таким чином, **FastAPI** автоматично відфільтрує всі дані, які не вказані у вихідній моделі (за допомогою Pydantic).
### `response_model` або тип повернення { #response-model-or-return-type }
### `response_model` або тип повернення
У цьому випадку, оскільки дві моделі різні, якщо ми анотуємо тип повернення функції як `UserOut`, редактор і інструменти скаржитимуться, що ми повертаємо невалідний тип, адже це різні класи.
У цьому випадку, оскільки дві моделі різні, якщо ми анотуємо тип повернення функції як `UserOut`, редактор і такі інструменти, як mypy, видадуть помилку, бо фактично ми повертаємо інший тип.
Саме тому в цьому прикладі нам треба оголосити це через параметр `response_model`.
Тому в цьому прикладі ми використовуємо параметр `response_model`, а не анотацію типу повернення.
...але читайте далі нижче, щоб побачити, як це обійти.
...але читайте далі, щоб дізнатися, як обійти це обмеження.
## Тип повернення і фільтрація даних { #return-type-and-data-filtering }
## Тип повернення і фільтрація даних
Продовжимо з попереднього прикладу. Ми хотіли **анотувати функцію одним типом**, але хотіли мати змогу повертати з функції те, що насправді містить **більше даних**.
Продовжимо з попереднього прикладу. Ми хотіли **анотувати функцію одним типом**, але при цьому повертати з неї більше даних.
Ми хочемо, щоб FastAPI продовжував **фільтрувати** дані, використовуючи модель відповіді. Тобто навіть якщо функція повертає більше даних, відповідь міститиме лише поля, оголошені в моделі відповіді.
Ми хочемо, щоб FastAPI продовжував **фільтрувати** ці дані за допомогою response_model. Тобто навіть якщо функція повертає більше інформації, у відповіді будуть лише ті поля, які вказані у response_model.
У попередньому прикладі, оскільки класи були різні, нам довелося використовувати параметр `response_model`. Але це також означає, що ми не отримуємо підтримки від редактора та інструментів, які перевіряють тип повернення функції.
У попередньому прикладі, оскільки класи були різні, нам довелося використовувати параметр `response_model`. Але це означає, що ми не отримуємо підтримки з боку редактора коду та інструментів перевірки типів щодо типу, який повертає функція.
Проте в більшості випадків, коли нам потрібно зробити щось подібне, ми просто хочемо, щоб модель **відфільтрувала/прибрала** частину даних, як у цьому прикладі.
Проте в більшості випадків, коли нам потрібно зробити щось подібне, ми просто хочемо, щоб модель **відфільтрувала або прибрала** частину даних, як у цьому прикладі.
І в таких випадках ми можемо використати класи та спадкування, щоб скористатися **анотаціями типів** функцій і отримати кращу підтримку в редакторі та інструментах, і при цьому зберегти **фільтрацію даних** у FastAPI.
У таких випадках ми можемо використати класи та спадкування, щоб скористатися **анотаціями типів** функцій — це дає кращу підтримку з боку редактора та інструментів типу mypy, і при цьому FastAPI продовжує виконувати **фільтрацію даних** у відповіді.
{* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *}
Завдяки цьому ми отримуємо підтримку інструментів — від редакторів і mypy, адже цей код коректний з точки зору типів, — але ми також отримуємо фільтрацію даних від FastAPI.
Завдяки цьому ми отримуємо підтримку інструментів — від редакторів і mypy, оскільки цей код є коректним з точки зору типів, — але ми також отримуємо фільтрацію даних від FastAPI.
Як це працює? Давайте розберемося. 🤓
### Анотації типів і підтримка інструментів { #type-annotations-and-tooling }
### Типи та підтримка інструментів
Спершу подивімося, як це бачать редактори, mypy та інші інструменти.
Спершу подивимось, як це бачать редактори, mypy та інші інструменти.
`BaseUser` має базові поля. Потім `UserIn` успадковує `BaseUser` і додає поле `password`, отже він включатиме всі поля з обох моделей.
`BaseUser` має базові поля. Потім `UserIn` успадковує `BaseUser` і додає поле `password`, отже, він матиме всі поля з обох моделей.
Ми анотуємо тип повернення функції як `BaseUser`, але фактично повертаємо екземпляр `UserIn`.
Ми зазначаємо тип повернення функції як `BaseUser`, але фактично повертаємо екземпляр `UserIn`.
Редактор, mypy та інші інструменти не скаржитимуться на це, тому що з точки зору типізації `UserIn` є підкласом `BaseUser`, а це означає, що він є *валідним* типом, коли очікується будь-що, що є `BaseUser`.
Редактор, mypy та інші інструменти не скаржитимуться на це, тому що з точки зору типізації `UserIn` є підкласом `BaseUser`, а це означає, що він є `валідним` типом, коли очікується будь-що, що є `BaseUser`.
### Фільтрація даних у FastAPI { #fastapi-data-filtering }
### Фільтрація даних у FastAPI
Тепер для FastAPI він побачить тип повернення і переконається, що те, що ви повертаєте, містить **лише** поля, які оголошені у цьому типі.
Тепер для FastAPI він бачить тип повернення і переконується, що те, що Ви повертаєте, містить **тільки** поля, які оголошені у цьому типі.
FastAPI виконує кілька внутрішніх операцій з Pydantic, щоб гарантувати, що ті самі правила наслідування класів не застосовуються для фільтрації повернених даних, інакше ви могли б зрештою повертати значно більше даних, ніж очікували.
FastAPI виконує кілька внутрішніх операцій з Pydantic, щоб гарантувати, що правила наслідування класів не застосовуються для фільтрації повернених даних, інакше Ви могли б повернути значно більше даних, ніж очікували.
Таким чином ви можете отримати найкраще з двох світів: анотації типів із **підтримкою інструментів** і **фільтрацію даних**.
Таким чином, Ви отримуєте найкраще з двох світів: анотації типів **з підтримкою інструментів** і **фільтрацію даних**.
## Подивитися в документації { #see-it-in-the-docs }
## Подивитись у документації
Коли ви дивитеся автоматичну документацію, ви можете перевірити, що вхідна модель і вихідна модель матимуть власну JSON Schema:
Коли Ви дивитесь автоматичну документацію, Ви можете побачити, що вхідна модель і вихідна модель мають власну JSON-схему:
<img src="/img/tutorial/response-model/image01.png">
І обидві моделі будуть використані для інтерактивної документації API:
І обидві моделі використовуються для інтерактивної API-документації:
<img src="/img/tutorial/response-model/image02.png">
## Інші анотації типів повернення { #other-return-type-annotations }
## Інші анотації типів повернення
Можуть бути випадки, коли ви повертаєте щось, що не є валідним полем Pydantic, і анотуєте це у функції лише для того, щоб отримати підтримку від інструментів (редактора, mypy тощо).
Існують випадки, коли Ви повертаєте щось, що не є допустимим полем Pydantic, але анотуєте це у функції лише для того, щоб отримати підтримку від інструментів (редактора, mypy тощо).
### Повернути Response напряму { #return-a-response-directly }
### Повернення Response напряму
Найпоширенішим випадком буде [повернення Response напряму, як пояснюється пізніше у розширеній документації](../advanced/response-directly.md){.internal-link target=_blank}.
{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *}
{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *}
Цей простий випадок автоматично обробляється FastAPI, тому що анотація типу повернення — це клас (або підклас) `Response`.
І інструменти також будуть задоволені, бо і `RedirectResponse`, і `JSONResponse` є підкласами `Response`, отже анотація типу коректна.
### Анотувати підклас Response { #annotate-a-response-subclass }
### Анотація підкласу Response
Ви також можете використати підклас `Response` в анотації типу:
Також можна використовувати підклас `Response` у анотації типу:
{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *}
{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *}
Це теж працюватиме, бо `RedirectResponse` — підклас `Response`, і FastAPI автоматично обробить цей простий випадок.
### Некоректні анотації типу повернення { #invalid-return-type-annotations }
### Некоректні анотації типу повернення
Але коли ви повертаєте якийсь інший довільний об’єкт, що не є валідним типом Pydantic (наприклад, об’єкт бази даних), і анотуєте його так у функції, FastAPI спробує створити модель відповіді Pydantic на основі цієї анотації типу і це завершиться помилкою.
Але коли Ви повертаєте якийсь інший довільний об’єкт, що не є валідним типом Pydantic (наприклад, об’єкт бази даних), і анотуєте його так у функції, FastAPI спробує створити Pydantic модель відповіді на основі цієї анотації типу, і це завершиться помилкою.
Те саме станеться, якщо ви використаєте <abbr title='Обєднання (union) між кількома типами означає «будь-який із цих типів».'>union</abbr> між різними типами, де один або більше не є валідними типами Pydantic, наприклад, це завершиться помилкою 💥:
Те саме станеться, якщо Ви використовуєте <abbr title="Об'єднання (union) кількох типів означає: «будь-який з цих типів».">union</abbr> між різними типами, де один або більше не є валідними типами Pydantic, наприклад, це спричинить помилку 💥:
{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *}
...це не працює, тому що анотація типу не є типом Pydantic і не є просто одним класом `Response` або його підкласом, це union (будь-який із двох) між `Response` і `dict`.
...це не працює, тому що тип анотації не є типом Pydantic і не є просто класом `Response` або його підкласом, а є об’єднанням (union) — або `Response`, або `dict`.
### Вимкнути модель відповіді { #disable-response-model }
### Відключення Моделі Відповіді
Продовжуючи приклад вище, можливо, ви не хочете мати стандартну валідацію даних, документацію, фільтрацію тощо, які виконує FastAPI.
Продовжуючи приклад вище, можливо, Ви не хочете використовувати стандартну валідацію даних, автоматичну документацію, фільтрацію тощо, які FastAPI виконує за замовчуванням.
Але ви можете все одно хотіти залишити анотацію типу повернення у функції, щоб отримати підтримку від інструментів, як-от редактори та перевірки типів (наприклад, mypy).
Але ви все одно можете залишити анотацію типу у функції, щоб зберегти підтримку з боку інструментів, таких як редактори коду або статичні перевірки типів (наприклад, mypy).
У такому випадку ви можете вимкнути генерацію моделі відповіді, встановивши `response_model=None`:
{* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *}
Це змусить FastAPI пропустити генерацію моделі відповіді, і таким чином ви зможете використовувати будь-які потрібні анотації типів повернення без впливу на ваш FastAPI застосунок. 🤓
Це змусить FastAPI пропустити генерацію моделі відповіді, і таким чином Ви зможете використовувати будь-які анотації типів повернення без впливу на вашу FastAPI аплікацію. 🤓
## Параметри кодування моделі відповіді { #response-model-encoding-parameters }
## Параметри кодування моделі відповіді
Ваша модель відповіді може мати значення за замовчуванням, наприклад:
@@ -229,19 +230,19 @@ FastAPI виконує кілька внутрішніх операцій з Pyd
* `tax: float = 10.5` має значення за замовчуванням `10.5`.
* `tags: List[str] = []` має значення за замовчуванням порожній список: `[]`.
але ви можете захотіти не включати їх у результат, якщо вони фактично не були збережені.
Але Ви можете захотіти не включати їх у результат, якщо вони фактично не були збережені.
Наприклад, якщо у вас є моделі з багатьма необов’язковими атрибутами у NoSQL базі даних, але ви не хочете надсилати дуже довгі JSON-відповіді, повні значень за замовчуванням.
Наприклад, якщо у Вас є моделі з багатьма необов’язковими атрибутами у NoSQL базі даних, але Ви не хочете відправляти дуже довгі JSON-відповіді, повні значень за замовчуванням.
### Використовуйте параметр `response_model_exclude_unset` { #use-the-response-model-exclude-unset-parameter }
### Використовуйте параметр `response_model_exclude_unset`
Ви можете встановити параметр *декоратора операції шляху* `response_model_exclude_unset=True`:
Ви можете встановити параметр декоратора шляху `response_model_exclude_unset=True`:
{* ../../docs_src/response_model/tutorial004_py310.py hl[22] *}
і ці значення за замовчуванням не будуть включені у відповідь, лише значення, які фактично встановлені.
і ці значення за замовчуванням не будуть включені у відповідь, тільки фактично встановлені значення.
Отже, якщо ви надішлете запит до цієї *операції шляху* для елемента з ID `foo`, відповідь (без включення значень за замовчуванням) буде:
Отже, якщо Ви надішлете запит до цього оператора шляху для елемента з item_id `foo`, відповідь (без включення значень за замовчуванням) буде:
```JSON
{
@@ -252,18 +253,32 @@ FastAPI виконує кілька внутрішніх операцій з Pyd
/// info | Інформація
У Pydantic версії 1 метод називався `.dict()`, він був застарілий (але ще підтримується) у Pydantic версії 2 і перейменований у `.model_dump()`.
Приклади тут використовують `.dict()` для сумісності з Pydantic v1, але Вам слід використовувати `.model_dump()`, якщо Ви можете використовувати Pydantic v2.
///
/// info | Інформація
FastAPI використовує `.dict()` моделі Pydantic з <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">параметром `exclude_unset`</a>, щоб досягти цього.
///
/// info | Інформація
Ви також можете використовувати:
* `response_model_exclude_defaults=True`
* `response_model_exclude_none=True`
як описано в <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">документації Pydantic</a> для `exclude_defaults` та `exclude_none`.
як описано в <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">документації Pydantic</a> for `exclude_defaults` та `exclude_none`.
///
#### Дані зі значеннями для полів із типовими значеннями { #data-with-values-for-fields-with-defaults }
#### Дані зі значеннями для полів із типовими значеннями
Але якщо ваші дані мають значення для полів моделі з типовими значеннями, як у елемента з ID `bar`:
Але якщо Ваші дані мають значення для полів моделі з типовими значеннями, як у елемента з item_id `bar`:
```Python hl_lines="3 5"
{
@@ -273,12 +288,11 @@ FastAPI виконує кілька внутрішніх операцій з Pyd
"tax": 20.2
}
```
вони будуть включені у відповідь.
#### Дані з тими самими значеннями, що й типові { #data-with-the-same-values-as-the-defaults }
#### Дані з тими самими значеннями, що й типові
Якщо дані мають ті самі значення, що й типові, як у елемента з ID `baz`:
Якщо дані мають ті самі значення, що й типові, як у елемента з item_id `baz`:
```Python hl_lines="3 5-6"
{
@@ -290,7 +304,7 @@ FastAPI виконує кілька внутрішніх операцій з Pyd
}
```
FastAPI достатньо розумний (насправді, Pydantic достатньо розумний), щоб зрозуміти, що, хоча `description`, `tax` і `tags` мають ті самі значення, що й типові, їх було встановлено явно (а не взято як значення за замовчуванням).
FastAPI достатньо розумний (насправді, Pydantic достатньо розумний), щоб зрозуміти, що, хоча `description`, `tax` і `tags` мають ті самі значення, що й типові, вони були встановлені явно (а не взяті як значення за замовчуванням).
Отже, вони будуть включені у JSON-відповідь.
@@ -298,23 +312,24 @@ FastAPI достатньо розумний (насправді, Pydantic дос
Зверніть увагу, що типові значення можуть бути будь-якими, не лише `None`.
Це може бути list (`[]`), `float` зі значенням `10.5` тощо.
Це може бути list (`[]`), `float` 10.5 тощо.
///
### `response_model_include` та `response_model_exclude` { #response-model-include-and-response-model-exclude }
### `response_model_include` та `response_model_exclude`
Ви також можете використовувати параметри *декоратора операції шляху* `response_model_include` та `response_model_exclude`.
Вони приймають `set` зі `str` з іменами атрибутів, які потрібно включити (пропускаючи решту) або виключити (включаючи решту).
Вони приймають `set` (множину) рядків (`str`) з іменами атрибутів, які потрібно включити (пропускаючи інші) або виключити (включаючи інші).
Це можна використовувати як швидкий спосіб, якщо у вас є лише одна модель Pydantic і ви хочете видалити деякі дані з виводу.
Це можна використовувати як швидкий спосіб, якщо у Вас є лише одна модель Pydantic і Ви хочете видалити деякі дані з виводу.
/// tip | Порада
Але все ж рекомендується використовувати описані вище підходи, застосовуючи кілька класів, замість цих параметрів.
Але все ж рекомендується використовувати описані вище підходи, із застосуванням кількох класів, замість цих параметрів.
Це тому, що JSON Schema, який генерується в OpenAPI вашого застосунку (і в документації), все одно буде відповідати повній моделі, навіть якщо ви використовуєте `response_model_include` або `response_model_exclude`, щоб пропустити деякі атрибути.
Це тому, що JSON Schema, який генерується у вашому OpenAPI додатку (і в документації), все одно буде відповідати повній моделі, навіть якщо Ви використовуєте `response_model_include` або `response_model_exclude` для виключення деяких атрибутів.
Це також стосується `response_model_by_alias`, який працює подібним чином.
@@ -330,14 +345,14 @@ FastAPI достатньо розумний (насправді, Pydantic дос
///
#### Використання `list` замість `set` { #using-lists-instead-of-sets }
#### Використання `list` замість `set`
Якщо ви забудете використати `set` і натомість застосуєте `list` або `tuple`, FastAPI все одно перетворить це на `set`, і все працюватиме правильно:
Якщо Ви забудете використати `set` і натомість застосуєте `list` або `tuple`, FastAPI все одно перетворить це на `set`, і все працюватиме правильно:
{* ../../docs_src/response_model/tutorial006_py310.py hl[29,35] *}
## Підсумок { #recap }
## Підсумок
Використовуйте параметр `response_model` *декоратора операції шляху*, щоб визначати моделі відповіді і особливо щоб гарантувати фільтрацію приватних даних.
Використовуйте параметр `response_model` *декоратора операції шляху*, щоб визначати моделі відповіді, особливо щоб гарантувати фільтрацію приватних даних.
Використовуйте `response_model_exclude_unset`, щоб повертати лише явно встановлені значення.

View File

@@ -1,6 +1,6 @@
# Код статусу відповіді { #response-status-code }
# Статус коди Відповідей
Так само, як ви можете вказати модель відповіді, ви також можете оголосити HTTP код статусу, що використовується для відповіді, за допомогою параметра `status_code` в будь-якій з *операцій шляху*:
Так само як Ви можете вказати модель відповіді, Ви також можете оголосити HTTP код статусу для відповіді за допомогою параметра `status_code` в будь-якій з *операцій шляху*:
* `@app.get()`
* `@app.post()`
@@ -8,83 +8,82 @@
* `@app.delete()`
* тощо.
{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *}
{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
/// note | Примітка
/// note | Нотатка
Зверніть увагу, що `status_code` є параметром методу «декоратора» (`get`, `post`, тощо). Не вашої *функції операції шляху*, як усі параметри та тіло.
Зверніть увагу, що `status_code` є параметром методу "декоратора" (`get`, `post` і т.д.), а не Вашої *функції операції шляху*, як усі інші параметри та тіло запиту.
///
Параметр `status_code` приймає число з HTTP кодом статусу.
Параметр `status_code` приймає число, яке відповідає HTTP коду статусу.
/// info | Інформація
`status_code` також може, як альтернативу, приймати `IntEnum`, наприклад, Python <a href="https://docs.python.org/3/library/http.html#http.HTTPStatus" class="external-link" target="_blank">`http.HTTPStatus`</a>.
`status_code` також може отримувати значення з `IntEnum`, наприклад, з Python <a href="https://docs.python.org/3/library/http.html#http.HTTPStatus" class="external-link" target="_blank">`http.HTTPStatus`</a>.
///
Він буде:
* Повертати цей код статусу у відповіді.
* Документувати його як такий у схемі OpenAPI (і, таким чином, в інтерфейсах користувача):
* Повертати вказаний код статусу у відповіді.
* Документувати його як такий у схемі OpenAPI (і, таким чином, в інтерфейсі користувача):
<img src="/img/tutorial/response-status-code/image01.png">
/// note | Примітка
/// note | Нотатка
Деякі коди відповіді (див. наступний розділ) вказують, що відповідь не має тіла.
FastAPI знає про це і створить документацію OpenAPI, яка вказує, що тіла відповіді немає.
FastAPI знає про це і створить OpenAPI документацію, яка вказує, що тіла відповіді немає.
///
## Про HTTP коди статусу { #about-http-status-codes }
## Про HTTP статус коди
/// note | Примітка
/// note | Нотатка
Якщо ви вже знаєте, що таке HTTP коди статусу, перейдіть до наступного розділу.
Якщо Ви вже знаєте, що таке HTTP коди статусу, переходьте до наступного розділу.
///
В HTTP ви надсилаєте числовий код статусу з 3 цифр як частину відповіді.
В HTTP Ви надсилаєте числовий код статусу з 3 цифр як частину відповіді.
Ці коди статусу мають пов’язану назву для їх розпізнавання, але важливою частиною є число.
Ці коди статусу мають пов’язану назву для їх розпізнавання, але найважливішою частиною є саме число.
Коротко:
* `100 - 199` — для «Information». Ви рідко використовуєте їх напряму. Відповіді з такими кодами статусу не можуть мати тіла.
* **`200 - 299`** — для «Successful» відповідей. Це ті, які ви використовуватимете найчастіше.
* `200` код статусу за замовчуванням, який означає, що все було «OK».
* Інший приклад `201`, «Created». Його зазвичай використовують після створення нового запису в базі даних.
* Особливий випадок `204`, «No Content». Цю відповідь використовують, коли немає вмісту для повернення клієнту, і тому відповідь не повинна мати тіла.
* **`300 - 399`** — для «Redirection». Відповіді з цими кодами статусу можуть мати або не мати тіла, за винятком `304`, «Not Modified», яка не повинна мати тіла.
* **`400 - 499`** — для відповідей «Client error». Це другий тип, який ви, ймовірно, будете використовувати найчастіше.
* Приклад `404`, для відповіді «Not Found».
* Для загальних помилок з боку клієнта ви можете просто використовувати `400`.
* `500 - 599` — для помилок сервера. Ви майже ніколи не використовуєте їх напряму. Коли щось піде не так у якійсь частині коду вашого застосунку або на сервері, автоматично буде повернено один із цих кодів статусу.
* **`100 - 199`** "Інформаційні" відповіді. Ви рідко використовуєте їх напряму. Відповіді з такими кодами не можуть мати тіла.
* **`200 - 299`** "Успішні" відповіді. Це ті, які Ви використовуватимете найчастіше.
* `200` - код за замовчуванням, який означає, що все пройшло "OK".
* Інший приклад `201`, "Created" (створено). Його зазвичай використовують після створення нового запису в базі даних.
* Особливий випадок `204`, "No Content" (немає вмісту). Ця відповідь використовується, коли немає даних для повернення клієнту, тому відповідь не повинна мати тіла.
* **`300 - 399`** "Перенаправлення". Відповіді з цими кодами можуть мати або не мати тіла, за винятком `304`, "Not Modified" (не змінено), яка не повинна мати тіла.
* **`400 - 499`** "Помилка клієнта". Це другий тип, який Ви, ймовірно, будете використовувати найчастіше.
* Приклад `404`, "Not Found" (не знайдено).
* Для загальних помилок клієнта можна використовувати `400`.
* `500 - 599` омилки сервера". Ви майже ніколи не використовуєте їх напряму. Якщо в коді Вашого застосунку або на сервері щось пішло не так, автоматично буде повернено один із цих кодів статусу.
/// tip | Порада
Щоб дізнатися більше про кожен код статусу і для чого призначений кожен із них, перегляньте документацію <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> про HTTP коди статусу</a>.
Щоб дізнатися більше про кожен код статусу і призначення кожного з них, перегляньте документацію <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> про HTTP коди статусу</a>.
///
## Скорочення, щоб запамятати назви { #shortcut-to-remember-the-names }
## Легкий спосіб запам'ятати назви
Розглянемо попередній приклад ще раз:
Розглянемо ще раз попередній приклад:
{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *}
{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
`201` це код статусу для «Created».
`201` - це код статусу для "Created" (створено).
Але вам не потрібно запам'ятовувати, що означає кожен із цих кодів.
Але Вам не потрібно запам'ятовувати, що означає кожен із цих кодів.
Ви можете використовувати зручні змінні з `fastapi.status`.
Ви можете використовувати зручні змінні з `fastapi.status`
{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *}
{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *}
Вони — просто для зручності, містять те саме число, але так ви можете скористатися автозаповненням редактора, щоб знайти їх:
Ці змінні просто для зручності. Вони містять ті ж самі числа, але Ви можете скористатися автозаповненням в редакторі:
<img src="/img/tutorial/response-status-code/image02.png">
@@ -92,10 +91,10 @@ FastAPI знає про це і створить документацію OpenAP
Ви також можете використати `from starlette import status`.
**FastAPI** надає той самий `starlette.status` як `fastapi.status` просто як зручність для вас, розробника. Але він походить безпосередньо зі Starlette.
**FastAPI** надає ті ж самі змінні `starlette.status` як `fastapi.status`, просто для зручності розробника. Однак вони походять безпосередньо зі Starlette.
///
## Зміна значення за замовчуванням { #changing-the-default }
## Зміна значення за замовчуванням
Пізніше, у [Посібнику для досвідчених користувачів](../advanced/response-change-status-code.md){.internal-link target=_blank}, ви побачите, як повертати інший код статусу, ніж значення за замовчуванням, яке ви оголошуєте тут.
Далі, у Посібнику для досвідчених користувачів{.internal-link target=_blank}, Ви дізнаєтесь, як повернути інший код статусу, ніж той, який Ви оголосили тут.

View File

@@ -1,22 +1,44 @@
# Декларування прикладів вхідних даних { #declare-request-example-data }
# Декларування прикладів вхідних даних
Ви можете задати приклади даних, які Ваш застосунок може отримувати.
Ось кілька способів, як це зробити.
## Додаткові дані JSON-схеми в моделях Pydantic { #extra-json-schema-data-in-pydantic-models }
## Додаткові дані JSON-схеми в моделях Pydantic
Ви можете задати `examples` для моделі Pydantic, які буде додано до згенерованої JSON-схеми.
//// tab | Pydantic v2
{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *}
Ця додаткова інформація буде додана як є до **JSON-схеми** для цієї моделі, і вона буде використана в документації до API.
////
Ви можете використати атрибут `model_config`, який приймає `dict`, як описано в <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">документації Pydantic: Configuration</a>.
//// tab | Pydantic v1
{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *}
////
Ця додаткова інформація буде додана як є до **JSON-схеми**, і вона буде використовуватися в документації до API.
//// tab | Pydantic v2
У версії Pydantic 2 використовується атрибут `model_config`, який приймає `dict`, як описано в <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">документації Pydantic: Конфігурація</a>.
Ви можете встановити `"json_schema_extra"` як `dict`, що містить будь-які додаткові дані, які Ви хочете відобразити у згенерованій JSON-схемі, включаючи `examples`.
/// tip | Порада
////
//// tab | Pydantic v1
У версії Pydantic 1 використовується внутрішній клас `Config` і параметр `schema_extra`, як описано в <a href="https://docs.pydantic.dev/1.10/usage/schema/#schema-customization" class="external-link" target="_blank">документації Pydantic: Налаштування схеми</a>.
Ви можете задати `schema_extra` як `dict`, що містить будь-які додаткові дані, які Ви хочете бачити у згенерованій JSON-схемі, включаючи `examples`.
////
/// tip | Підказка
Ви можете використати ту ж техніку, щоб розширити JSON-схему і додати власну додаткову інформацію.
@@ -28,19 +50,19 @@
OpenAPI 3.1.0 (який використовується починаючи з FastAPI 0.99.0) додав підтримку `examples`, що є частиною стандарту **JSON-схеми**.
До цього підтримувався лише ключ `example` з одним прикладом. Він все ще підтримується в OpenAPI 3.1.0, але є застарілим і не входить до стандарту JSON Schema. Тому рекомендується перейти з `example` на `examples`. 🤓
До цього підтримувався лише ключ `example` з одним прикладом. Він все ще підтримується в OpenAPI 3.1.0, але є застарілим і не входить до стандарту JSON Schema. Тому рекомендується перейти з `example` на `examples`. 🤓
Більше про це можна прочитати в кінці цієї сторінки.
///
## Додаткові аргументи `Field` { #field-additional-arguments }
## Додаткові аргументи `Field`
Коли ви використовуєте `Field()` у моделях Pydantic, Ви також можете вказати додаткові `examples`:
{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *}
## `examples` у JSON-схемі — OpenAPI { #examples-in-json-schema-openapi }
## `examples` у JSON-схемі — OpenAPI
При використанні будь-кого з наступного:
@@ -54,41 +76,41 @@ OpenAPI 3.1.0 (який використовується починаючи з F
Ви також можете задати набір `examples` з додатковою інформацією, яка буде додана до їхніх **JSON-схем** у **OpenAPI**.
### `Body` з `examples` { #body-with-examples }
### `Body` з `examples`
Тут ми передаємо `examples`, які містять один приклад очікуваних даних у `Body()`:
{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *}
### Приклад у UI документації { #example-in-the-docs-ui }
### Приклад у UI документації
За допомогою будь-якого з наведених вище методів це виглядатиме так у `/docs`:
За допомогою будь-якого з наведених вище методів це виглядатиме так у документації за `/docs`:
<img src="/img/tutorial/body-fields/image01.png">
### `Body` з кількома `examples` { #body-with-multiple-examples }
### `Body` з кількома `examples`
Звичайно, Ви також можете передати кілька `examples`:
{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *}
Коли Ви це робите, приклади будуть частиною внутрішньої **JSON-схеми** для цих даних тіла.
Коли Ви це робите, приклади будуть частиною внутрішньої **JSON-схеми** для цих даних.
Втім, на момент написання цього (<abbr title="2023-08-26">час написання цього</abbr>), Swagger UI — інструмент, який відповідає за відображення UI документації — не підтримує показ кількох прикладів для даних у **JSON-схемі**. Але нижче можна прочитати про обхідний шлях.
Втім, на момент написання цього (<abbr title="2023-08-26">26 серпня 2023</abbr>), Swagger UI — інструмент, який відповідає за відображення UI документації — не підтримує показ кількох прикладів у **JSON-схеми**. Але нижче можна прочитати про обхідний шлях.
### Специфічні для OpenAPI `examples` { #openapi-specific-examples }
### Специфічні для OpenAPI `examples`
Ще до того, як **JSON-схема** почала підтримувати `examples`, OpenAPI вже мала підтримку іншого поля, яке також називається `examples`.
Ще до того, як **JSON-схема** почала підтримувати `examples`, OpenAPI вже мала підтримку поля з такою ж назвою — `examples`.
Це **специфічне для OpenAPI** поле `examples` розміщується в іншому розділі специфікації OpenAPI. Воно розміщується в **деталях кожної *операції шляху***, а не всередині кожної JSON-схеми.
Це **специфічне для OpenAPI** поле `examples` розміщується в іншій частині специфікації OpenAPIу **деталях кожної *операції шляху***, а не всередині самої JSON-схеми.
І Swagger UI вже давно підтримує це поле `examples`. Тому Ви можете використовувати його, щоб **відображати** різні **приклади в UI документації**.
Swagger UI вже давно підтримує це поле `examples`. Тому Ви можете використовувати його, щоб **відображати** кілька **прикладів у документації**.
Форма цього специфічного для OpenAPI поля `examples` — це `dict` з **кількома прикладами** (а не `list`), кожен із яких має додаткову інформацію, яка також буде додана до **OpenAPI**.
Це поле `examples` у специфікації OpenAPI — це `dict` (словник) з **кількома прикладами** (а не список `list`), кожен із яких може містити додаткову інформацію, що буде додана до **OpenAPI**.
Воно не включається всередину кожної JSON-схеми, що міститься в OpenAPI, воно розміщується зовні, безпосередньо в *операції шляху*.
Воно не включається до JSON Schema кожного параметра, а розміщується зовні, безпосередньо в *операції шляху*.
### Використання параметра `openapi_examples` { #using-the-openapi-examples-parameter }
### Використання параметра `openapi_examples`
Ви можете оголосити специфічні для OpenAPI `examples` у FastAPI за допомогою параметра `openapi_examples` для:
@@ -100,32 +122,30 @@ OpenAPI 3.1.0 (який використовується починаючи з F
* `Form()`
* `File()`
Ключі `dict` ідентифікують кожен приклад, а кожне значення — це інший `dict`.
Кожен специфічний `dict` прикладу в `examples` може містити:
Ключі словника (`dict`) ідентифікують кожен приклад, а кожне значення `dict` — кожен специфічний словник `dict` в `examples` може містити:
* `summary`: короткий опис прикладу.
* `description`: розгорнутий опис, який може містити Markdown.
* `value`: це сам приклад, який буде показано, наприклад `dict`.
* `externalValue`: альтернатива `value`, URL-адреса, що вказує на приклад. Проте це може не підтримуватися такою кількістю інструментів, як `value`.
* `description`: розгорнутий опис (може містити Markdown).
* `value`: сам приклад, наприклад, словник (`dict`).
* `externalValue`: альтернатива `value`, URL-адреса, що вказує на приклад. Проте ця опція може не підтримуватися більшістю інструментів, на відміну від `value`.
Використання виглядає так:
{* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *}
### Приклади OpenAPI в UI документації { #openapi-examples-in-the-docs-ui }
### Приклади OpenAPI у UI документації
З `openapi_examples`, доданим до `Body()`, `/docs` виглядатиме так:
З параметром `openapi_examples`, доданим до `Body()`, документація `/docs` виглядатиме так:
<img src="/img/tutorial/body-fields/image02.png">
## Технічні деталі { #technical-details }
## Технічні деталі
/// tip | Порада
/// tip | Підказка
Якщо Ви вже використовуєте **FastAPI** версії **0.99.0 або вище**, Ви, ймовірно, можете **пропустити** ці технічні деталі.
Якщо Ви вже використовуєте **FastAPI** версії **0.99.0 або вище**, Ви можете **пропустити** цей розділ.
Вони більш актуальні для старих версій, до появи OpenAPI 3.1.0.
Він більш актуальний для старих версій, до появи OpenAPI 3.1.0.
Можна вважати це коротким **історичним екскурсом** у OpenAPI та JSON Schema. 🤓
@@ -135,68 +155,68 @@ OpenAPI 3.1.0 (який використовується починаючи з F
Це дуже технічна інформація про стандарти **JSON Schema** і **OpenAPI**.
Якщо вищезгадані ідеї вже працюють у Вас, цього може бути достатньо, і Вам, ймовірно, не потрібні ці деталі — можете пропустити.
Якщо вищезгадані ідеї вже працюють у Вас — можете не заглиблюватися в ці деталі.
///
До OpenAPI 3.1.0 OpenAPI використовував стару та модифіковану версію **JSON Schema**.
До OpenAPI 3.1.0 специфікація використовувала стару та модифіковану версію **JSON Schema**.
JSON Schema не мала `examples`, тож OpenAPI додала власне поле `example` до своєї модифікованої версії.
Оскільки JSON Schema раніше не підтримувала `examples`, OpenAPI додала власне поле `examples`.
OpenAPI також додала поля `example` і `examples` до інших частин специфікації:
OpenAPI також додала `example` і `examples` до інших частин специфікації:
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-object" class="external-link" target="_blank">`Parameter Object` (в специфікації)</a>, який використовувався утилітами FastAPI:
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-object" class="external-link" target="_blank">`Parameter Object` (в специфікації)</a> використовується FastAPI для:
* `Path()`
* `Query()`
* `Header()`
* `Cookie()`
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object" class="external-link" target="_blank">`Request Body Object`, у полі `content`, у `Media Type Object` (в специфікації)</a>, який використовувався утилітами FastAPI:
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object" class="external-link" target="_blank">`Request Body Object`, в полі `content`, в `Media Type Object` (в специфікації)</a> використовується FastAPI для:
* `Body()`
* `File()`
* `Form()`
/// info | Інформація
Цей старий специфічний для OpenAPI параметр `examples` тепер називається `openapi_examples`, починаючи з FastAPI `0.103.0`.
Цей старий параметр `examples`, специфічний для OpenAPI, тепер називається `openapi_examples`, починаючи з FastAPI версії `0.103.0`.
///
### Поле `examples` у JSON Schema { #json-schemas-examples-field }
### Поле `examples` у JSON Schema
Пізніше JSON Schema додала поле <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a> у нову версію специфікації.
А потім новий OpenAPI 3.1.0 базувався на найновішій версії (JSON Schema 2020-12), яка включала це нове поле `examples`.
І вже OpenAPI 3.1.0 базується на цій новій версії (JSON Schema 2020-12), яка включає поле `examples`.
І тепер це нове поле `examples` має вищий пріоритет за старе одиночне (і кастомне) поле `example`, яке тепер є застарілим.
Тепер це поле `examples` є пріоритетним і замінює старе (і кастомне) поле `example`, яке стало застарілим.
Це нове поле `examples` у JSON Schema — це **просто `list`** прикладів, а не `dict` з додатковими метаданими, як в інших місцях OpenAPI (описаних вище).
Нове поле `examples` у JSON Schema — це **просто список (`list`)** прикладів, без додаткових метаданих (на відміну від OpenAPI).
/// info | Інформація
Навіть після релізу OpenAPI 3.1.0 з цією новою простішою інтеграцією з JSON Schema, протягом певного часу Swagger UI, інструмент, який надає автоматичну документацію, не підтримував OpenAPI 3.1.0 (тепер підтримує, починаючи з версії 5.0.0 🎉).
Навіть після того, як з'явився OpenAPI 3.1.0, який підтримував examples у JSON Schema, інструмент Swagger UI ще деякий час не підтримував цю версію (підтримка з’явилась з версії 5.0.0 🎉).
Через це версії FastAPI до 0.99.0 все ще використовували версії OpenAPI нижчі за 3.1.0.
///
### `examples` у Pydantic і FastAPI { #pydantic-and-fastapi-examples }
### `Examples` в Pydantic і FastAPI
Коли Ви додаєте `examples` у модель Pydantic через `schema_extra` або `Field(examples=["something"])`, цей приклад додається до **JSON Schema** для цієї моделі Pydantic.
Коли Ви додаєте `examples` у модель Pydantic через `schema_extra` або `Field(examples=["something"])`, ці приклади додаються до **JSON Schema** цієї моделі.
І ця **JSON Schema** Pydantic-моделі включається до **OpenAPI** Вашого API, а потім використовується в UI документації.
І ця **JSON Schema** Pydantic-моделі включається до **OpenAPI** Вашого API, а потім використовується в UI документації (docs UI).
У версіях FastAPI до 0.99.0 (0.99.0 і вище використовують новіший OpenAPI 3.1.0), коли Ви використовували `example` або `examples` з будь-якими іншими утилітами (`Query()`, `Body()` тощо), ці приклади не додавалися до JSON Schema, що описує ці дані (навіть не до власної версії JSON Schema в OpenAPI), натомість вони додавалися безпосередньо до декларації *операції шляху* в OpenAPI (поза межами частин OpenAPI, які використовують JSON Schema).
У версіях FastAPI до 0.99.0 (починаючи з 0.99.0 використовується новіший OpenAPI 3.1.0), коли Ви використовували `example` або `examples` з іншими утилітами (`Query()`, `Body()` тощо), ці приклади не додавалися до JSON Schema, який описує ці дані (навіть не до власної версії JSON Schema у OpenAPI). Натомість вони додавалися безпосередньо до опису *обробника шляху* *(path operation)* в OpenAPI (тобто поза межами частин, які використовують JSON Schema).
Але тепер, коли FastAPI 0.99.0 і вище використовує OpenAPI 3.1.0, який використовує JSON Schema 2020-12, і Swagger UI 5.0.0 і вище, все стало більш узгодженим, і приклади включаються до JSON Schema.
Але тепер, коли FastAPI 0.99.0 і вище використовують OpenAPI 3.1.0, а той — JSON Schema 2020-12, разом із Swagger UI 5.0.0 і вище все стало більш узгодженим, і examples тепер включаються до JSON Schema.
### Swagger UI та специфічні для OpenAPI `examples` { #swagger-ui-and-openapi-specific-examples }
### Swagger UI та специфічні для OpenAPI `examples`
Оскільки Swagger UI не підтримував кілька прикладів JSON Schema (станом на 2023-08-26), користувачі не мали можливості показати кілька прикладів у документації.
Раніше (станом на 26 серпня 2023 року) Swagger UI не підтримував кілька прикладів у JSON Schema, тому користувачі не мали можливості показати декілька прикладів у документації.
Щоб вирішити це, FastAPI `0.103.0` **додав підтримку** оголошення того самого старого **OpenAPI-специфічного** поля `examples` через новий параметр `openapi_examples`. 🤓
Щоб вирішити це, FastAPI починаючи з версії 0.103.0 **додав підтримку** старого **OpenAPI-специфічного** поля `examples` через новий параметр `openapi_examples`. 🤓
### Підсумок { #summary }
### Підсумок
Раніше я казав, що не дуже люблю історію... а тепер подивіться на мене — читаю «технічні історичні» лекції. 😅
Раніше я казав, що не люблю історію... а тепер ось я — розповідаю "технічні історичні" лекції. 😅
Коротко: **оновіться до FastAPI 0.99.0 або вище**і все стане значно **простішим, узгодженим та інтуїтивно зрозумілим**, і Вам не доведеться знати всі ці історичні деталі. 😎

View File

@@ -1,72 +1,70 @@
# Безпека { #security }
# Безпека
Існує багато способів реалізувати безпеку, автентифікацію та авторизацію.
І зазвичай це складна і «непроста» тема.
Це зазвичай складна і "непроста" тема.
У багатьох фреймворках і системах лише обробка безпеки та автентифікації потребує великих зусиль і коду (у багатьох випадках це може бути 50% або більше від усього написаного коду).
У багатьох фреймворках і системах забезпечення безпеки та автентифікації займає величезну частину зусиль і коду (іноді — понад 50% всього написаного коду).
**FastAPI** надає кілька інструментів, які допоможуть вам працювати з **безпекою** легко, швидко, стандартним способом, без необхідності вивчати всі специфікації безпеки.
**FastAPI** надає кілька інструментів, які допоможуть Вам впоратися з **безпекою** легко, швидко, стандартним способом, без необхідності вивчати всі специфікації безпеки.
Але спочатку розгляньмо кілька невеликих понять.
Але спочатку кілька коротких понять.
## Поспішаєте? { #in-a-hurry }
## Поспішаєте?
Якщо вам не цікаві всі ці терміни й вам просто потрібно додати безпеку з автентифікацією на основі імені користувача та пароля *прямо зараз*, переходьте до наступних розділів.
Якщо Вам не цікаві всі ці терміни й просто потрібно *швидко* додати автентифікацію за логіном і паролем — переходьте до наступних розділів.
## OAuth2 { #oauth2 }
## OAuth2
OAuth2 — це специфікація, що визначає кілька способів обробки автентифікації та авторизації.
OAuth2 — це специфікація, що описує кілька способів обробки автентифікації та авторизації.
Це досить об'ємна специфікація, яка охоплює кілька складних випадків використання.
Це досить об'ємна специфікація, яка охоплює складні випадки використання.
Вона включає способи автентифікації через «третю сторону».
Вона включає способи автентифікації через "третю сторону".
Саме це лежить в основі всіх систем із «увійти через Facebook, Google, X (Twitter), GitHub».
Саме це лежить в основі "входу через Google, Facebook, X (Twitter), GitHub" тощо.
### OAuth 1 { #oauth-1 }
### OAuth 1
Раніше існував OAuth 1, який значно відрізняється від OAuth2 і є складнішим, оскільки містив прямі специфікації щодо того, як шифрувати комунікацію.
Раніше існував OAuth 1, який значно відрізняється від OAuth2 і є складнішим, оскільки містив специфікації для шифрування комунікацій.
Зараз він не дуже популярний або використовується.
Зараз майже не використовується.
OAuth2 не вказує, як саме шифрувати комунікацію — він очікує, що ваш застосунок доступний через HTTPS.
OAuth2 не вказує, як саме шифрувати з'єднання — воно очікує, що ваш застосунок працює через HTTPS.
/// tip | Порада
У розділі про **деплой** ви побачите, як налаштувати HTTPS безкоштовно з Traefik та Let's Encrypt.
У розділі про **деплой** Ви побачите, як налаштувати HTTPS безкоштовно з Traefik та Let's Encrypt.
///
## OpenID Connect { #openid-connect }
## OpenID Connect
OpenID Connect — ще одна специфікація, побудована на основі **OAuth2**.
Вона просто розширює OAuth2, уточнюючи деякі відносно неоднозначні речі в OAuth2, щоб зробити його більш сумісним.
Вона розширює OAuth2, уточнюючи деякі неоднозначності для досягнення кращої сумісності.
Наприклад, вхід через Google використовує OpenID Connect (який під капотом використовує OAuth2).
Наприклад, вхід через Google використовує OpenID Connect (який базується на OAuth2).
Але вхід через Facebook не підтримує OpenID Connect. Він має власний різновид OAuth2.
Але вхід через Facebook — ні. Він має власну реалізацію на базі OAuth2.
### OpenID (не «OpenID Connect») { #openid-not-openid-connect }
### OpenID (не "OpenID Connect")
Існувала також специфікація «OpenID». Вона намагалася розвʼязати те саме, що й **OpenID Connect**, але не базувалась на OAuth2.
Існувала також специфікація "OpenID", яка намагалася розвʼязати ті самі задачі, що й **OpenID Connect**, але не базувалась на OAuth2.
Тож це була повністю додаткова система.
Це була зовсім інша система, і сьогодні вона майже не використовується.
Зараз вона не дуже популярна або використовується.
## OpenAPI
## OpenAPI { #openapi }
OpenAPI (раніше відомий як Swagger) — це відкрита специфікація для побудови API (тепер частина Linux Foundation).
OpenAPI (раніше Swagger) — це специфікація для побудови API (тепер під егідою Linux Foundation).
**FastAPI** базується на **OpenAPI**.
Саме це робить можливими кілька автоматичних інтерактивних інтерфейсів документації, генерацію коду тощо.
Завдяки цьому Ви отримуєте автоматичну інтерактивну документацію, генерацію коду та багато іншого.
OpenAPI має спосіб визначати різні «схеми» безпеки.
OpenAPI дозволяє описувати різні "схеми" безпеки.
Використовуючи їх, ви можете скористатися всіма цими інструментами, що базуються на стандартах, зокрема цими інтерактивними системами документації.
Використовуючи їх, Ви можете скористатися всіма цими інструментами, що базуються на стандартах, зокрема інтерактивними системами документації.
OpenAPI визначає такі схеми безпеки:
@@ -74,33 +72,33 @@ OpenAPI визначає такі схеми безпеки:
* Параметр запиту.
* Заголовок.
* Cookie.
* `http`: стандартні системи HTTP-автентифікації, включаючи:
* `bearer`: заголовок `Authorization` зі значенням `Bearer ` та токеном. Це успадковано з OAuth2.
* HTTP Basic автентифікацію.
* HTTP Digest тощо.
* `http`: стандартні методи HTTP-автентифікації, включаючи:
* `bearer`: заголовок `Authorization` зі значенням `Bearer` та токеном. Це успадковано з OAuth2.
* HTTP Basic автентифікація
* HTTP Digest, тощо.
* `oauth2`: усі способи обробки безпеки за допомогою OAuth2 (так звані «потоки»).
* Декілька з цих потоків підходять для створення провайдера автентифікації OAuth 2.0 (наприклад, Google, Facebook, X (Twitter), GitHub тощо):
* `implicit`
* `clientCredentials`
* `authorizationCode`
* Але є один окремий «потік», який можна ідеально використати для обробки автентифікації напряму в цьому ж застосунку:
* `password`: у кількох наступних розділах будуть приклади цього.
* `openIdConnect`: має спосіб визначити, як автоматично виявляти дані автентифікації OAuth2.
* Саме це автоматичне виявлення визначено у специфікації OpenID Connect.
* Деякі з цих потоків підходять для створення власного провайдера автентифікації OAuth 2.0 (наприклад, Google, Facebook, X (Twitter), GitHub тощо):
* `implicit`— неявний
* `clientCredentials`— облікові дані клієнта
* `authorizationCode` — код авторизації
* Але є один окремий «потік», який ідеально підходить для реалізації автентифікації всередині одного додатку:
* `password`: у наступних розділах буде приклад використання цього потоку.
* `openIdConnect`: дозволяє автоматично виявляти параметри автентифікації OAuth2.
* Це автоматичне виявлення визначається у специфікації OpenID Connect.
/// tip | Порада
Інтеграція інших провайдерів автентифікації/авторизації, таких як Google, Facebook, X (Twitter), GitHub тощо, також можлива і відносно проста.
Інтеграція інших провайдерів автентифікації/авторизації, таких як Google, Facebook, X (Twitter), GitHub тощо також можлива і відносно проста.
Найскладніше — це створити провайдера автентифікації/авторизації на кшталт таких, але **FastAPI** надає вам інструменти, щоб зробити це легко, виконуючи важку частину роботи за вас.
Найскладніше — це створити власного провайдера автентифікації/авторизації, як Google чи Facebook. Але **FastAPI** надає Вам інструменти, щоб зробити це легко, беручи на себе важку частину роботи.
///
## Утиліти **FastAPI** { #fastapi-utilities }
## Інструменти **FastAPI**
FastAPI надає кілька інструментів для кожної з описаних схем безпеки в модулі `fastapi.security`, які спрощують використання цих механізмів безпеки.
FastAPI надає кілька інструментів для кожної з описаних схем безпеки в модулі `fastapi.security`, які спрощують використання цих механізмів захисту.
У наступних розділах ви побачите, як додати безпеку до свого API за допомогою цих інструментів, які надає **FastAPI**.
У наступних розділах Ви побачите, як додати безпеку до свого API за допомогою цих інструментів **FastAPI**.
А також побачите, як це автоматично інтегрується в інтерактивну систему документації.
А також побачите, як вона автоматично інтегрується в інтерактивну документацію вашого API.

View File

@@ -1,13 +1,13 @@
# Статичні файли { #static-files }
# Статичні файли
Ви можете автоматично надавати статичні файли з каталогу, використовуючи `StaticFiles`.
## Використання `StaticFiles` { #use-staticfiles }
## Використання `StaticFiles`
* Імпортуйте `StaticFiles`.
* «Під'єднати» екземпляр `StaticFiles()` з вказанням необхідного шляху.
* "Під'єднати" екземпляр `StaticFiles()` з вказанням необхідного шляху.
{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *}
{* ../../docs_src/static_files/tutorial001.py hl[2,6] *}
/// note | Технічні деталі
@@ -17,24 +17,24 @@
///
### Що таке «Під'єднання» { #what-is-mounting }
### Що таке "Під'єднання"
«Під'єднання» означає додавання повноцінного «незалежного» застосунку за певним шляхом, який потім обробляє всі під шляхи.
"Під'єднання" означає додавання повноцінного "незалежного" застосунку за певним шляхом, який потім обробляє всі під шляхи.
Це відрізняється від використання `APIRouter`, оскільки під'єднаний застосунок є повністю незалежним. OpenAPI та документація вашого основного застосунку не будуть знати нічого про ваш під'єднаний застосунок тощо.
Це відрізняється від використання `APIRouter`, оскільки під'єднаний застосунок є повністю незалежним. OpenAPI та документація вашого основного застосунку не будуть знати нічого про ваш під'єднаний застосунок.
Ви можете дізнатися більше про це в [Посібнику для просунутих користувачів](../advanced/index.md){.internal-link target=_blank}.
## Деталі { #details }
## Деталі
Перше `"/static"` вказує на під шлях, за яким буде «під'єднано» цей новий «підзастосунок». Тому будь-який шлях, який починається з `"/static"`, буде оброблятися ним.
Перше `"/static"` вказує на під шлях, за яким буде "під'єднано" цей новий "застосунок". Тому будь-який шлях, який починається з `"/static"`, буде оброблятися ним.
`directory="static"` визначає назву каталогу, що містить ваші статичні файли.
`directory="static"` визначає каталог, що містить ваші статичні файли.
`name="static"` це ім'я, яке можна використовувати всередині **FastAPI**.
Усі ці параметри можуть бути іншими за "`static`", налаштуйте їх відповідно до потреб і особливостей вашого застосунку.
Усі ці параметри можуть бути змінені відповідно до потреб і особливостей вашого застосунку.
## Додаткова інформація { #more-info }
## Додаткова інформація
Детальніше про налаштування та можливості можна дізнатися в <a href="https://www.starlette.dev/staticfiles/" class="external-link" target="_blank">документації Starlette про статичні файли</a>.

View File

@@ -1,18 +1,17 @@
# Тестування { #testing }
# Тестування
Завдяки <a href="https://www.starlette.dev/testclient/" class="external-link" target="_blank">Starlette</a> тестувати застосунки **FastAPI** просто й приємно.
Тестування **FastAPI** додатків є простим та ефективним завдяки бібліотеці <a href="https://www.starlette.dev/testclient/" class="external-link" target="_blank">Starlette</a>, яка базується на <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>.
Оскільки HTTPX розроблений на основі Requests, його API є інтуїтивно зрозумілим для тих, хто вже знайомий з Requests.
Воно базується на <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>, який, своєю чергою, спроєктований на основі Requests, тож він дуже знайомий та інтуїтивно зрозумілий.
З його допомогою Ви можете використовувати <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a> безпосередньо з **FastAPI**.
З його допомогою ви можете використовувати <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a> безпосередньо з **FastAPI**.
## Використання `TestClient` { #using-testclient }
## Використання `TestClient`
/// info | Інформація
Щоб використовувати `TestClient`, спочатку встановіть <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a>.
Переконайтеся, що ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили `httpx`, наприклад:
Переконайтеся, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили саму бібліотеку, наприклад:
```console
$ pip install httpx
@@ -22,7 +21,7 @@ $ pip install httpx
Імпортуйте `TestClient`.
Створіть `TestClient`, передавши йому ваш застосунок **FastAPI**.
Створіть `TestClient`, передавши йому Ваш застосунок **FastAPI**.
Створюйте функції з іменами, що починаються з `test_` (це стандартна угода для `pytest`).
@@ -30,7 +29,8 @@ $ pip install httpx
Записуйте прості `assert`-вирази зі стандартними виразами Python, які потрібно перевірити (це також стандарт для `pytest`).
{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *}
{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *}
/// tip | Порада
@@ -46,25 +46,25 @@ $ pip install httpx
Ви також можете використовувати `from starlette.testclient import TestClient`.
**FastAPI** надає той самий `starlette.testclient` під назвою `fastapi.testclient` просто для зручності для вас, розробника. Але він безпосередньо походить із Starlette.
**FastAPI** надає той самий `starlette.testclient` під назвою `fastapi.testclient` для зручності розробників, але він безпосередньо походить із Starlette.
///
/// tip | Порада
Якщо ви хочете викликати `async`-функції у ваших тестах, окрім відправлення запитів до вашого застосунку FastAPI (наприклад, асинхронні функції роботи з базою даних), перегляньте [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} у розширеному керівництві.
Якщо Вам потрібно викликати `async`-функції у ваших тестах, окрім відправлення запитів до FastAPI-застосунку (наприклад, асинхронні функції роботи з базою даних), перегляньте [Асинхронні тести](../advanced/async-tests.md){.internal-link target=_blank} у розширеному керівництві.
///
## Розділення тестів { #separating-tests }
## Розділення тестів
У реальному застосунку ваші тести, ймовірно, будуть в окремому файлі.
У реальному застосунку Ваші тести, ймовірно, будуть в окремому файлі.
Також ваш застосунок **FastAPI** може складатися з кількох файлів/модулів тощо.
Також Ваш **FastAPI**-застосунок може складатися з кількох файлів або модулів тощо.
### Файл застосунку **FastAPI** { #fastapi-app-file }
### Файл застосунку **FastAPI**
Припустимо, у вас є структура файлів, описана в розділі [Bigger Applications](bigger-applications.md){.internal-link target=_blank}:
Припустимо, у Вас є структура файлів, описана в розділі [Більші застосунки](bigger-applications.md){.internal-link target=_blank}:
```
.
@@ -72,15 +72,14 @@ $ pip install httpx
│   ├── __init__.py
│   └── main.py
```
У файлі `main.py` знаходиться Ваш застосунок **FastAPI** :
У файлі `main.py` знаходиться ваш застосунок **FastAPI**:
{* ../../docs_src/app_testing/main.py *}
### Файл тестування
{* ../../docs_src/app_testing/app_a_py39/main.py *}
Ви можете створити файл `test_main.py` з Вашими тестами. Він може знаходитися в тому ж пакеті Python (у тій самій директорії з файлом `__init__.py`):
### Файл тестування { #testing-file }
Ви можете створити файл `test_main.py` з вашими тестами. Він може знаходитися в тому ж пакеті Python (у тій самій директорії з файлом `__init__.py`):
``` hl_lines="5"
.
@@ -90,18 +89,18 @@ $ pip install httpx
│   └── test_main.py
```
Оскільки цей файл знаходиться в тому ж пакеті, ви можете використовувати відносний імпорт, щоб імпортувати об'єкт `app` із модуля `main` (`main.py`):
Оскільки цей файл знаходиться в тому ж пакеті, Ви можете використовувати відносний імпорт, щоб імпортувати об'єкт `app` із модуля `main` (`main.py`):
{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *}
{* ../../docs_src/app_testing/test_main.py hl[3] *}
...і написати код для тестів так само як і раніше.
## Тестування: розширений приклад { #testing-extended-example }
## Тестування: розширений приклад
Тепер розширимо цей приклад і додамо більше деталей, щоб побачити, як тестувати різні частини.
### Розширений файл застосунку **FastAPI** { #extended-fastapi-app-file }
### Розширений файл застосунку **FastAPI**
Залишимо ту саму структуру файлів:
@@ -113,26 +112,75 @@ $ pip install httpx
│   └── test_main.py
```
Припустимо, що тепер файл `main.py` із вашим застосунком **FastAPI** містить інші **операції шляху**.
Припустимо, що тепер файл `main.py` із Вашим **FastAPI**-застосунком містить додаткові операції шляху (**path operations**).
Він має `GET`-операцію, яка може повертати помилку.
Він має `POST`-операцію, яка може повертати кілька помилок.
Обидві *операції шляху* вимагають заголовок `X-Token`.
Обидві операції шляху вимагають заголовок `X-Token`.
{* ../../docs_src/app_testing/app_b_an_py310/main.py *}
//// tab | Python 3.10+
### Розширений тестовий файл { #extended-testing-file }
```Python
{!> ../../docs_src/app_testing/app_b_an_py310/main.py!}
```
Потім ви можете оновити `test_main.py`, додавши розширені тести:
////
{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *}
//// tab | Python 3.9+
```Python
{!> ../../docs_src/app_testing/app_b_an_py39/main.py!}
```
Коли вам потрібно передати клієнту інформацію в запиті, але ви не знаєте, як це зробити, ви можете пошукати (Google), як це зробити в `httpx`, або навіть як це зробити з `requests`, оскільки дизайн HTTPX базується на дизайні Requests.
////
Далі ви просто повторюєте ці ж дії у ваших тестах.
//// tab | Python 3.8+
```Python
{!> ../../docs_src/app_testing/app_b_an/main.py!}
```
////
//// tab | Python 3.10+ non-Annotated
/// tip | Порада
Бажано використовувати версію з `Annotated`, якщо це можливо
///
```Python
{!> ../../docs_src/app_testing/app_b_py310/main.py!}
```
////
//// tab | Python 3.8+ non-Annotated
/// tip | Порада
Бажано використовувати версію з `Annotated`, якщо це можливо
///
```Python
{!> ../../docs_src/app_testing/app_b/main.py!}
```
////
### Розширений тестовий файл
Потім Ви можете оновити `test_main.py`, додавши розширені тести:
{* ../../docs_src/app_testing/app_b/test_main.py *}
Коли Вам потрібно передати клієнту інформацію в запиті, але Ви не знаєте, як це зробити, Ви можете пошукати (наприклад, у Google) спосіб реалізації в `httpx`, або навіть у `requests`, оскільки HTTPX розроблений на основі дизайну Requests.
Далі Ви просто повторюєте ці ж дії у ваших тестах.
Наприклад:
@@ -147,16 +195,15 @@ $ pip install httpx
/// info | Інформація
Зверніть увагу, що `TestClient` отримує дані, які можна конвертувати в JSON, а не Pydantic-моделі.
Якщо у вас є Pydantic-модель у тесті, і ви хочете передати її дані в застосунок під час тестування, ви можете використати `jsonable_encoder`, описаний у розділі [JSON Compatible Encoder](encoder.md){.internal-link target=_blank}.
Якщо у Вас є Pydantic-модель у тесті, і Ви хочете передати її дані в додаток під час тестування, Ви можете використати `jsonable_encoder`, описаний у розділі [JSON Compatible Encoder](encoder.md){.internal-link target=_blank}.
///
## Запуск { #run-it }
## Запуск тестів
Після цього вам потрібно встановити `pytest`.
Переконайтеся, що ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його і встановили необхідні пакети, наприклад:
Переконайтеся, що Ви створили [віртуальне середовище]{.internal-link target=_blank}, активували його і встановили необхідні пакети, наприклад:
<div class="termy">
@@ -168,7 +215,7 @@ $ pip install pytest
</div>
Він автоматично знайде файли та тести, виконає їх і повідомить вам результати.
`pytest` автоматично знайде файли з тестами, виконає їх і надасть вам результати.
Запустіть тести за допомогою:

View File

@@ -0,0 +1,729 @@
import re
from typing import TypedDict
CODE_INCLUDE_RE = re.compile(r"^\{\*\s*(\S+)\s*(.*)\*\}$")
CODE_INCLUDE_PLACEHOLDER = "<CODE_INCLUDE>"
HEADER_WITH_PERMALINK_RE = re.compile(r"^(#{1,6}) (.+?)(\s*\{\s*#.*\s*\})?\s*$")
HEADER_LINE_RE = re.compile(r"^(#{1,6}) (.+?)(?:\s*\{\s*(#.*)\s*\})?\s*$")
TIANGOLO_COM = "https://fastapi.tiangolo.com"
ASSETS_URL_PREFIXES = ("/img/", "/css/", "/js/")
MARKDOWN_LINK_RE = re.compile(
r"(?<!\\)(?<!\!)" # not an image ![...] and not escaped \[...]
r"\[(?P<text>.*?)\]" # link text (non-greedy)
r"\("
r"(?P<url>[^)\s]+)" # url (no spaces and `)`)
r'(?:\s+["\'](?P<title>.*?)["\'])?' # optional title in "" or ''
r"\)"
r"(?:\s*\{(?P<attrs>[^}]*)\})?" # optional attributes in {}
)
HTML_LINK_RE = re.compile(r"<a\s+[^>]*>.*?</a>")
HTML_LINK_TEXT_RE = re.compile(r"<a\b([^>]*)>(.*?)</a>")
HTML_LINK_OPEN_TAG_RE = re.compile(r"<a\b([^>]*)>")
HTML_ATTR_RE = re.compile(r'(\w+)\s*=\s*([\'"])(.*?)\2')
CODE_BLOCK_LANG_RE = re.compile(r"^`{3,4}([\w-]*)", re.MULTILINE)
SLASHES_COMMENT_RE = re.compile(
r"^(?P<code>.*?)(?P<comment>(?:(?<= )// .*)|(?:^// .*))?$"
)
HASH_COMMENT_RE = re.compile(r"^(?P<code>.*?)(?P<comment>(?:(?<= )# .*)|(?:^# .*))?$")
class CodeIncludeInfo(TypedDict):
line_no: int
line: str
class HeaderPermalinkInfo(TypedDict):
line_no: int
hashes: str
title: str
permalink: str
class MarkdownLinkInfo(TypedDict):
line_no: int
url: str
text: str
title: str | None
attributes: str | None
full_match: str
class HTMLLinkAttribute(TypedDict):
name: str
quote: str
value: str
class HtmlLinkInfo(TypedDict):
line_no: int
full_tag: str
attributes: list[HTMLLinkAttribute]
text: str
class MultilineCodeBlockInfo(TypedDict):
lang: str
start_line_no: int
content: list[str]
# Code includes
# -----------------------------------------------------------------------------------------
def extract_code_includes(lines: list[str]) -> list[CodeIncludeInfo]:
"""
Exctract lines that contain code includes.
Return list of CodeIncludeInfo namedtuples, where each tuple contains:
- `line_no` - line number (1-based)
- `line` - text of the line
"""
includes: list[CodeIncludeInfo] = []
for line_no, line in enumerate(lines, start=1):
if CODE_INCLUDE_RE.match(line):
includes.append(CodeIncludeInfo(line_no=line_no, line=line))
return includes
def replace_code_includes_with_placeholders(text: list[str]) -> list[str]:
"""
Replace code includes with placeholders.
"""
modified_text = text.copy()
includes = extract_code_includes(text)
for include in includes:
modified_text[include["line_no"] - 1] = CODE_INCLUDE_PLACEHOLDER
return modified_text
def replace_placeholders_with_code_includes(
text: list[str], original_includes: list[CodeIncludeInfo]
) -> list[str]:
"""
Replace code includes placeholders with actual code includes from the original (English) document.
Fail if the number of placeholders does not match the number of original includes.
"""
code_include_lines = [
line_no
for line_no, line in enumerate(text)
if line.strip() == CODE_INCLUDE_PLACEHOLDER
]
if len(code_include_lines) != len(original_includes):
raise ValueError(
"Number of code include placeholders does not match the number of code includes "
"in the original document "
f"({len(code_include_lines)} vs {len(original_includes)})"
)
modified_text = text.copy()
for i, line_no in enumerate(code_include_lines):
modified_text[line_no] = original_includes[i]["line"]
return modified_text
# Header permalinks
# -----------------------------------------------------------------------------------------
def extract_header_permalinks(lines: list[str]) -> list[HeaderPermalinkInfo]:
"""
Extract list of header permalinks from the given lines.
Return list of HeaderPermalinkInfo namedtuples, where each tuple contains:
- `line_no` - line number (1-based)
- `hashes` - string of hashes representing header level (e.g., "###")
- `permalink` - permalink string (e.g., "{#permalink}")
"""
headers: list[HeaderPermalinkInfo] = []
in_code_block3 = False
in_code_block4 = False
for line_no, line in enumerate(lines, start=1):
if not (in_code_block3 or in_code_block4):
if line.startswith("```"):
count = len(line) - len(line.lstrip("`"))
if count == 3:
in_code_block3 = True
continue
elif count >= 4:
in_code_block4 = True
continue
header_match = HEADER_WITH_PERMALINK_RE.match(line)
if header_match:
hashes, title, permalink = header_match.groups()
headers.append(
HeaderPermalinkInfo(
hashes=hashes, line_no=line_no, permalink=permalink, title=title
)
)
elif in_code_block3:
if line.startswith("```"):
count = len(line) - len(line.lstrip("`"))
if count == 3:
in_code_block3 = False
continue
elif in_code_block4:
if line.startswith("````"):
count = len(line) - len(line.lstrip("`"))
if count >= 4:
in_code_block4 = False
continue
return headers
def remove_header_permalinks(lines: list[str]) -> list[str]:
"""
Remove permalinks from headers in the given lines.
"""
modified_lines: list[str] = []
for line in lines:
header_match = HEADER_WITH_PERMALINK_RE.match(line)
if header_match:
hashes, title, _permalink = header_match.groups()
modified_line = f"{hashes} {title}"
modified_lines.append(modified_line)
else:
modified_lines.append(line)
return modified_lines
def replace_header_permalinks(
text: list[str],
header_permalinks: list[HeaderPermalinkInfo],
original_header_permalinks: list[HeaderPermalinkInfo],
) -> list[str]:
"""
Replace permalinks in the given text with the permalinks from the original document.
Fail if the number or level of headers does not match the original.
"""
modified_text: list[str] = text.copy()
if len(header_permalinks) != len(original_header_permalinks):
raise ValueError(
"Number of headers with permalinks does not match the number in the "
"original document "
f"({len(header_permalinks)} vs {len(original_header_permalinks)})"
)
for header_no in range(len(header_permalinks)):
header_info = header_permalinks[header_no]
original_header_info = original_header_permalinks[header_no]
if header_info["hashes"] != original_header_info["hashes"]:
raise ValueError(
"Header levels do not match between document and original document"
f" (found {header_info['hashes']}, expected {original_header_info['hashes']})"
f" for header №{header_no + 1} in line {header_info['line_no']}"
)
line_no = header_info["line_no"] - 1
hashes = header_info["hashes"]
title = header_info["title"]
permalink = original_header_info["permalink"]
modified_text[line_no] = f"{hashes} {title}{permalink}"
return modified_text
# Markdown links
# -----------------------------------------------------------------------------------------
def extract_markdown_links(lines: list[str]) -> list[tuple[str, int]]:
"""
Extract all markdown links from the given lines.
Return list of MarkdownLinkInfo namedtuples, where each tuple contains:
- `line_no` - line number (1-based)
- `url` - link URL
- `text` - link text
- `title` - link title (if any)
"""
links: list[MarkdownLinkInfo] = []
for line_no, line in enumerate(lines, start=1):
for m in MARKDOWN_LINK_RE.finditer(line):
links.append(
MarkdownLinkInfo(
line_no=line_no,
url=m.group("url"),
text=m.group("text"),
title=m.group("title"),
attributes=m.group("attrs"),
full_match=m.group(0),
)
)
return links
def _add_lang_code_to_url(url: str, lang_code: str) -> str:
if url.startswith(TIANGOLO_COM):
rel_url = url[len(TIANGOLO_COM) :]
if not rel_url.startswith(ASSETS_URL_PREFIXES):
url = url.replace(TIANGOLO_COM, f"{TIANGOLO_COM}/{lang_code}")
return url
def _construct_markdown_link(
url: str, text: str, title: str | None, attributes: str | None, lang_code: str
) -> str:
"""
Construct a markdown link, adjusting the URL for the given language code if needed.
"""
url = _add_lang_code_to_url(url, lang_code)
if title:
link = f'[{text}]({url} "{title}")'
else:
link = f"[{text}]({url})"
if attributes:
link += f"{{{attributes}}}"
return link
def replace_markdown_links(
text: list[str],
links: list[MarkdownLinkInfo],
original_links: list[MarkdownLinkInfo],
lang_code: str,
) -> list[str]:
"""
Replace markdown links in the given text with the original links.
Fail if the number of links does not match the original.
"""
if len(links) != len(original_links):
raise ValueError(
"Number of markdown links does not match the number in the "
"original document "
f"({len(links)} vs {len(original_links)})"
)
modified_text = text.copy()
for i, link_info in enumerate(links):
link_text = link_info["text"]
link_title = link_info["title"]
original_link_info = original_links[i]
# Replace
replacement_link = _construct_markdown_link(
url=original_link_info["url"],
text=link_text,
title=link_title,
attributes=original_link_info["attributes"],
lang_code=lang_code,
)
line_no = link_info["line_no"] - 1
modified_line = modified_text[line_no]
modified_line = modified_line.replace(
link_info["full_match"], replacement_link, 1
)
modified_text[line_no] = modified_line
return modified_text
# HTML links
# -----------------------------------------------------------------------------------------
def extract_html_links(lines: list[str]) -> list[HtmlLinkInfo]:
"""
Extract all HTML links from the given lines.
Return list of HtmlLinkInfo namedtuples, where each tuple contains:
- `line_no` - line number (1-based)
- `full_tag` - full HTML link tag
- `attributes` - list of HTMLLinkAttribute namedtuples (name, quote, value)
- `text` - link text
"""
links = []
for line_no, line in enumerate(lines, start=1):
for html_link in HTML_LINK_RE.finditer(line):
link_str = html_link.group(0)
link_text_match = HTML_LINK_TEXT_RE.match(link_str)
assert link_text_match is not None
link_text = link_text_match.group(2)
assert isinstance(link_text, str)
link_open_tag_match = HTML_LINK_OPEN_TAG_RE.match(link_str)
assert link_open_tag_match is not None
link_open_tag = link_open_tag_match.group(1)
assert isinstance(link_open_tag, str)
attributes: list[HTMLLinkAttribute] = []
for attr_name, attr_quote, attr_value in re.findall(
HTML_ATTR_RE, link_open_tag
):
assert isinstance(attr_name, str)
assert isinstance(attr_quote, str)
assert isinstance(attr_value, str)
attributes.append(
HTMLLinkAttribute(
name=attr_name, quote=attr_quote, value=attr_value
)
)
links.append(
HtmlLinkInfo(
line_no=line_no,
full_tag=link_str,
attributes=attributes,
text=link_text,
)
)
return links
def _construct_html_link(
link_text: str,
attributes: list[HTMLLinkAttribute],
lang_code: str,
) -> str:
"""
Reconstruct HTML link, adjusting the URL for the given language code if needed.
"""
attributes_upd: list[HTMLLinkAttribute] = []
for attribute in attributes:
if attribute["name"] == "href":
original_url = attribute["value"]
url = _add_lang_code_to_url(original_url, lang_code)
attributes_upd.append(
HTMLLinkAttribute(name="href", quote=attribute["quote"], value=url)
)
else:
attributes_upd.append(attribute)
attrs_str = " ".join(
f"{attribute['name']}={attribute['quote']}{attribute['value']}{attribute['quote']}"
for attribute in attributes_upd
)
return f"<a {attrs_str}>{link_text}</a>"
def replace_html_links(
text: list[str],
links: list[HtmlLinkInfo],
original_links: list[HtmlLinkInfo],
lang_code: str,
) -> list[str]:
"""
Replace HTML links in the given text with the links from the original document.
Adjust URLs for the given language code.
Fail if the number of links does not match the original.
"""
if len(links) != len(original_links):
raise ValueError(
"Number of HTML links does not match the number in the "
"original document "
f"({len(links)} vs {len(original_links)})"
)
modified_text = text.copy()
for link_index, link in enumerate(links):
original_link_info = original_links[link_index]
# Replace in the document text
replacement_link = _construct_html_link(
link_text=link["text"],
attributes=original_link_info["attributes"],
lang_code=lang_code,
)
line_no = link["line_no"] - 1
modified_text[line_no] = modified_text[line_no].replace(
link["full_tag"], replacement_link, 1
)
return modified_text
# Multiline code blocks
# -----------------------------------------------------------------------------------------
def get_code_block_lang(line: str) -> str:
match = CODE_BLOCK_LANG_RE.match(line)
if match:
return match.group(1)
return ""
def extract_multiline_code_blocks(text: list[str]) -> list[MultilineCodeBlockInfo]:
blocks: list[MultilineCodeBlockInfo] = []
in_code_block3 = False
in_code_block4 = False
current_block_lang = ""
current_block_start_line = -1
current_block_lines = []
for line_no, line in enumerate(text, start=1):
stripped = line.lstrip()
# --- Detect opening fence ---
if not (in_code_block3 or in_code_block4):
if stripped.startswith("```"):
current_block_start_line = line_no
count = len(stripped) - len(stripped.lstrip("`"))
if count == 3:
in_code_block3 = True
current_block_lang = get_code_block_lang(stripped)
current_block_lines = [line]
continue
elif count >= 4:
in_code_block4 = True
current_block_lang = get_code_block_lang(stripped)
current_block_lines = [line]
continue
# --- Detect closing fence ---
elif in_code_block3:
if stripped.startswith("```"):
count = len(stripped) - len(stripped.lstrip("`"))
if count == 3:
current_block_lines.append(line)
blocks.append(
MultilineCodeBlockInfo(
lang=current_block_lang,
start_line_no=current_block_start_line,
content=current_block_lines,
)
)
in_code_block3 = False
current_block_lang = ""
current_block_start_line = -1
current_block_lines = []
continue
current_block_lines.append(line)
elif in_code_block4:
if stripped.startswith("````"):
count = len(stripped) - len(stripped.lstrip("`"))
if count >= 4:
current_block_lines.append(line)
blocks.append(
MultilineCodeBlockInfo(
lang=current_block_lang,
start_line_no=current_block_start_line,
content=current_block_lines,
)
)
in_code_block4 = False
current_block_lang = ""
current_block_start_line = -1
current_block_lines = []
continue
current_block_lines.append(line)
return blocks
def _split_hash_comment(line: str) -> tuple[str, str | None]:
match = HASH_COMMENT_RE.match(line)
if match:
code = match.group("code").rstrip()
comment = match.group("comment")
return code, comment
return line.rstrip(), None
def _split_slashes_comment(line: str) -> tuple[str, str | None]:
match = SLASHES_COMMENT_RE.match(line)
if match:
code = match.group("code").rstrip()
comment = match.group("comment")
return code, comment
return line, None
def replace_multiline_code_block(
block_a: MultilineCodeBlockInfo, block_b: MultilineCodeBlockInfo
) -> list[str]:
"""
Replace multiline code block `a` with block `b` leaving comments intact.
Syntax of comments depends on the language of the code block.
Raises ValueError if the blocks are not compatible (different languages or different number of lines).
"""
start_line = block_a["start_line_no"]
end_line_no = start_line + len(block_a["content"]) - 1
if block_a["lang"] != block_b["lang"]:
raise ValueError(
f"Code block (lines {start_line}-{end_line_no}) "
"has different language than the original block "
f"('{block_a['lang']}' vs '{block_b['lang']}')"
)
if len(block_a["content"]) != len(block_b["content"]):
raise ValueError(
f"Code block (lines {start_line}-{end_line_no}) "
"has different number of lines than the original block "
f"({len(block_a['content'])} vs {len(block_b['content'])})"
)
block_language = block_a["lang"].lower()
if block_language in {"mermaid"}:
if block_a != block_b:
print(
f"Skipping mermaid code block replacement (lines {start_line}-{end_line_no}). "
"This should be checked manually."
)
return block_a["content"].copy() # We don't handle mermaid code blocks for now
code_block: list[str] = []
for line_a, line_b in zip(block_a["content"], block_b["content"]):
line_a_comment: str | None = None
line_b_comment: str | None = None
# Handle comments based on language
if block_language in {
"python",
"py",
"sh",
"bash",
"dockerfile",
"requirements",
"gitignore",
"toml",
"yaml",
"yml",
"hash-style-comments",
}:
_line_a_code, line_a_comment = _split_hash_comment(line_a)
_line_b_code, line_b_comment = _split_hash_comment(line_b)
res_line = line_b
if line_b_comment:
res_line = res_line.replace(line_b_comment, line_a_comment, 1)
code_block.append(res_line)
elif block_language in {"console", "json", "slash-style-comments"}:
_line_a_code, line_a_comment = _split_slashes_comment(line_a)
_line_b_code, line_b_comment = _split_slashes_comment(line_b)
res_line = line_b
if line_b_comment:
res_line = res_line.replace(line_b_comment, line_a_comment, 1)
code_block.append(res_line)
else:
code_block.append(line_b)
return code_block
def replace_multiline_code_blocks_in_text(
text: list[str],
code_blocks: list[MultilineCodeBlockInfo],
original_code_blocks: list[MultilineCodeBlockInfo],
) -> list[MultilineCodeBlockInfo]:
"""
Update each code block in `text` with the corresponding code block from
`original_code_blocks` with comments taken from `code_blocks`.
Raises ValueError if the number, language, or shape of code blocks do not match.
"""
if len(code_blocks) != len(original_code_blocks):
raise ValueError(
"Number of code blocks does not match the number in the original document "
f"({len(code_blocks)} vs {len(original_code_blocks)})"
)
modified_text = text.copy()
for block, original_block in zip(code_blocks, original_code_blocks):
updated_content = replace_multiline_code_block(block, original_block)
start_line_index = block["start_line_no"] - 1
for i, updated_line in enumerate(updated_content):
modified_text[start_line_index + i] = updated_line
return modified_text
# All checks
# -----------------------------------------------------------------------------------------
def check_translation(
doc_lines: list[str],
en_doc_lines: list[str],
lang_code: str,
auto_fix: bool,
path: str,
) -> list[str]:
# Fix code includes
en_code_includes = extract_code_includes(en_doc_lines)
doc_lines_with_placeholders = replace_code_includes_with_placeholders(doc_lines)
fixed_doc_lines = replace_placeholders_with_code_includes(
doc_lines_with_placeholders, en_code_includes
)
if auto_fix and (fixed_doc_lines != doc_lines):
print(f"Fixing code includes in: {path}")
doc_lines = fixed_doc_lines
# Fix permalinks
en_permalinks = extract_header_permalinks(en_doc_lines)
doc_permalinks = extract_header_permalinks(doc_lines)
fixed_doc_lines = replace_header_permalinks(
doc_lines, doc_permalinks, en_permalinks
)
if auto_fix and (fixed_doc_lines != doc_lines):
print(f"Fixing header permalinks in: {path}")
doc_lines = fixed_doc_lines
# Fix markdown links
en_markdown_links = extract_markdown_links(en_doc_lines)
doc_markdown_links = extract_markdown_links(doc_lines)
fixed_doc_lines = replace_markdown_links(
doc_lines, doc_markdown_links, en_markdown_links, lang_code
)
if auto_fix and (fixed_doc_lines != doc_lines):
print(f"Fixing markdown links in: {path}")
doc_lines = fixed_doc_lines
# Fix HTML links
en_html_links = extract_html_links(en_doc_lines)
doc_html_links = extract_html_links(doc_lines)
fixed_doc_lines = replace_html_links(
doc_lines, doc_html_links, en_html_links, lang_code
)
if auto_fix and (fixed_doc_lines != doc_lines):
print(f"Fixing HTML links in: {path}")
doc_lines = fixed_doc_lines
# Fix multiline code blocks
en_code_blocks = extract_multiline_code_blocks(en_doc_lines)
doc_code_blocks = extract_multiline_code_blocks(doc_lines)
fixed_doc_lines = replace_multiline_code_blocks_in_text(
doc_lines, doc_code_blocks, en_code_blocks
)
if auto_fix and (fixed_doc_lines != doc_lines):
print(f"Fixing multiline code blocks in: {path}")
doc_lines = fixed_doc_lines
return doc_lines

View File

@@ -25,7 +25,6 @@ SUPPORTED_LANGS = {
"es",
"pt",
"ru",
"uk",
}

View File

@@ -0,0 +1,32 @@
import shutil
from pathlib import Path
import pytest
from typer.testing import CliRunner
@pytest.fixture(name="runner")
def get_runner():
runner = CliRunner()
with runner.isolated_filesystem():
yield runner
@pytest.fixture(name="root_dir")
def prepare_paths(runner):
docs_dir = Path("docs")
en_docs_dir = docs_dir / "en" / "docs"
lang_docs_dir = docs_dir / "lang" / "docs"
en_docs_dir.mkdir(parents=True, exist_ok=True)
lang_docs_dir.mkdir(parents=True, exist_ok=True)
yield Path.cwd()
@pytest.fixture
def copy_test_files(root_dir: Path, request: pytest.FixtureRequest):
en_file_path = Path(request.param[0])
translation_file_path = Path(request.param[1])
shutil.copy(str(en_file_path), str(root_dir / "docs" / "en" / "docs" / "doc.md"))
shutil.copy(
str(translation_file_path), str(root_dir / "docs" / "lang" / "docs" / "doc.md")
)

View File

@@ -0,0 +1,44 @@
# Code blocks { #code-blocks }
Some text
```python
# This is a sample Python code block
def hello_world():
# Comment with indentation
print("Hello, world!") # Print greeting
```
Some more text
```toml
# This is a sample TOML code block
title = "TOML Example" # Title of the document
```
And more text
```console
// Use the command "live" and pass the language code as a CLI argument
$ python ./scripts/docs.py live es
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008
<span style="color: green;">[INFO]</span> Start watching changes
<span style="color: green;">[INFO]</span> Start detecting changes
```
And even more text
```json
{
// This is a sample JSON code block
"greeting": "Hello, world!" // Greeting
}
```
Mermaid diagram
```mermaid
flowchart LR
stone(philosophers-stone) -->|requires| harry-1[harry v1]
```

View File

@@ -0,0 +1,45 @@
# Code blocks { #code-blocks }
Some text
```python
# This is a sample Python code block
def hello_world():
# Comment with indentation
print("Hello, world!") # Print greeting
```
Some more text
```toml
# Extra line
# This is a sample TOML code block
title = "TOML Example" # Title of the document
```
And more text
```console
// Use the command "live" and pass the language code as a CLI argument
$ python ./scripts/docs.py live es
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008
<span style="color: green;">[INFO]</span> Start watching changes
<span style="color: green;">[INFO]</span> Start detecting changes
```
And even more text
```json
{
// This is a sample JSON code block
"greeting": "Hello, world!" // Greeting
}
```
Диаграма Mermaid
```mermaid
flowchart LR
stone(philosophers-stone) -->|requires| harry-1[harry v1]
```

View File

@@ -0,0 +1,45 @@
# Code blocks { #code-blocks }
Some text
```python
# This is a sample Python code block
def hello_world():
# Comment with indentation
print("Hello, world!") # Print greeting
```
Some more text
The following block is missing first line:
```toml
title = "TOML Example" # Title of the document
```
And more text
```console
// Use the command "live" and pass the language code as a CLI argument
$ python ./scripts/docs.py live es
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008
<span style="color: green;">[INFO]</span> Start watching changes
<span style="color: green;">[INFO]</span> Start detecting changes
```
And even more text
```json
{
// This is a sample JSON code block
"greeting": "Hello, world!" // Greeting
}
```
Диаграма Mermaid
```mermaid
flowchart LR
stone(philosophers-stone) -->|requires| harry-1[harry v1]
```

View File

@@ -0,0 +1,44 @@
# Code blocks { #code-blocks }
Some text
```python
# This is a sample Python code block
def hello_world():
# Comment with indentation
print("Hello, world!") # Print greeting
```
Some more text
```toml
# This is a sample TOML code block
title = "TOML Example" # Title of the document
```
And more text
```console
// Use the command "live" and pass the language code as a CLI argument
$ python ./scripts/docs.py live es
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008
<span style="color: green;">[INFO]</span> Start watching changes
<span style="color: green;">[INFO]</span> Start detecting changes
```
And even more text
```json
{
// This is a sample JSON code block
"greeting": "Hello, world!" // Greeting
}
```
Диаграма Mermaid
```mermaid
flowchart LR
stone(philosophers-stone) -->|requires| harry-1[harry v1]
```

View File

@@ -0,0 +1,44 @@
# Code blocks { #code-blocks }
Some text
```python
# This is a sample Python code block
def hello_world():
# Comment with indentation
print("Hello, world!") # Print greeting
```
Some more text
```toml
# This is a sample TOML code block
title = "TOML Example" # Title of the document
```
And more text
```console
// Use the command "live" and pass the language code as a CLI argument
$ python ./scripts/docs.py live es
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008
<span style="color: green;">[INFO]</span> Start watching changes
<span style="color: green;">[INFO]</span> Start detecting changes
```
And even more text
```json
{
// This is a sample JSON code block
"greeting": "Hello, world!" // Greeting
}
```
Диаграма Mermaid
```mermaid
flowchart LR
stone(philosophers-stone) -->|требует| harry-1[harry v1]
```

View File

@@ -0,0 +1,50 @@
# Code blocks { #code-blocks }
Some text
```python
# This is a sample Python code block
def hello_world():
# Comment with indentation
print("Hello, world!") # Print greeting
```
Some more text
```toml
# This is a sample TOML code block
title = "TOML Example" # Title of the document
```
Extra code block
```
$ cd my_project
```
And more text
```console
// Use the command "live" and pass the language code as a CLI argument
$ python ./scripts/docs.py live es
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008
<span style="color: green;">[INFO]</span> Start watching changes
<span style="color: green;">[INFO]</span> Start detecting changes
```
And even more text
```json
{
// This is a sample JSON code block
"greeting": "Hello, world!" // Greeting
}
```
Диаграма Mermaid
```mermaid
flowchart LR
stone(philosophers-stone) -->|requires| harry-1[harry v1]
```

View File

@@ -0,0 +1,41 @@
# Code blocks { #code-blocks }
Some text
```python
# This is a sample Python code block
def hello_world():
# Comment with indentation
print("Hello, world!") # Print greeting
```
Some more text
Missing code block...
And more text
```console
// Use the command "live" and pass the language code as a CLI argument
$ python ./scripts/docs.py live es
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008
<span style="color: green;">[INFO]</span> Start watching changes
<span style="color: green;">[INFO]</span> Start detecting changes
```
And even more text
```json
{
// This is a sample JSON code block
"greeting": "Hello, world!" // Greeting
}
```
Диаграма Mermaid
```mermaid
flowchart LR
stone(philosophers-stone) -->|requires| harry-1[harry v1]
```

View File

@@ -0,0 +1,46 @@
# Code blocks { #code-blocks }
Some text
```python
# This is a sample Python code block
def hello_world():
# Comment with indentation
print("Hello, world!") # Print greeting
```
Some more text
The following block has wrong language code (should be TOML):
```yaml
# This is a sample TOML code block
title = "TOML Example" # Title of the document
```
And more text
```console
// Use the command "live" and pass the language code as a CLI argument
$ python ./scripts/docs.py live es
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008
<span style="color: green;">[INFO]</span> Start watching changes
<span style="color: green;">[INFO]</span> Start detecting changes
```
And even more text
```json
{
// This is a sample JSON code block
"greeting": "Hello, world!" // Greeting
}
```
Диаграма Mermaid
```mermaid
flowchart LR
stone(philosophers-stone) -->|requires| harry-1[harry v1]
```

View File

@@ -0,0 +1,46 @@
# Code blocks { #code-blocks }
Some text
```python
# This is a sample Python code block
def hello_world():
# Comment with indentation
print("Hello, world!") # Print greeting
```
Some more text
The following block has wrong language code (should be TOML):
```
# This is a sample TOML code block
title = "TOML Example" # Title of the document
```
And more text
```console
// Use the command "live" and pass the language code as a CLI argument
$ python ./scripts/docs.py live es
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008
<span style="color: green;">[INFO]</span> Start watching changes
<span style="color: green;">[INFO]</span> Start detecting changes
```
And even more text
```json
{
// This is a sample JSON code block
"greeting": "Hello, world!" // Greeting
}
```
Диаграма Mermaid
```mermaid
flowchart LR
stone(philosophers-stone) -->|requires| harry-1[harry v1]
```

View File

@@ -0,0 +1,58 @@
from pathlib import Path
import pytest
from typer.testing import CliRunner
from scripts.translation_fixer import cli
data_path = Path(
"scripts/tests/test_translation_fixer/test_code_blocks/data"
).absolute()
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_lines_number_gt.md")],
indirect=True,
)
def test_gt(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
assert result.exit_code == 1, result.output
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(
f"{data_path}/translated_doc_lines_number_gt.md"
).read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert "Error processing docs/lang/docs/doc.md" in result.output
assert (
"Code block (lines 14-18) has different number of lines than the original block (5 vs 4)"
) in result.output
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_lines_number_lt.md")],
indirect=True,
)
def test_lt(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
# assert result.exit_code == 1, result.output
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(
f"{data_path}/translated_doc_lines_number_lt.md"
).read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert "Error processing docs/lang/docs/doc.md" in result.output
assert (
"Code block (lines 16-18) has different number of lines than the original block (3 vs 4)"
) in result.output

View File

@@ -0,0 +1,59 @@
from pathlib import Path
import pytest
from typer.testing import CliRunner
from scripts.translation_fixer import cli
data_path = Path(
"scripts/tests/test_translation_fixer/test_code_blocks/data"
).absolute()
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_mermaid_translated.md")],
indirect=True,
)
def test_translated(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
assert result.exit_code == 0, result.output
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(
f"{data_path}/translated_doc_mermaid_translated.md"
).read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert (
"Skipping mermaid code block replacement (lines 41-44). This should be checked manually."
) in result.output
@pytest.mark.parametrize(
"copy_test_files",
[
(
f"{data_path}/en_doc.md",
f"{data_path}/translated_doc_mermaid_not_translated.md",
)
],
indirect=True,
)
def test_not_translated(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
assert result.exit_code == 0, result.output
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(
f"{data_path}/translated_doc_mermaid_not_translated.md"
).read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert ("Skipping mermaid code block replacement") not in result.output

View File

@@ -0,0 +1,56 @@
from pathlib import Path
import pytest
from typer.testing import CliRunner
from scripts.translation_fixer import cli
data_path = Path(
"scripts/tests/test_translation_fixer/test_code_blocks/data"
).absolute()
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_gt.md")],
indirect=True,
)
def test_gt(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
assert result.exit_code == 1, result.output
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert "Error processing docs/lang/docs/doc.md" in result.output
assert (
"Number of code blocks does not match the number "
"in the original document (6 vs 5)"
) in result.output
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_lt.md")],
indirect=True,
)
def test_lt(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
# assert result.exit_code == 1, result.output
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert "Error processing docs/lang/docs/doc.md" in result.output
assert (
"Number of code blocks does not match the number "
"in the original document (4 vs 5)"
) in result.output

View File

@@ -0,0 +1,58 @@
from pathlib import Path
import pytest
from typer.testing import CliRunner
from scripts.translation_fixer import cli
data_path = Path(
"scripts/tests/test_translation_fixer/test_code_blocks/data"
).absolute()
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_wrong_lang_code.md")],
indirect=True,
)
def test_wrong_lang_code_1(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
assert result.exit_code == 1, result.output
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(
f"{data_path}/translated_doc_wrong_lang_code.md"
).read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert "Error processing docs/lang/docs/doc.md" in result.output
assert (
"Code block (lines 16-19) has different language than the original block ('yaml' vs 'toml')"
) in result.output
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_wrong_lang_code_2.md")],
indirect=True,
)
def test_wrong_lang_code_2(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
assert result.exit_code == 1, result.output
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(
f"{data_path}/translated_doc_wrong_lang_code_2.md"
).read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert "Error processing docs/lang/docs/doc.md" in result.output
assert (
"Code block (lines 16-19) has different language than the original block ('' vs 'toml')"
) in result.output

View File

@@ -0,0 +1,13 @@
# Header
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
Some text
{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
Some more text
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
And even more text

View File

@@ -0,0 +1,15 @@
# Header
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
Some text
{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
Some more text
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
And even more text
{* ../../docs_src/python_types/tutorial001_py39.py *}

View File

@@ -0,0 +1,13 @@
# Header
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
Some text
{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
Some more text
...
And even more text

View File

@@ -0,0 +1,56 @@
from pathlib import Path
import pytest
from typer.testing import CliRunner
from scripts.translation_fixer import cli
data_path = Path(
"scripts/tests/test_translation_fixer/test_code_includes/data"
).absolute()
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_gt.md")],
indirect=True,
)
def test_gt(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
assert result.exit_code == 1
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert "Error processing docs/lang/docs/doc.md" in result.output
assert (
"Number of code include placeholders does not match the number of code includes "
"in the original document (4 vs 3)"
) in result.output
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_lt.md")],
indirect=True,
)
def test_lt(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
assert result.exit_code == 1
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert "Error processing docs/lang/docs/doc.md" in result.output
assert (
"Number of code include placeholders does not match the number of code includes "
"in the original document (2 vs 3)"
) in result.output

View File

@@ -0,0 +1,244 @@
# Test translation fixer tool { #test-translation-fixer }
## Code blocks with and without comments { #code-blocks-with-and-without-comments }
This is a test page for the translation fixer tool.
### Code blocks with comments { #code-blocks-with-comments }
The following code blocks include comments in different styles.
Fixer tool should fix content, but preserve comments correctly.
```python
# This is a sample Python code block
def hello_world():
# Comment with indentation
print("Hello, world!") # Print greeting
```
```toml
# This is a sample TOML code block
title = "TOML Example" # Title of the document
```
```console
// Use the command "live" and pass the language code as a CLI argument
$ python ./scripts/docs.py live es
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008
<span style="color: green;">[INFO]</span> Start watching changes
<span style="color: green;">[INFO]</span> Start detecting changes
```
```json
{
// This is a sample JSON code block
"greeting": "Hello, world!" // Greeting
}
```
### Code blocks with comments where language uses different comment styles { #code-blocks-with-different-comment-styles }
The following code blocks include comments in different styles based on the language.
Fixer tool will not preserve comments in these blocks.
```json
{
# This is a sample JSON code block
"greeting": "Hello, world!" # Print greeting
}
```
```console
# This is a sample console code block
$ echo "Hello, world!" # Print greeting
```
```toml
// This is a sample TOML code block
title = "TOML Example" // Title of the document
```
### Code blocks with comments with unsupported languages or without language specified { #code-blocks-with-unsupported-languages }
The following code blocks use unsupported languages for comment preservation.
Fixer tool will not preserve comments in these blocks.
```javascript
// This is a sample JavaScript code block
console.log("Hello, world!"); // Print greeting
```
```
# This is a sample console code block
$ echo "Hello, world!" # Print greeting
```
```
// This is a sample console code block
$ echo "Hello, world!" // Print greeting
```
### Code blocks with comments that don't follow pattern { #code-blocks-with-comments-without-pattern }
Fixer tool expects comments that follow specific pattern:
- For hash-style comments: comment starts with `# ` (hash following by whitespace) in the beginning of the string or after a whitespace.
- For slash-style comments: comment starts with `// ` (two slashes following by whitespace) in the beginning of the string or after a whitespace.
If comment doesn't follow this pattern, fixer tool will not preserve it.
```python
#Function declaration
def hello_world():# Print greeting
print("Hello, world!") #Print greeting without space after hash
```
```console
//Function declaration
def hello_world():// Print greeting
print("Hello, world!") //Print greeting without space after slashes
```
## Code blocks with quadruple backticks { #code-blocks-with-quadruple-backticks }
The following code block uses quadruple backticks.
````python
# Hello world function
def hello_world():
print("Hello, world!") # Print greeting
````
### Backticks number mismatch is fixable { #backticks-number-mismatch-is-fixable }
The following code block has triple backticks in the original document, but quadruple backticks in the translated document.
It will be fixed by the fixer tool (will convert to triple backticks).
```Python
# Some Python code
```
### Triple backticks inside quadruple backticks { #triple-backticks-inside-quadruple-backticks }
Comments inside nested code block will NOT be preserved.
````
Here is a code block with quadruple backticks that contains triple backticks inside:
```python
# This is a sample Python code block
def hello_world():
print("Hello, world!") # Print greeting
```
````
# Code includes { #code-includes }
## Simple code includes { #simple-code-includes }
{* ../../docs_src/python_types/tutorial001_py39.py *}
{* ../../docs_src/python_types/tutorial002_py39.py *}
## Code includes with highlighting { #code-includes-with-highlighting }
{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}
{* ../../docs_src/python_types/tutorial006_py39.py hl[10] *}
## Code includes with line ranges { #code-includes-with-line-ranges }
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] *}
## Code includes with line ranges and highlighting { #code-includes-with-line-ranges-and-highlighting }
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
{* ../../docs_src/dependencies/tutorial015_an_py310.py ln[10:15] hl[12:14] *}
## Code includes qith title { #code-includes-with-title }
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
## Code includes with unknown attributes { #code-includes-with-unknown-attributes }
{* ../../docs_src/python_types/tutorial001_py39.py unknown[123] *}
## Some more code includes to test fixing { #some-more-code-includes-to-test-fixing }
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
# Links { #links }
## Markdown-style links { #markdown-style-links }
This is a [Markdown link](https://example.com) to an external site.
This is a link with attributes: [**FastAPI** Project Generators](project-generation.md){.internal-link target=_blank}
This is a link to the main FastAPI site: [FastAPI](https://fastapi.tiangolo.com) - tool should add language code to the URL.
This is a link to one of the pages on FastAPI site: [How to](https://fastapi.tiangolo.com/how-to/) - tool should add language code to the URL.
Link to test wrong attribute: [**FastAPI** Project Generators](project-generation.md){.internal-link} - tool should fix the attribute.
Link with a title: [Example](https://example.com "Example site") - URL will be fixed, title preserved.
### Markdown link to static assets { #markdown-link-to-static-assets }
These are links to static assets:
* [FastAPI Logo](https://fastapi.tiangolo.com/img/fastapi-logo.png)
* [FastAPI CSS](https://fastapi.tiangolo.com/css/fastapi.css)
* [FastAPI JS](https://fastapi.tiangolo.com/js/fastapi.js)
Tool should NOT add language code to their URLs.
## HTML-style links { #html-style-links }
This is an <a href="https://example.com" target="_blank" class="external-link">HTML link</a> to an external site.
This is an <a href="https://fastapi.tiangolo.com">link to the main FastAPI site</a> - tool should add language code to the URL.
This is an <a href="https://fastapi.tiangolo.com/how-to/">link to one of the pages on FastAPI site</a> - tool should add language code to the URL.
Link to test wrong attribute: <a href="project-generation.md" class="internal-link">**FastAPI** Project Generators</a> - tool should fix the attribute.
### HTML links to static assets { #html-links-to-static-assets }
These are links to static assets:
* <a href="https://fastapi.tiangolo.com/img/fastapi-logo.png">FastAPI Logo</a>
* <a href="https://fastapi.tiangolo.com/css/fastapi.css">FastAPI CSS</a>
* <a href="https://fastapi.tiangolo.com/js/fastapi.js">FastAPI JS</a>
Tool should NOT add language code to their URLs.
# Header (with HTML link to <a href="https://tiangolo.com">tiangolo.com</a>) { #header-with-html-link-to-tiangolo-com }
#Not a header
```Python
# Also not a header
```
Some text

View File

@@ -0,0 +1,240 @@
# Тестовый инструмент исправления переводов { #test-translation-fixer }
## Блоки кода с комментариями и без комментариев { #code-blocks-with-and-without-comments }
Это тестовая страница для инструмента исправления переводов.
### Блоки кода с комментариями { #code-blocks-with-comments }
Следующие блоки кода содержат комментарии в разных стилях.
Инструмент исправления должен исправлять содержимое, но корректно сохранять комментарии.
```python
# Это пример блока кода на Python
def hello_world():
# Комментарий с отступом
print("Hello, world!") # Печать приветствия
```
```toml
# Это пример блока кода на TOML
title = "TOML Example" # Заголовок документа
```
```console
// Используйте команду "live" и передайте код языка в качестве аргумента CLI
$ python ./scripts/docs.py live es
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008
<span style="color: green;">[INFO]</span> Start watching changes
<span style="color: green;">[INFO]</span> Start detecting changes
```
```json
{
// Это пример блока кода на JSON
"greeting": "Hello, world!" // Печать приветствия
}
```
### Блоки кода с комментариями, где язык использует другие стили комментариев { #code-blocks-with-different-comment-styles }
Следующие блоки кода содержат комментарии в разных стилях в зависимости от языка.
Инструмент исправления не будет сохранять комментарии в этих блоках.
```json
{
# Это пример блока кода на JSON
"greeting": "Hello, world!" # Печать приветствия
}
```
```console
# Это пример блока кода консоли
$ echo "Hello, world!" # Печать приветствия
```
```toml
// Это пример блока кода на TOML
title = "TOML Example" // Заголовок документа
```
### Блоки кода с комментариями на неподдерживаемых языках или без указания языка { #code-blocks-with-unsupported-languages }
Следующие блоки кода используют неподдерживаемые языки для сохранения комментариев.
Инструмент исправления не будет сохранять комментарии в этих блоках.
```javascript
// Это пример блока кода на JavaScript
console.log("Hello, world!"); // Печать приветствия
```
```
# Это пример блока кода консоли
$ echo "Hello, world!" # Печать приветствия
```
```
// Это пример блока кода консоли
$ echo "Hello, world!" // Печать приветствия
```
### Блоки кода с комментариями, которые не соответствуют шаблону { #code-blocks-with-comments-without-pattern }
Инструмент исправления ожидает комментарии, которые соответствуют определённому шаблону:
- Для комментариев в стиле с решёткой: комментарий начинается с `# ` (решётка, затем пробел) в начале строки или после пробела.
- Для комментариев в стиле со слешами: комментарий начинается с `// ` (два слеша, затем пробел) в начале строки или после пробела.
Если комментарий не соответствует этому шаблону, инструмент исправления не будет его сохранять.
```python
#Объявление функции
def hello_world():# Печать приветствия
print("Hello, world!") #Печать приветствия без пробела после решётки
```
```console
//Объявление функции
def hello_world():// Печать приветствия
print("Hello, world!") //Печать приветствия без пробела после слешей
```
## Блок кода с четырёхкратными обратными кавычками { #code-blocks-with-quadruple-backticks }
Следующий блок кода содержит четырёхкратные обратные кавычки.
````python
# Функция приветствия
def hello_world():
print("Hello, world") # Печать приветствия
````
### Несоответствие обратных кавычек фиксится { #backticks-number-mismatch-is-fixable }
Следующий блок кода имеет тройные обратные кавычки в оригинальном документе, но четырёхкратные обратные кавычки в переведённом документе.
Это будет исправлено инструментом исправления (будет преобразовано в тройные обратные кавычки).
````Python
# Немного кода на Python
````
### Блок кода в тройных обратных кавычка внутри блока кода в четырёхкратных обратных кавычках { #triple-backticks-inside-quadruple-backticks }
Комментарии внутри вложенного блока кода в тройных обратных кавычках НЕ БУДУТ сохранены.
````
Here is a code block with quadruple backticks that contains triple backticks inside:
```python
# Этот комментарий НЕ будет сохранён
def hello_world():
print("Hello, world") # Как и этот комментарий
```
````
# Включения кода { #code-includes }
## Простые включения кода { #simple-code-includes }
{* ../../docs_src/python_types/tutorial001_py39.py *}
{* ../../docs_src/python_types/tutorial002_py39.py *}
## Включения кода с подсветкой { #code-includes-with-highlighting }
{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}
{* ../../docs_src/python_types/tutorial006_py39.py hl[10] *}
## Включения кода с диапазонами строк { #code-includes-with-line-ranges }
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] *}
## Включения кода с диапазонами строк и подсветкой { #code-includes-with-line-ranges-and-highlighting }
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
{* ../../docs_src/dependencies/tutorial015_an_py310.py ln[10:15] hl[12:14] *}
## Включения кода с заголовком { #code-includes-with-title }
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
## Включения кода с неизвестными атрибутами { #code-includes-with-unknown-attributes }
{* ../../docs_src/python_types/tutorial001_py39.py unknown[123] *}
## Ещё включения кода для тестирования исправления { #some-more-code-includes-to-test-fixing }
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19 : 21] *}
{* ../../docs_src/bigger_applications/app_an_py39/wrong.py hl[3] title["app/internal/admin.py"] *}
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[1:30] hl[1:10] *}
# Ссылки { #links }
## Ссылки в стиле Markdown { #markdown-style-links }
Это [Markdown-ссылка](https://example.com) на внешний сайт.
Это ссылка с атрибутами: [**FastAPI** генераторы проектов](project-generation.md){.internal-link target=_blank}
Это ссылка на основной сайт FastAPI: [FastAPI](https://fastapi.tiangolo.com) — инструмент должен добавить код языка в URL.
Это ссылка на одну из страниц на сайте FastAPI: [How to](https://fastapi.tiangolo.com/how-to) — инструмент должен добавить код языка в URL.
Ссылка для тестирования неправильного атрибута: [**FastAPI** генераторы проектов](project-generation.md){.external-link} - инструмент должен исправить атрибут.
Ссылка с заголовком: [Пример](http://example.com/ "Сайт для примера") - URL будет исправлен инструментом, заголовок сохранится.
### Markdown ссылки на статические ресурсы { #markdown-link-to-static-assets }
Это ссылки на статические ресурсы:
* [FastAPI Logo](https://fastapi.tiangolo.com/img/fastapi-logo.png)
* [FastAPI CSS](https://fastapi.tiangolo.com/css/fastapi.css)
* [FastAPI JS](https://fastapi.tiangolo.com/js/fastapi.js)
Инструмент НЕ должен добавлять код языка в их URL.
## Ссылки в стиле HTML { #html-style-links }
Это <a href="https://example.com" target="_blank" class="external-link">HTML-ссылка</a> на внешний сайт.
Это <a href="https://fastapi.tiangolo.com">ссылка на основной сайт FastAPI</a> — инструмент должен добавить код языка в URL.
Это <a href="https://fastapi.tiangolo.com/how-to/">ссылка на одну из страниц на сайте FastAPI</a> — инструмент должен добавить код языка в URL.
Ссылка для тестирования неправильного атрибута: <a href="project-generation.md" class="external-link">**FastAPI** генераторы проектов</a> - инструмент должен исправить атрибут.
### HTML ссылки на статические ресурсы { #html-links-to-static-assets }
Это ссылки на статические ресурсы:
* <a href="https://fastapi.tiangolo.com/img/fastapi-logo.png">FastAPI Logo</a>
* <a href="https://fastapi.tiangolo.com/css/fastapi.css">FastAPI CSS</a>
* <a href="https://fastapi.tiangolo.com/js/fastapi.js">FastAPI JS</a>
Инструмент НЕ должен добавлять код языка в их URL.
# Заголовок (с HTML ссылкой на <a href="https://tiangolo.com">tiangolo.com</a>) { #header-5 }
#Не заголовок
```Python
# Также не заголовок
```
Немного текста

View File

@@ -0,0 +1,240 @@
# Тестовый инструмент исправления переводов { #test-translation-fixer }
## Блоки кода с комментариями и без комментариев { #code-blocks-with-and-without-comments }
Это тестовая страница для инструмента исправления переводов.
### Блоки кода с комментариями { #code-blocks-with-comments }
Следующие блоки кода содержат комментарии в разных стилях.
Инструмент исправления должен исправлять содержимое, но корректно сохранять комментарии.
```python
# Это пример блока кода на Python
def hello_world():
# Комментарий с отступом
print("Hello, world!") # Печать приветствия
```
```toml
# Это пример блока кода на TOML
title = "TOML Example" # Заголовок документа
```
```console
// Используйте команду "live" и передайте код языка в качестве аргумента CLI
$ python ./scripts/docs.py live es
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008
<span style="color: green;">[INFO]</span> Start watching changes
<span style="color: green;">[INFO]</span> Start detecting changes
```
```json
{
// Это пример блока кода на JSON
"greeting": "Hello, world!" // Печать приветствия
}
```
### Блоки кода с комментариями, где язык использует другие стили комментариев { #code-blocks-with-different-comment-styles }
Следующие блоки кода содержат комментарии в разных стилях в зависимости от языка.
Инструмент исправления не будет сохранять комментарии в этих блоках.
```json
{
# This is a sample JSON code block
"greeting": "Hello, world!" # Print greeting
}
```
```console
# This is a sample console code block
$ echo "Hello, world!" # Print greeting
```
```toml
// This is a sample TOML code block
title = "TOML Example" // Title of the document
```
### Блоки кода с комментариями на неподдерживаемых языках или без указания языка { #code-blocks-with-unsupported-languages }
Следующие блоки кода используют неподдерживаемые языки для сохранения комментариев.
Инструмент исправления не будет сохранять комментарии в этих блоках.
```javascript
// This is a sample JavaScript code block
console.log("Hello, world!"); // Print greeting
```
```
# This is a sample console code block
$ echo "Hello, world!" # Print greeting
```
```
// This is a sample console code block
$ echo "Hello, world!" // Print greeting
```
### Блоки кода с комментариями, которые не соответствуют шаблону { #code-blocks-with-comments-without-pattern }
Инструмент исправления ожидает комментарии, которые соответствуют определённому шаблону:
- Для комментариев в стиле с решёткой: комментарий начинается с `# ` (решётка, затем пробел) в начале строки или после пробела.
- Для комментариев в стиле со слешами: комментарий начинается с `// ` (два слеша, затем пробел) в начале строки или после пробела.
Если комментарий не соответствует этому шаблону, инструмент исправления не будет его сохранять.
```python
#Function declaration
def hello_world():# Print greeting
print("Hello, world!") #Print greeting without space after hash
```
```console
//Function declaration
def hello_world():// Print greeting
print("Hello, world!") //Print greeting without space after slashes
```
## Блок кода с четырёхкратными обратными кавычками { #code-blocks-with-quadruple-backticks }
Следующий блок кода содержит четырёхкратные обратные кавычки.
````python
# Функция приветствия
def hello_world():
print("Hello, world!") # Печать приветствия
````
### Несоответствие обратных кавычек фиксится { #backticks-number-mismatch-is-fixable }
Следующий блок кода имеет тройные обратные кавычки в оригинальном документе, но четырёхкратные обратные кавычки в переведённом документе.
Это будет исправлено инструментом исправления (будет преобразовано в тройные обратные кавычки).
```Python
# Немного кода на Python
```
### Блок кода в тройных обратных кавычка внутри блока кода в четырёхкратных обратных кавычках { #triple-backticks-inside-quadruple-backticks }
Комментарии внутри вложенного блока кода в тройных обратных кавычках НЕ БУДУТ сохранены.
````
Here is a code block with quadruple backticks that contains triple backticks inside:
```python
# This is a sample Python code block
def hello_world():
print("Hello, world!") # Print greeting
```
````
# Включения кода { #code-includes }
## Простые включения кода { #simple-code-includes }
{* ../../docs_src/python_types/tutorial001_py39.py *}
{* ../../docs_src/python_types/tutorial002_py39.py *}
## Включения кода с подсветкой { #code-includes-with-highlighting }
{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}
{* ../../docs_src/python_types/tutorial006_py39.py hl[10] *}
## Включения кода с диапазонами строк { #code-includes-with-line-ranges }
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] *}
## Включения кода с диапазонами строк и подсветкой { #code-includes-with-line-ranges-and-highlighting }
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
{* ../../docs_src/dependencies/tutorial015_an_py310.py ln[10:15] hl[12:14] *}
## Включения кода с заголовком { #code-includes-with-title }
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
## Включения кода с неизвестными атрибутами { #code-includes-with-unknown-attributes }
{* ../../docs_src/python_types/tutorial001_py39.py unknown[123] *}
## Ещё включения кода для тестирования исправления { #some-more-code-includes-to-test-fixing }
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
# Ссылки { #links }
## Ссылки в стиле Markdown { #markdown-style-links }
Это [Markdown-ссылка](https://example.com) на внешний сайт.
Это ссылка с атрибутами: [**FastAPI** генераторы проектов](project-generation.md){.internal-link target=_blank}
Это ссылка на основной сайт FastAPI: [FastAPI](https://fastapi.tiangolo.com/lang) — инструмент должен добавить код языка в URL.
Это ссылка на одну из страниц на сайте FastAPI: [How to](https://fastapi.tiangolo.com/lang/how-to/) — инструмент должен добавить код языка в URL.
Ссылка для тестирования неправильного атрибута: [**FastAPI** генераторы проектов](project-generation.md){.internal-link} - инструмент должен исправить атрибут.
Ссылка с заголовком: [Пример](https://example.com "Сайт для примера") - URL будет исправлен инструментом, заголовок сохранится.
### Markdown ссылки на статические ресурсы { #markdown-link-to-static-assets }
Это ссылки на статические ресурсы:
* [FastAPI Logo](https://fastapi.tiangolo.com/img/fastapi-logo.png)
* [FastAPI CSS](https://fastapi.tiangolo.com/css/fastapi.css)
* [FastAPI JS](https://fastapi.tiangolo.com/js/fastapi.js)
Инструмент НЕ должен добавлять код языка в их URL.
## Ссылки в стиле HTML { #html-style-links }
Это <a href="https://example.com" target="_blank" class="external-link">HTML-ссылка</a> на внешний сайт.
Это <a href="https://fastapi.tiangolo.com/lang">ссылка на основной сайт FastAPI</a> — инструмент должен добавить код языка в URL.
Это <a href="https://fastapi.tiangolo.com/lang/how-to/">ссылка на одну из страниц на сайте FastAPI</a> — инструмент должен добавить код языка в URL.
Ссылка для тестирования неправильного атрибута: <a href="project-generation.md" class="internal-link">**FastAPI** генераторы проектов</a> - инструмент должен исправить атрибут.
### HTML ссылки на статические ресурсы { #html-links-to-static-assets }
Это ссылки на статические ресурсы:
* <a href="https://fastapi.tiangolo.com/img/fastapi-logo.png">FastAPI Logo</a>
* <a href="https://fastapi.tiangolo.com/css/fastapi.css">FastAPI CSS</a>
* <a href="https://fastapi.tiangolo.com/js/fastapi.js">FastAPI JS</a>
Инструмент НЕ должен добавлять код языка в их URL.
# Заголовок (с HTML ссылкой на <a href="https://tiangolo.com">tiangolo.com</a>) { #header-with-html-link-to-tiangolo-com }
#Не заголовок
```Python
# Также не заголовок
```
Немного текста

View File

@@ -0,0 +1,30 @@
from pathlib import Path
import pytest
from typer.testing import CliRunner
from scripts.translation_fixer import cli
data_path = Path(
"scripts/tests/test_translation_fixer/test_complex_doc/data"
).absolute()
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc.md")],
indirect=True,
)
def test_fix(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
assert result.exit_code == 0, result.output
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = (data_path / "translated_doc_expected.md").read_text()
assert fixed_content == expected_content
assert "Fixing multiline code blocks in" in result.output
assert "Fixing markdown links in" in result.output

View File

@@ -0,0 +1,19 @@
# Header 1 { #header-1 }
Some text
## Header 2 { #header-2 }
Some more text
### Header 3 { #header-3 }
Even more text
# Header 4 { #header-4 }
A bit more text
#Not a header
Final portion of text

View File

@@ -0,0 +1,19 @@
# Header 1 { #header-1 }
Some text
# Header 2 { #header-2 }
Some more text
### Header 3 { #header-3 }
Even more text
# Header 4 { #header-4 }
A bit more text
#Not a header
Final portion of text

View File

@@ -0,0 +1,19 @@
# Header 1 { #header-1 }
Some text
## Header 2 { #header-2 }
Some more text
### Header 3 { #header-3 }
Even more text
## Header 4 { #header-4 }
A bit more text
#Not a header
Final portion of text

View File

@@ -0,0 +1,19 @@
# Header 1 { #header-1 }
Some text
## Header 2 { #header-2 }
Some more text
### Header 3 { #header-3 }
Even more text
# Header 4 { #header-4 }
A bit more text
# Extra header
Final portion of text

View File

@@ -0,0 +1,19 @@
# Header 1 { #header-1 }
Some text
## Header 2 { #header-2 }
Some more text
### Header 3 { #header-3 }
Even more text
Header 4 is missing
A bit more text
#Not a header
Final portion of text

View File

@@ -0,0 +1,60 @@
from pathlib import Path
import pytest
from typer.testing import CliRunner
from scripts.translation_fixer import cli
data_path = Path(
"scripts/tests/test_translation_fixer/test_header_permalinks/data"
).absolute()
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_level_mismatch_1.md")],
indirect=True,
)
def test_level_mismatch_1(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
assert result.exit_code == 1
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(
f"{data_path}/translated_doc_level_mismatch_1.md"
).read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert "Error processing docs/lang/docs/doc.md" in result.output
assert (
"Header levels do not match between document and original document"
" (found #, expected ##) for header №2 in line 5"
) in result.output
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_level_mismatch_2.md")],
indirect=True,
)
def test_level_mismatch_2(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
assert result.exit_code == 1
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(
f"{data_path}/translated_doc_level_mismatch_2.md"
).read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert "Error processing docs/lang/docs/doc.md" in result.output
assert (
"Header levels do not match between document and original document"
" (found ##, expected #) for header №4 in line 13"
) in result.output

View File

@@ -0,0 +1,56 @@
from pathlib import Path
import pytest
from typer.testing import CliRunner
from scripts.translation_fixer import cli
data_path = Path(
"scripts/tests/test_translation_fixer/test_header_permalinks/data"
).absolute()
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_gt.md")],
indirect=True,
)
def test_gt(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
assert result.exit_code == 1
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert "Error processing docs/lang/docs/doc.md" in result.output
assert (
"Number of headers with permalinks does not match the number "
"in the original document (5 vs 4)"
) in result.output
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_lt.md")],
indirect=True,
)
def test_lt(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
assert result.exit_code == 1
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert "Error processing docs/lang/docs/doc.md" in result.output
assert (
"Number of headers with permalinks does not match the number "
"in the original document (3 vs 4)"
) in result.output

View File

@@ -0,0 +1,19 @@
# Header 1 { #header-1 }
Some text with a link to <a href="https://fastapi.tiangolo.com">FastAPI</a>.
## Header 2 { #header-2 }
Two links here: <a href="https://fastapi.tiangolo.com/how-to/">How to</a> and <a href="project-generation.md" class="internal-link" target="_blank">Project Generators</a>.
### Header 3 { #header-3 }
Another link: <a href="project-generation.md" class="internal-link" target="_blank" title="Link title">**FastAPI** Project Generators</a> with title.
# Header 4 { #header-4 }
Link to anchor: <a href="#header-2">Header 2</a>
# Header with <a href="http://example.com">link</a> { #header-with-link }
Some text

View File

@@ -0,0 +1,21 @@
# Заголовок 1 { #header-1 }
Немного текста со ссылкой на <a href="https://fastapi.tiangolo.com">FastAPI</a>.
## Заголовок 2 { #header-2 }
Две ссылки здесь: <a href="https://fastapi.tiangolo.com/how-to/">How to</a> и <a href="project-generation.md" class="internal-link" target="_blank">Project Generators</a>.
### Заголовок 3 { #header-3 }
Ещё ссылка: <a href="project-generation.md" class="internal-link" target="_blank" title="Тайтл">**FastAPI** Генераторы Проектов</a> с тайтлом.
И ещё одна <a href="https://github.com">экстра ссылка</a>.
# Заголовок 4 { #header-4 }
Ссылка на якорь: <a href="#header-2">Заголовок 2</a>
# Заголовок со <a href="http://example.com">ссылкой</a> { #header-with-link }
Немного текста

View File

@@ -0,0 +1,19 @@
# Заголовок 1 { #header-1 }
Немного текста со ссылкой на <a href="https://fastapi.tiangolo.com">FastAPI</a>.
## Заголовок 2 { #header-2 }
Две ссылки здесь: <a href="https://fastapi.tiangolo.com/how-to/">How to</a> и <a href="project-generation.md" class="internal-link" target="_blank">Project Generators</a>.
### Заголовок 3 { #header-3 }
Ещё ссылка: <a href="project-generation.md" class="internal-link" target="_blank" title="Тайтл">**FastAPI** Генераторы Проектов</a> с тайтлом.
# Заголовок 4 { #header-4 }
Ссылка на якорь: <a href="#header-2">Заголовок 2</a>
# Заголовок с потерянной ссылкой { #header-with-link }
Немного текста

View File

@@ -0,0 +1,54 @@
from pathlib import Path
import pytest
from typer.testing import CliRunner
from scripts.translation_fixer import cli
data_path = Path("scripts/tests/test_translation_fixer/test_html_links/data").absolute()
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_gt.md")],
indirect=True,
)
def test_gt(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
assert result.exit_code == 1, result.output
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert "Error processing docs/lang/docs/doc.md" in result.output
assert (
"Number of HTML links does not match the number "
"in the original document (7 vs 6)"
) in result.output
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_lt.md")],
indirect=True,
)
def test_lt(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
# assert result.exit_code == 1, result.output
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert "Error processing docs/lang/docs/doc.md" in result.output
assert (
"Number of HTML links does not match the number "
"in the original document (5 vs 6)"
) in result.output

View File

@@ -0,0 +1,19 @@
# Header 1 { #header-1 }
Some text with a link to [FastAPI](https://fastapi.tiangolo.com).
## Header 2 { #header-2 }
Two links here: [How to](https://fastapi.tiangolo.com/how-to/) and [Project Generators](project-generation.md){.internal-link target=_blank}.
### Header 3 { #header-3 }
Another link: [**FastAPI** Project Generators](project-generation.md "Link title"){.internal-link target=_blank} with title.
# Header 4 { #header-4 }
Link to anchor: [Header 2](#header-2)
# Header with [link](http://example.com) { #header-with-link }
Some text

View File

@@ -0,0 +1,19 @@
# Заголовок 1 { #header-1 }
Немного текста со ссылкой на [FastAPI](https://fastapi.tiangolo.com).
## Заголовок 2 { #header-2 }
Две ссылки здесь: [How to](https://fastapi.tiangolo.com/how-to/) и [Project Generators](project-generation.md){.internal-link target=_blank}.
### Заголовок 3 { #header-3 }
Ещё ссылка: [**FastAPI** Генераторы Проектов](project-generation.md "Тайтл"){.internal-link target=_blank} с тайтлом.
# Заголовок 4 { #header-4 }
Ссылка на якорь: [Заголовок 2](#header-2)
# Заголовок со [ссылкой](http://example.com) { #header-with-link }
Немного текста

View File

@@ -0,0 +1,21 @@
# Заголовок 1 { #header-1 }
Немного текста со ссылкой на [FastAPI](https://fastapi.tiangolo.com).
## Заголовок 2 { #header-2 }
Две ссылки здесь: [How to](https://fastapi.tiangolo.com/how-to/) и [Project Generators](project-generation.md){.internal-link target=_blank}.
### Заголовок 3 { #header-3 }
Ещё ссылка: [**FastAPI** Генераторы Проектов](project-generation.md "Тайтл"){.internal-link target=_blank} с тайтлом.
И ещё одна [экстра ссылка](https://github.com).
# Заголовок 4 { #header-4 }
Ссылка на якорь: [Заголовок 2](#header-2)
# Заголовок со [ссылкой](http://example.com) { #header-with-link }
Немного текста

View File

@@ -0,0 +1,19 @@
# Заголовок 1 { #header-1 }
Немного текста со ссылкой на [FastAPI](https://fastapi.tiangolo.com).
## Заголовок 2 { #header-2 }
Две ссылки здесь: [How to](https://fastapi.tiangolo.com/how-to/) и [Project Generators](project-generation.md){.internal-link target=_blank}.
### Заголовок 3 { #header-3 }
Ещё ссылка: [**FastAPI** Генераторы Проектов](project-generation.md "Тайтл"){.internal-link target=_blank} с тайтлом.
# Заголовок 4 { #header-4 }
Ссылка на якорь: [Заголовок 2](#header-2)
# Заголовок с потерянной ссылкой { #header-with-link }
Немного текста

View File

@@ -0,0 +1,56 @@
from pathlib import Path
import pytest
from typer.testing import CliRunner
from scripts.translation_fixer import cli
data_path = Path(
"scripts/tests/test_translation_fixer/test_markdown_links/data"
).absolute()
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_gt.md")],
indirect=True,
)
def test_gt(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
assert result.exit_code == 1, result.output
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(f"{data_path}/translated_doc_number_gt.md").read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert "Error processing docs/lang/docs/doc.md" in result.output
assert (
"Number of markdown links does not match the number "
"in the original document (7 vs 6)"
) in result.output
@pytest.mark.parametrize(
"copy_test_files",
[(f"{data_path}/en_doc.md", f"{data_path}/translated_doc_number_lt.md")],
indirect=True,
)
def test_lt(runner: CliRunner, root_dir: Path, copy_test_files):
result = runner.invoke(
cli,
["fix-pages", "docs/lang/docs/doc.md"],
)
# assert result.exit_code == 1, result.output
fixed_content = (root_dir / "docs" / "lang" / "docs" / "doc.md").read_text()
expected_content = Path(f"{data_path}/translated_doc_number_lt.md").read_text()
assert fixed_content == expected_content # Translated doc remains unchanged
assert "Error processing docs/lang/docs/doc.md" in result.output
assert (
"Number of markdown links does not match the number "
"in the original document (5 vs 6)"
) in result.output

View File

@@ -0,0 +1,132 @@
import os
from collections.abc import Iterable
from pathlib import Path
from typing import Annotated
import typer
from scripts.doc_parsing_utils import check_translation
non_translated_sections = (
f"reference{os.sep}",
"release-notes.md",
"fastapi-people.md",
"external-links.md",
"newsletter.md",
"management-tasks.md",
"management.md",
"contributing.md",
)
cli = typer.Typer()
@cli.callback()
def callback():
pass
def iter_all_lang_paths(lang_path_root: Path) -> Iterable[Path]:
"""
Iterate on the markdown files to translate in order of priority.
"""
first_dirs = [
lang_path_root / "learn",
lang_path_root / "tutorial",
lang_path_root / "advanced",
lang_path_root / "about",
lang_path_root / "how-to",
]
first_parent = lang_path_root
yield from first_parent.glob("*.md")
for dir_path in first_dirs:
yield from dir_path.rglob("*.md")
first_dirs_str = tuple(str(d) for d in first_dirs)
for path in lang_path_root.rglob("*.md"):
if str(path).startswith(first_dirs_str):
continue
if path.parent == first_parent:
continue
yield path
def get_all_paths(lang: str):
res: list[str] = []
lang_docs_root = Path("docs") / lang / "docs"
for path in iter_all_lang_paths(lang_docs_root):
relpath = path.relative_to(lang_docs_root)
if not str(relpath).startswith(non_translated_sections):
res.append(str(relpath))
return res
def process_one_page(path: Path) -> bool:
"""
Fix one translated document by comparing it to the English version.
Returns True if processed successfully, False otherwise.
"""
try:
lang_code = path.parts[1]
if lang_code == "en":
print(f"Skipping English document: {path}")
return True
en_doc_path = Path("docs") / "en" / Path(*path.parts[2:])
doc_lines = path.read_text(encoding="utf-8").splitlines()
en_doc_lines = en_doc_path.read_text(encoding="utf-8").splitlines()
doc_lines = check_translation(
doc_lines=doc_lines,
en_doc_lines=en_doc_lines,
lang_code=lang_code,
auto_fix=True,
path=str(path),
)
# Write back the fixed document
doc_lines.append("") # Ensure file ends with a newline
path.write_text("\n".join(doc_lines), encoding="utf-8")
except ValueError as e:
print(f"Error processing {path}: {e}")
return False
return True
@cli.command()
def fix_all(ctx: typer.Context, language: str):
docs = get_all_paths(language)
all_good = True
for page in docs:
doc_path = Path("docs") / language / "docs" / page
res = process_one_page(doc_path)
all_good = all_good and res
if not all_good:
raise typer.Exit(code=1)
@cli.command()
def fix_pages(
doc_paths: Annotated[
list[Path],
typer.Argument(help="List of paths to documents."),
],
):
all_good = True
for path in doc_paths:
res = process_one_page(path)
all_good = all_good and res
if not all_good:
raise typer.Exit(code=1)
if __name__ == "__main__":
cli()