committed by
GitHub
95 changed files with 7211 additions and 1289 deletions
@ -0,0 +1,45 @@ |
|||
labels: [lang-all] |
|||
body: |
|||
- type: markdown |
|||
attributes: |
|||
value: | |
|||
Thanks for your interest in helping translate the FastAPI docs! 🌍 |
|||
|
|||
Please follow these instructions carefully to propose a new language translation. 🙏 |
|||
|
|||
This structured process helps ensure translations can be properly maintained long-term. |
|||
- type: checkboxes |
|||
id: checks |
|||
attributes: |
|||
label: Initial Checks |
|||
description: Please confirm and check all the following options. |
|||
options: |
|||
- label: I checked that this language is not already being translated in FastAPI docs. |
|||
required: true |
|||
- label: I searched existing discussions to ensure no one else proposed this language. |
|||
required: true |
|||
- label: I am a native speaker of the language I want to help translate. |
|||
required: true |
|||
- type: input |
|||
id: language |
|||
attributes: |
|||
label: Target Language |
|||
description: What language do you want to translate the FastAPI docs into? |
|||
placeholder: e.g. Latin |
|||
validations: |
|||
required: true |
|||
- type: textarea |
|||
id: additional_info |
|||
attributes: |
|||
label: Additional Information |
|||
description: Any other relevant information about your translation proposal |
|||
- type: markdown |
|||
attributes: |
|||
value: | |
|||
Translations are automatized with AI and then reviewed by native speakers. 🤖 🙋 |
|||
|
|||
This allows us to keep them consistent and up-to-date. |
|||
|
|||
If there are several native speakers commenting on this discussion and |
|||
committing to help review new translations, the FastAPI team will review it |
|||
and potentially make it an official translation. 😎 |
@ -0,0 +1,77 @@ |
|||
name: Translate |
|||
|
|||
on: |
|||
workflow_dispatch: |
|||
inputs: |
|||
debug_enabled: |
|||
description: Run with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate) |
|||
required: false |
|||
default: "false" |
|||
command: |
|||
description: Command to run |
|||
type: choice |
|||
options: |
|||
- translate-page |
|||
- translate-lang |
|||
- update-outdated |
|||
- add-missing |
|||
- update-and-add |
|||
- remove-all-removable |
|||
language: |
|||
description: Language to translate to as a letter code (e.g. "es" for Spanish) |
|||
type: string |
|||
required: false |
|||
default: "" |
|||
en_path: |
|||
description: File path in English to translate (e.g. docs/en/docs/index.md) |
|||
type: string |
|||
required: false |
|||
default: "" |
|||
|
|||
env: |
|||
UV_SYSTEM_PYTHON: 1 |
|||
|
|||
jobs: |
|||
job: |
|||
if: github.repository_owner == 'fastapi' |
|||
runs-on: ubuntu-latest |
|||
permissions: |
|||
contents: write |
|||
steps: |
|||
- name: Dump GitHub context |
|||
env: |
|||
GITHUB_CONTEXT: ${{ toJson(github) }} |
|||
run: echo "$GITHUB_CONTEXT" |
|||
- uses: actions/checkout@v4 |
|||
- name: Set up Python |
|||
uses: actions/setup-python@v5 |
|||
with: |
|||
python-version: "3.11" |
|||
- name: Setup uv |
|||
uses: astral-sh/setup-uv@v6 |
|||
with: |
|||
version: "0.4.15" |
|||
enable-cache: true |
|||
cache-dependency-glob: | |
|||
requirements**.txt |
|||
pyproject.toml |
|||
- name: Install Dependencies |
|||
run: uv pip install -r requirements-github-actions.txt -r requirements-translations.txt |
|||
# Allow debugging with tmate |
|||
- name: Setup tmate session |
|||
uses: mxschmitt/action-tmate@v3 |
|||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} |
|||
with: |
|||
limit-access-to-actor: true |
|||
env: |
|||
GITHUB_TOKEN: ${{ secrets.FASTAPI_TRANSLATIONS }} |
|||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} |
|||
- name: FastAPI Translate |
|||
run: | |
|||
python ./scripts/translate.py ${{ github.event.inputs.command }} |
|||
python ./scripts/translate.py make-pr |
|||
env: |
|||
GITHUB_TOKEN: ${{ secrets.FASTAPI_TRANSLATIONS }} |
|||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} |
|||
LANGUAGE: ${{ github.event.inputs.language }} |
|||
EN_PATH: ${{ github.event.inputs.en_path }} |
@ -0,0 +1,3 @@ |
|||
# সম্পর্কে |
|||
|
|||
**FastAPI** সম্পর্কে বিস্তারিত — এর ডিজাইন, অনুপ্রেরণা ও আরও অনেক কিছু। 🤓 |
@ -0,0 +1,298 @@ |
|||
# এনভায়রনমেন্ট ভেরিয়েবলস |
|||
|
|||
/// tip |
|||
|
|||
আপনি যদি "এনভায়রনমেন্ট ভেরিয়েবলস" কী এবং সেগুলো কীভাবে ব্যবহার করতে হয় সেটা জানেন, তাহলে এই অংশটি স্কিপ করে যেতে পারেন। |
|||
|
|||
/// |
|||
|
|||
এনভায়রনমেন্ট ভেরিয়েবল (সংক্ষেপে "**env var**" নামেও পরিচিত) হলো এমন একটি ভেরিয়েবল যা পাইথন কোডের **বাইরে**, **অপারেটিং সিস্টেমে** থাকে এবং আপনার পাইথন কোড (বা অন্যান্য প্রোগ্রাম) দ্বারা যাকে রিড করা যায়। |
|||
|
|||
এনভায়রনমেন্ট ভেরিয়েবলস অ্যাপ্লিকেশনের **সেটিংস** পরিচালনা করতে, পাইথনের **ইনস্টলেশন** প্রক্রিয়ার অংশ হিসেবে, ইত্যাদি কাজে উপযোগী হতে পারে। |
|||
|
|||
## Env Vars তৈরী এবং ব্যবহার |
|||
|
|||
আপনি **শেল (টার্মিনাল)**-এ, পাইথনের প্রয়োজন ছাড়াই, এনভায়রনমেন্ট ভেরিয়েবলস **তৈরি** এবং ব্যবহার করতে পারবেনঃ |
|||
|
|||
//// tab | লিনাক্স, ম্যাকওএস, উইন্ডোজ Bash |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// আপনি চাইলে MY_NAME নামে একটি env var তৈরি করতে পারেন |
|||
$ export MY_NAME="Wade Wilson" |
|||
|
|||
// তারপরে এটিকে চাইলে অন্যান্য প্রোগ্রামে ব্যবহার করতে পারেন |
|||
$ echo "Hello $MY_NAME" |
|||
|
|||
Hello Wade Wilson |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
//// tab | উইন্ডোজ পাওয়ারশেল |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// MY_NAME নামে env var তৈরি |
|||
$ $Env:MY_NAME = "Wade Wilson" |
|||
|
|||
// অন্যান্য প্রোগ্রামে এটিকে ব্যবহার |
|||
$ echo "Hello $Env:MY_NAME" |
|||
|
|||
Hello Wade Wilson |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
## পাইথনে env vars রিড করা |
|||
|
|||
আপনি চাইলে পাইথনের **বাইরে**, টার্মিনালে (বা অন্য কোনো উপায়ে) এনভায়রনমেন্ট ভেরিয়েবলস তৈরি করতে পারেন, এবং পরে সেগুলো **পাইথনে রিড** (অ্যাক্সেস করতে) পারেন। |
|||
|
|||
উদাহরণস্বরূপ, আপনার `main.py` নামে একটি ফাইল থাকতে পারেঃ |
|||
|
|||
```Python hl_lines="3" |
|||
import os |
|||
|
|||
name = os.getenv("MY_NAME", "World") |
|||
print(f"Hello {name} from Python") |
|||
``` |
|||
|
|||
/// tip |
|||
|
|||
<a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> এর দ্বিতীয় আর্গুমেন্টটি হলো এর ডিফল্ট ভ্যালু যা রিটার্ন করা হবে। |
|||
|
|||
যদি এটি দেওয়া না হয়, ডিফল্টভাবে `None` ব্যবহৃত হবে, এখানে আমরা ডিফল্ট ভ্যালু হিসেবে `"World"` ব্যবহার করেছি। |
|||
|
|||
/// |
|||
|
|||
তারপরে পাইথন প্রোগ্রামটিকে নিম্নোক্তভাবে কল করা যাবেঃ |
|||
|
|||
//// tab | লিনাক্স, ম্যাকওএস, উইন্ডোজ Bash |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// এখনো আমরা এনভায়রনমেন্ট ভেরিয়েবল সেট করিনি |
|||
$ python main.py |
|||
|
|||
// যেহেতু env var সেট করা হয়নি, তাই আমরা ডিফল্ট ভ্যালু পাচ্ছি |
|||
|
|||
Hello World from Python |
|||
|
|||
// কিন্তু আমরা প্রথমে যদি একটা এনভায়রনমেন্ট ভারিয়েবল তৈরি করে নেই |
|||
$ export MY_NAME="Wade Wilson" |
|||
|
|||
// এবং তারপর আবার প্রোগ্রাটিকে কল করি |
|||
$ python main.py |
|||
|
|||
// এখন এটি এনভায়রনমেন্ট ভেরিয়েবল রিড করতে পারবে |
|||
|
|||
Hello Wade Wilson from Python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
//// tab | উইন্ডোজ পাওয়ারশেল |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// এখনো আমরা এনভায়রনমেন্ট ভেরিয়েবল সেট করিনি |
|||
$ python main.py |
|||
|
|||
// যেহেতু env var সেট করা হয়নি, তাই আমরা ডিফল্ট ভ্যালু পাচ্ছি |
|||
|
|||
Hello World from Python |
|||
|
|||
// কিন্তু আমরা প্রথমে যদি একটা এনভায়রনমেন্ট ভারিয়েবল তৈরি করে নেই |
|||
$ $Env:MY_NAME = "Wade Wilson" |
|||
|
|||
// এবং তারপর আবার প্রোগ্রাটিকে কল করি |
|||
$ python main.py |
|||
|
|||
// এখন এটি এনভায়রনমেন্ট ভেরিয়েবল রিড করতে পারবে |
|||
|
|||
Hello Wade Wilson from Python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
যেহেতু এনভায়রনমেন্ট ভেরিয়েবলস কোডের বাইরে সেট করা যায়, কিন্তু পরবর্তীতে কোড দ্বারা রিড করা যায়, এবং বাকি ফাইলগুলোর সাথে রাখতে (`git` এ কমিট) হয় না, তাই কনফিগারেশনস বা **সেটিংস** এর জন্য এগুলো সাধারণত ব্যবহৃত হয়ে থাকে। |
|||
|
|||
আপনি একটি এনভায়রনমেন্ট ভেরিয়েবল শুধুমাত্র একটি **নির্দিষ্ট প্রোগ্রাম ইনভোকেশনের** জন্যও তৈরি করতে পারেন, যা শুধুমাত্র সেই প্রোগ্রামের জন্যই এভেইলেবল থাকবে এবং শুধুমাত্র তার চলাকালীন সময় পর্যন্তই সক্রিয় থাকবে। |
|||
|
|||
এটি করতে, প্রোগ্রামটি রান করার ঠিক আগেই, একই লাইনে এনভায়রনমেন্ট ভেরিয়েবল তৈরি করুন: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// প্রোগ্রামটি কল করার সময় একই লাইনে MY_NAME এনভায়রনমেন্ট ভেরিয়েবল তৈরি করুন |
|||
$ MY_NAME="Wade Wilson" python main.py |
|||
|
|||
// এখন এটি এনভায়রনমেন্ট ভ্যরিয়েবলটিকে রিড করতে পারবে |
|||
|
|||
Hello Wade Wilson from Python |
|||
|
|||
// পরবর্তীতে এনভায়রনমেন্ট ভেরিয়েবলটিকে আর ব্যবহার করা যাচ্ছে না |
|||
$ python main.py |
|||
|
|||
Hello World from Python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
/// tip |
|||
|
|||
এটি নিয়ে আরো বিস্তারিত পড়তে পারেন এখানে <a href="https://12factor.net/config" class="external-link" target="_blank">The Twelve-Factor App: Config</a>। |
|||
|
|||
/// |
|||
|
|||
## টাইপস এবং ভ্যালিডেশন |
|||
|
|||
এই এনভায়রনমেন্ট ভেরিয়েবলগুলো শুধুমাত্র **টেক্সট স্ট্রিংস** হ্যান্ডেল করতে পারে, যেহেতু এগুলো পাইথনের বাইরে অবস্থিত এবং অন্যান্য প্রোগ্রাম এবং সিস্টেমের বাকি অংশের (এমনকি বিভিন্ন অপারেটিং সিস্টেম যেমন লিনাক্স, উইন্ডোজ, ম্যাকওএস) সাথে সামঞ্জস্যপূর্ণ হতে হয়। |
|||
|
|||
এর অর্থ হচ্ছে পাইথনে এনভায়রনমেন্ট ভেরিয়েবল থেকে রিড করা **যেকোনো ভ্যালু** একটি `str` হবে, এবং অন্য কোনো টাইপে কনভার্সন বা যেকোনো ভেলিডেশন কোডে আলাদাভাবে করতে হবে। |
|||
|
|||
এনভায়রনমেন্ট ভেরিয়েবল ব্যবহার করে **এপ্লিকেশন সেটিংস** হ্যান্ডেল করা নিয়ে আরো বিস্তারিত জানা যাবে [Advanced User Guide - Settings and Environment Variables](./advanced/settings.md){.internal-link target=_blank}. |
|||
|
|||
## `PATH` এনভায়রনমেন্ট ভেরিয়েবল |
|||
|
|||
**`PATH`** নামে একটি **বিশেষ** এনভায়রনমেন্ট ভেরিয়েবল রয়েছে, যেটি প্রোগ্রাম রান করার জন্য অপারেটিং সিস্টেমস (লিনাক্স, ম্যাকওএস, উইন্ডোজ) দ্বারা ব্যবহৃত হয়। |
|||
|
|||
`PATH` ভেরিয়েবল এর ভ্যালু হচ্ছে একটি বিশাল স্ট্রিং যা ডিরেক্টরিকে কোলন `:` দিয়ে আলাদা করার মাধ্যমে লিনাক্সে ও ম্যাকওএস এ, এবং সেমিকোলন `;` এর মাধ্যমে উইন্ডোজ এ তৈরি করা থাকে। |
|||
|
|||
উদাহরণস্বরূপ, `PATH` ভেরিয়েবল নিচের মতো দেখতে হতে পারেঃ |
|||
|
|||
//// tab | লিনাক্স, ম্যাকওএস |
|||
|
|||
```plaintext |
|||
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin |
|||
``` |
|||
|
|||
তারমানে হলো সিস্টেম প্রোগ্রামগুলোকে নিচের ডিরেক্টরিগুলোতে খুঁজবেঃ |
|||
|
|||
* `/usr/local/bin` |
|||
* `/usr/bin` |
|||
* `/bin` |
|||
* `/usr/sbin` |
|||
* `/sbin` |
|||
|
|||
//// |
|||
|
|||
//// tab | উইন্ডোজ |
|||
|
|||
```plaintext |
|||
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 |
|||
``` |
|||
|
|||
তারমানে হলো সিস্টেম প্রোগ্রামগুলোকে নিচের ডিরেক্টরিগুলোতে খুঁজবেঃ |
|||
|
|||
* `C:\Program Files\Python312\Scripts` |
|||
* `C:\Program Files\Python312` |
|||
* `C:\Windows\System32` |
|||
|
|||
//// |
|||
|
|||
যখন আপনি টার্মিনালে কোনো **কমান্ড** লিখবেন, অপারেটিং সিস্টেম **প্রত্যেকটি ডিরেক্টরিতে** প্রোগ্রামটি **খুঁজবে** যেগুলো `PATH` এনভায়রনমেন্ট ভেরিয়েবল এ লিস্ট করা আছে। |
|||
|
|||
উদাহরণস্বরূপ, যখন আপনি টার্মিনালে `python` টাইপ করবেন, অপারেটিং সিস্টেম এই লিস্ট এর **প্রথম ডিরেক্টরিতে** `python` নামের একটি প্রোগ্রাম খুঁজবে। |
|||
|
|||
যদি এটি খুঁজে পায়, তাহলে এটি প্রোগ্রামটিকে ব্যবহার করবে। অন্যথায় এটি **অন্যান্য ডিরেক্টরিগুলোতে** এটিকে খুঁজতে থাকবে। |
|||
|
|||
### পাইথন ইনস্টল এবং `PATH` আপডেট |
|||
|
|||
যখন আপনি পাইথন ইনস্টল করেন, আপনি `PATH` এনভায়রনমেন্ট ভেরিয়েবল আপডেট করতে চান কিনা সেটা জিজ্ঞেস করা হতে পারে। |
|||
|
|||
//// tab | লিনাক্স, ম্যাকওএস |
|||
|
|||
ধরা যাক আপনি পাইথন ইনস্টল করলেন এবং এটি `/opt/custompython/bin` ডিরেক্টরিতে ইনস্টল হচ্ছে। |
|||
|
|||
যদি আপনি "Yes" সিলেক্ট করে `PATH` এনভায়রনমেন্ট ভেরিয়েবল আপডেট করতে চান, তাহলে ইনস্টলার `/opt/custompython/bin` কে `PATH` এনভায়রনমেন্ট ভেরিয়েবল এ এড করে দিবে। |
|||
|
|||
এটা দেখতে এমনটা হতে পারেঃ |
|||
|
|||
```plaintext |
|||
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin |
|||
``` |
|||
|
|||
এইভাবে, আপনি যখন টার্মিনালে `python` টাইপ করেন, সিস্টেম পাইথন প্রোগ্রামটিকে `/opt/custompython/bin` (সর্বশেষ ডিরেক্টরি) তে খুঁজে পাবে এবং এটাকে ব্যবহার করবে। |
|||
|
|||
//// |
|||
|
|||
//// tab | উইন্ডোজ |
|||
|
|||
ধরা যাক আপনি পাইথন ইনস্টল করলেন এবং এটি `C:\opt\custompython\bin` ডিরেক্টরিতে ইনস্টল হচ্ছে। |
|||
|
|||
যদি আপনি "Yes" সিলেক্ট করে `PATH` এনভায়রনমেন্ট ভেরিয়েবল আপডেট করতে চান, তাহলে ইনস্টলার `C:\opt\custompython\bin` কে `PATH` এনভায়রনমেন্ট ভেরিয়েবল এ এড করে দিবে। |
|||
|
|||
```plaintext |
|||
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin |
|||
``` |
|||
|
|||
এইভাবে, আপনি যখন টার্মিনালে `python` টাইপ করেন, সিস্টেম পাইথন প্রোগ্রামটিকে `C:\opt\custompython\bin` (সর্বশেষ ডিরেক্টরি) তে খুঁজে পাবে এবং এটাকে ব্যবহার করবে। |
|||
|
|||
//// |
|||
|
|||
তাই, আপনি যদি টাইপ করেনঃ |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// tab | লিনাক্স, ম্যাকওএস |
|||
|
|||
সিস্টেম `python` প্রোগ্রামকে `/opt/custompython/bin` এ **খুঁজে পাবে** এবং এটাকে রান করবে। |
|||
|
|||
এটা মোটামুটিভাবে নিচের মতো করে লেখার সমান হবেঃ |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ /opt/custompython/bin/python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
//// tab | উইন্ডোজ |
|||
|
|||
সিস্টেম `python` প্রোগ্রামকে `C:\opt\custompython\bin\python` এ **খুঁজে পাবে** এবং এটাকে রান করবে। |
|||
|
|||
এটা মোটামুটিভাবে নিচের মতো করে লেখার সমান হবেঃ |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ C:\opt\custompython\bin\python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
এই তথ্যগুলো [ভার্চুয়াল এনভায়রনমেন্টস](virtual-environments.md){.internal-link target=_blank} শেখার ক্ষেত্রে সহায়ক হবে। |
|||
|
|||
## উপসংহার |
|||
|
|||
এর মাধ্যমে আপনি **এনভায়রনমেন্ট ভেরিয়েবলস** কি এবং এটিকে পাইথনে কিভাবে ব্যবহার করতে হয় তার সম্পর্কে বেসিক ধারনা পেলেন। |
|||
|
|||
চাইলে এই সম্পর্কে আরো বিস্তারিত পড়তে পারেন <a href="https://en.wikipedia.org/wiki/Environment_variable" class="external-link" target="_blank">Wikipedia for Environment Variable</a> এ। |
|||
|
|||
অনেক ক্ষেত্রে, দেখা মাত্রই এনভায়রনমেন্ট ভেরিয়েবল কীভাবে প্রয়োজন হবে তা স্পষ্ট হয় না। কিন্তু ডেভেলপমেন্টের সময় আপনি নানা রকম পরিস্থিতিতে এগুলোর সম্মুখীন হবেন, তাই এগুলো সম্পর্কে জেনে রাখা ভালো। |
|||
|
|||
উদাহরণস্বরূপ, আপনার এই ইনফরমেশনটি পরবর্তী, [ভার্চুয়াল এনভায়রনমেন্টস](virtual-environments.md) অংশে দরকার হবে। |
File diff suppressed because it is too large
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 26 KiB |
@ -0,0 +1,444 @@ |
|||
# همزمانی و async / await |
|||
|
|||
جزئیات در مورد سینتکس `async def` برای *توابع عملیات مسیر* و یه کم پیشزمینه در مورد کد ناهمزمان، همزمانی و موازیسازی. |
|||
|
|||
## عجله داری؟ |
|||
|
|||
<abbr title="خیلی طولانی بود؛ نخوندم"><strong>TL;DR:</strong></abbr> |
|||
|
|||
اگه از کتابخونههای سومشخصی استفاده میکنی که بهت میگن با `await` صداشون کنی، مثل: |
|||
|
|||
```Python |
|||
results = await some_library() |
|||
``` |
|||
|
|||
اون وقت، *توابع عملیات مسیرت* رو با `async def` تعریف کن، اینجوری: |
|||
|
|||
```Python hl_lines="2" |
|||
@app.get('/') |
|||
async def read_results(): |
|||
results = await some_library() |
|||
return results |
|||
``` |
|||
|
|||
/// note |
|||
|
|||
فقط توی توابعی که با `async def` ساخته شدن میتونی از `await` استفاده کنی. |
|||
|
|||
/// |
|||
|
|||
--- |
|||
|
|||
اگه از یه کتابخونه سومشخص استفاده میکنی که با یه چیزی (مثل دیتابیس، API، سیستم فایل و غیره) ارتباط داره و از `await` پشتیبانی نمیکنه (که الان برای بیشتر کتابخونههای دیتابیس اینجوریه)، اون وقت *توابع عملیات مسیرت* رو عادی، فقط با `def` تعریف کن، اینجوری: |
|||
|
|||
```Python hl_lines="2" |
|||
@app.get('/') |
|||
def results(): |
|||
results = some_library() |
|||
return results |
|||
``` |
|||
|
|||
--- |
|||
|
|||
اگه برنامهات (به هر دلیلی) لازم نیست با چیز دیگهای ارتباط برقرار کنه و منتظر جوابش بمونه، از `async def` استفاده کن. |
|||
|
|||
--- |
|||
|
|||
اگه نمیدونی چیکار کنی، از `def` معمولی استفاده کن. |
|||
|
|||
--- |
|||
|
|||
**توجه**: میتونی توی *توابع عملیات مسیرت* هر چقدر که لازم داری `def` و `async def` رو قاطی کنی و هر کدوم رو با بهترین گزینه برات تعریف کنی. FastAPI خودش کار درست رو باهاشون انجام میده. |
|||
|
|||
به هر حال، توی هر کدوم از موقعیتهای بالا، FastAPI هنوز ناهمزمان کار میکنه و خیلی خیلی سریع هست. |
|||
|
|||
ولی با دنبال کردن مراحل بالا، میتونه یه سری بهینهسازی عملکرد هم بکنه. |
|||
|
|||
## جزئیات فنی |
|||
|
|||
نسخههای مدرن پایتون از **"کد ناهمزمان"** با چیزی که بهش **"کروتین"** میگن پشتیبانی میکنن، با سینتکس **`async` و `await`**. |
|||
|
|||
بیاید این جمله رو تکهتکه توی بخشهای زیر ببینیم: |
|||
|
|||
* **کد ناهمزمان** |
|||
* **`async` و `await`** |
|||
* **کروتینها** |
|||
|
|||
## کد ناهمزمان |
|||
|
|||
کد ناهمزمان یعنی زبون 💬 یه راهی داره که به کامپیوتر / برنامه 🤖 بگه توی یه جای کد، باید منتظر بمونه تا *یه چیز دیگه* یه جای دیگه تموم بشه. فرض کن اون *یه چیز دیگه* اسمش "فایل-آروم" 📝 باشه. |
|||
|
|||
پس، توی اون مدت، کامپیوتر میتونه بره یه کار دیگه بکنه، تا وقتی "فایل-آروم" 📝 تموم بشه. |
|||
|
|||
بعدش کامپیوتر / برنامه 🤖 هر وقت فرصتی داشته باشه برمیگرده، چون دوباره منتظره، یا هر وقت همه کاری که اون لحظه داشته تموم کرده. و میبینه آیا کارایی که منتظرشون بوده تموم شدن یا نه، و هر کاری که باید بکنه رو انجام میده. |
|||
|
|||
بعد، اون 🤖 اولین کاری که تموم شده (مثلاً "فایل-آروم" 📝 ما) رو برمیداره و هر کاری که باید باهاش بکنه رو ادامه میده. |
|||
|
|||
این "منتظر یه چیز دیگه بودن" معمولاً به عملیات <abbr title="ورودی و خروجی">I/O</abbr> اشاره داره که نسبتاً "آروم" هستن (نسبت به سرعت پردازنده و حافظه RAM)، مثل منتظر موندن برای: |
|||
|
|||
* دادههایی که از کلاینت از طریق شبکه فرستاده میشن |
|||
* دادههایی که برنامهات فرستاده تا از طریق شبکه به کلاینت برسه |
|||
* محتوای یه فایل توی دیسک که سیستم بخوندش و به برنامهات بده |
|||
* محتوایی که برنامهات به سیستم داده تا توی دیسک بنویسه |
|||
* یه عملیات API از راه دور |
|||
* یه عملیات دیتابیس که تموم بشه |
|||
* یه کوئری دیتابیس که نتایجش برگرده |
|||
* و غیره. |
|||
|
|||
چون زمان اجرا بیشتر صرف انتظار برای عملیات <abbr title="ورودی و خروجی">I/O</abbr> میشه، بهشون میگن عملیات "I/O bound". |
|||
|
|||
بهش "ناهمزمان" میگن چون کامپیوتر / برنامه لازم نیست با کار آروم "همزمان" باشه، منتظر لحظه دقیق تموم شدن کار بمونه، در حالی که هیچ کاری نمیکنه، تا نتیجه رو بگیره و کارش رو ادامه بده. |
|||
|
|||
به جاش، چون یه سیستم "ناهمزمان" هست، وقتی کار تموم شد، میتونه یه کم توی صف منتظر بمونه (چند میکروثانیه) تا کامپیوتر / برنامه هر کاری که رفته بکنه رو تموم کنه، و بعد برگرده نتیجه رو بگیره و باهاش کار کنه. |
|||
|
|||
برای "همزمان" (برخلاف "ناهمزمان") معمولاً از اصطلاح "ترتیبی" هم استفاده میکنن، چون کامپیوتر / برنامه همه مراحل رو به ترتیب دنبال میکنه قبل از اینکه بره سراغ یه کار دیگه، حتی اگه اون مراحل شامل انتظار باشن. |
|||
|
|||
### همزمانی و برگرها |
|||
|
|||
این ایده **ناهمزمان** که بالا توضیح دادم گاهی بهش **"همزمانی"** هم میگن. با **"موازیسازی"** فرق داره. |
|||
|
|||
**همزمانی** و **موازیسازی** هر دو به "اتفاق افتادن چیزای مختلف کموبیش همزمان" ربط دارن. |
|||
|
|||
ولی جزئیات بین *همزمانی* و *موازیسازی* خیلی متفاوته. |
|||
|
|||
برای دیدن فرقش، این داستان در مورد برگرها رو تصور کن: |
|||
|
|||
### برگرهای همزمان |
|||
|
|||
با عشقت میری فستفود بگیرین، توی صف وایمیستی در حالی که صندوقدار سفارش آدمای جلوی تو رو میگیره. 😍 |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-01.png" class="illustration"> |
|||
|
|||
بعد نوبت تو میشه، سفارش دو تا برگر خیلی شیک برای خودت و عشقت میدی. 🍔🍔 |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-02.png" class="illustration"> |
|||
|
|||
صندوقدار یه چیزی به آشپز توی آشپزخونه میگه تا بدونن باید برگرهای تو رو آماده کنن (گرچه الان دارن برگرهای مشتریای قبلی رو درست میکنن). |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-03.png" class="illustration"> |
|||
|
|||
پول رو میدی. 💸 |
|||
|
|||
صندوقدار شماره نوبتت رو بهت میده. |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-04.png" class="illustration"> |
|||
|
|||
وقتی منتظری، با عشقت میری یه میز انتخاب میکنی، میشینی و کلی با عشقت حرف میزنی (چون برگرهات خیلی شیکن و آماده کردنشون یه کم طول میکشه). |
|||
|
|||
وقتی پشت میز با عشقت نشستی، در حالی که منتظر برگرهایی، میتونی اون زمان رو صرف تحسین این کنی که عشقت چقدر باحال، ناز و باهوشه ✨😍✨. |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-05.png" class="illustration"> |
|||
|
|||
وقتی منتظری و با عشقت حرف میزنی، هر از گاهی شمارهای که رو پیشخون نشون داده میشه رو چک میکنی که ببینی نوبتت شده یا نه. |
|||
|
|||
بعد یه جایی بالاخره نوبتت میشه. میری پیشخون، برگرهات رو میگیری و برمیگردی سر میز. |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-06.png" class="illustration"> |
|||
|
|||
تو و عشقت برگرها رو میخورین و یه وقت خوب باهم دارین. ✨ |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-07.png" class="illustration"> |
|||
|
|||
/// info |
|||
|
|||
تصاویر قشنگ از <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">کترینا تامپسون</a>. 🎨 |
|||
|
|||
/// |
|||
|
|||
--- |
|||
|
|||
تصور کن تو توی این داستان کامپیوتر / برنامه 🤖 هستی. |
|||
|
|||
وقتی توی صف هستی، فقط بیکاری 😴، منتظر نوبتت هستی، کار خیلی "مفیدی" نمیکنی. ولی صف سریع پیش میره چون صندوقدار فقط سفارش میگیره (آمادشون نمیکنه)، پس این خوبه. |
|||
|
|||
بعد، وقتی نوبتت میشه، کار "مفید" واقعی میکنی، منو رو پردازش میکنی، تصمیم میگیری چی میخوای، انتخاب عشقت رو میگیری، پول میدی، چک میکنی اسکناس یا کارت درست رو دادی، چک میکنی درست حساب شده، چک میکنی سفارش آیتمای درست رو داره و غیره. |
|||
|
|||
ولی بعد، گرچه هنوز برگرهات رو نداری، کارت با صندوقدار "موقتاً متوقف" ⏸ میشه، چون باید منتظر بمونی 🕙 تا برگرهات آماده بشن. |
|||
|
|||
ولی وقتی از پیشخون دور میشی و با شماره نوبتت سر میز میشینی، میتونی توجهت رو 🔀 به عشقت بدی و "کار" ⏯ 🤓 رو اون بکنی. بعدش دوباره داری یه چیز خیلی "مفید" انجام میدی، مثل لاس زدن با عشقت 😍. |
|||
|
|||
بعد صندوقدار 💁 با گذاشتن شمارهات رو نمایشگر پیشخون میگه "من با درست کردن برگرها تموم کردم"، ولی تو مثل دیوونهها وقتی شمارهات رو نمایشگر میاد فوری نمیپری. میدونی کسی برگرهات رو نمیدزده چون شماره نوبتت رو داری، و اونا هم مال خودشون رو دارن. |
|||
|
|||
پس منتظر میمونی تا عشقت داستانش رو تموم کنه (کار فعلی ⏯ / وظیفهای که داره پردازش میشه 🤓)، آروم لبخند میزنی و میگی که میری برگرها رو بیاری ⏸. |
|||
|
|||
بعد میری پیشخون 🔀، به کار اولیه که حالا تموم شده ⏯، برگرها رو میگیری، تشکر میکنی و میبرشون سر میز. این مرحله / وظیفه تعامل با پیشخون رو تموم میکنه ⏹. این به نوبه خودش یه وظیفه جدید، "خوردن برگرها" 🔀 ⏯، میسازه، ولی اون قبلی که "گرفتن برگرها" بود تموم شده ⏹. |
|||
|
|||
### برگرهای موازی |
|||
|
|||
حالا فرض کن اینا "برگرهای همزمان" نیستن، بلکه "برگرهای موازی" هستن. |
|||
|
|||
با عشقت میری فستفود موازی بگیری. |
|||
|
|||
توی صف وایمیستی در حالی که چند تا (مثلاً 8 تا) صندوقدار که همزمان آشپز هم هستن سفارش آدمای جلوی تو رو میگیرن. |
|||
|
|||
همه قبل تو منتظرن برگرهاشون آماده بشه قبل از اینکه پیشخون رو ترک کنن، چون هر کدوم از 8 تا صندوقدار میره و برگر رو همون موقع درست میکنه قبل از اینکه سفارش بعدی رو بگیره. |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-01.png" class="illustration"> |
|||
|
|||
بالاخره نوبت تو میشه، سفارش دو تا برگر خیلی شیک برای خودت و عشقت میدی. |
|||
|
|||
پول رو میدی 💸. |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-02.png" class="illustration"> |
|||
|
|||
صندوقدار میره آشپزخونه. |
|||
|
|||
منتظر میمونی، جلوی پیشخون وایستادی 🕙، که کسی قبل از تو برگرهات رو نگیره، چون شماره نوبت نیست. |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-03.png" class="illustration"> |
|||
|
|||
چون تو و عشقت مشغول این هستین که نذارین کسی جلوتون بیاد و هر وقت برگرها رسیدن اونا رو بگیره، نمیتونی به عشقت توجه کنی. 😞 |
|||
|
|||
این کار "همزمان" هست، تو با صندوقدار/آشپز 👨🍳 "همزمان" هستی. باید منتظر بمونی 🕙 و درست همون لحظه که صندوقدار/آشپز 👨🍳 برگرها رو تموم میکنه و بهت میده اونجا باشی، وگرنه ممکنه یکی دیگه اونا رو بگیره. |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-04.png" class="illustration"> |
|||
|
|||
بعد صندوقدار/آشپزت 👨🍳 بالاخره بعد از یه مدت طولانی انتظار 🕙 جلوی پیشخون با برگرهات برمیگرده. |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-05.png" class="illustration"> |
|||
|
|||
برگرهات رو میگیری و با عشقت میری سر میز. |
|||
|
|||
فقط میخورینشون، و تمومه. ⏹ |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-06.png" class="illustration"> |
|||
|
|||
حرف زدن یا لاس زدن زیاد نبود چون بیشتر وقت صرف انتظار 🕙 جلوی پیشخون شد. 😞 |
|||
|
|||
/// info |
|||
|
|||
تصاویر قشنگ از <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">کترینا تامپسون</a>. 🎨 |
|||
|
|||
/// |
|||
|
|||
--- |
|||
|
|||
توی این سناریوی برگرهای موازی، تو یه کامپیوتر / برنامه 🤖 با دو تا پردازنده (تو و عشقت) هستی، هر دو منتظر 🕙 و توجهشون ⏯ رو برای مدت طولانی "انتظار جلوی پیشخون" 🕙 گذاشتن. |
|||
|
|||
فستفود 8 تا پردازنده (صندوقدار/آشپز) داره. در حالی که فستفود برگرهای همزمان شاید فقط 2 تا داشته (یه صندوقدار و یه آشپز). |
|||
|
|||
ولی با این حال، تجربه نهایی بهترین نیست. 😞 |
|||
|
|||
--- |
|||
|
|||
این معادل موازی داستان برگرها بود. 🍔 |
|||
|
|||
برای یه مثال "واقعیتر" از زندگی، یه بانک رو تصور کن. |
|||
|
|||
تا همین چند وقت پیش، بیشتر بانکها چند تا صندوقدار 👨💼👨💼👨💼👨💼 داشتن و یه صف بزرگ 🕙🕙🕙🕙🕙🕙🕙🕙. |
|||
|
|||
همه صندوقدارها کار رو با یه مشتری بعد از اون یکی 👨💼⏯ انجام میدادن. |
|||
|
|||
و باید توی صف 🕙 مدت زیادی منتظر بمونی وگرنه نوبتت رو از دست میدی. |
|||
|
|||
احتمالاً نمیخوای عشقت 😍 رو با خودت ببری بانک 🏦 برای کارای روزمره. |
|||
|
|||
### نتیجهگیری برگرها |
|||
|
|||
توی این سناریوی "برگرهای فستفود با عشقت"، چون کلی انتظار 🕙 هست، خیلی منطقیتره که یه سیستم همزمان ⏸🔀⏯ داشته باشی. |
|||
|
|||
این برای بیشتر برنامههای وب هم صدق میکنه. |
|||
|
|||
خیلی خیلی کاربر، ولی سرورت منتظر 🕙 اتصال نهچندان خوبشون هست تا درخواستهاشون رو بفرستن. |
|||
|
|||
و بعد دوباره منتظر 🕙 که جوابها برگردن. |
|||
|
|||
این "انتظار" 🕙 توی میکروثانیهها اندازهگیری میشه، ولی با این حال، جمعش که بکنی آخرش کلی انتظار میشه. |
|||
|
|||
برای همین استفاده از کد ناهمزمان ⏸🔀⏯ برای APIهای وب خیلی منطقیه. |
|||
|
|||
این نوع ناهمزمانی چیزیه که NodeJS رو محبوب کرد (گرچه NodeJS موازی نیست) و نقطه قوت Go بهعنوان یه زبون برنامهنویسیه. |
|||
|
|||
و همون سطح عملکردی هست که با **FastAPI** میگیری. |
|||
|
|||
و چون میتونی همزمانی و موازیسازی رو همزمان داشته باشی، عملکرد بالاتری از بیشتر فریمورکهای تستشده NodeJS میگیری و همتراز با Go، که یه زبون کامپایلشده نزدیک به C هست <a href="https://www.techempower.com/benchmarks/#section=data-r17&hw=ph&test=query&l=zijmkf-1" class="external-link" target="_blank">(همه اینا به لطف Starlette)</a>. |
|||
|
|||
### آیا همزمانی از موازیسازی بهتره؟ |
|||
|
|||
نه! این نتیجه داستان نیست. |
|||
|
|||
همزمانی با موازیسازی فرق داره. و توی **سناریوهای خاص** که کلی انتظار دارن بهتره. به همین خاطر، معمولاً برای توسعه برنامههای وب خیلی از موازیسازی بهتره. ولی نه برای همهچیز. |
|||
|
|||
برای اینکه یه تعادل بذاریم، این داستان کوتاه رو تصور کن: |
|||
|
|||
> باید یه خونه بزرگ و کثیف رو تمیز کنی. |
|||
|
|||
*آره، کل داستان همینه*. |
|||
|
|||
--- |
|||
|
|||
هیچ انتظاری 🕙 اونجا نیست، فقط کلی کار برای انجام دادن توی جاهای مختلف خونه. |
|||
|
|||
میتونی مثل مثال برگرها نوبت بذاری، اول پذیرایی، بعد آشپزخونه، ولی چون منتظر چیزی نیستی 🕙، فقط داری تمیز میکنی و تمیز میکنی، نوبتها هیچ تأثیری نداره. |
|||
|
|||
با نوبت یا بدون نوبت (همزمانی) همون قدر طول میکشه تا تمومش کنی و همون مقدار کار رو کردی. |
|||
|
|||
ولی توی این موقعیت، اگه بتونی اون 8 تا صندوقدار/آشپز/حالا-تمیزکار رو بیاری، و هر کدومشون (بهعلاوه خودت) یه قسمت از خونه رو تمیز کنن، میتونی همه کار رو **موازی** انجام بدی، با کمک اضافی، و خیلی زودتر تمومش کنی. |
|||
|
|||
توی این سناریو، هر کدوم از تمیزکارها (از جمله خودت) یه پردازندهست که کار خودش رو میکنه. |
|||
|
|||
و چون بیشتر زمان اجرا صرف کار واقعی میشه (به جای انتظار)، و کار توی کامپیوتر با <abbr title="واحد پردازش مرکزی">CPU</abbr> انجام میشه، به این مشکلات میگن "CPU bound". |
|||
|
|||
--- |
|||
|
|||
مثالهای رایج عملیات CPU bound چیزایی هستن که نیاز به پردازش ریاضی پیچیده دارن. |
|||
|
|||
مثلاً: |
|||
|
|||
* پردازش **صدا** یا **تصویر**. |
|||
* **بینایی کامپیوتری**: یه تصویر از میلیونها پیکسل تشکیل شده، هر پیکسل 3 تا مقدار / رنگ داره، پردازشش معمولاً نیاز داره چیزی رو رو اون پیکسلها همزمان حساب کنی. |
|||
* **یادگیری ماشین**: معمولاً کلی ضرب "ماتریس" و "بردار" لازم داره. یه جدول بزرگ پر از عدد رو تصور کن که همهشون رو همزمان ضرب میکنی. |
|||
* **یادگیری عمیق**: این یه زیرشاخه از یادگیری ماشینه، پس همون قضیه صدق میکنه. فقط این که یه جدول عدد برای ضرب کردن نیست، بلکه یه مجموعه بزرگ از اونا هست، و توی خیلی موارد از یه پردازنده خاص برای ساخت و / یا استفاده از این مدلها استفاده میکنی. |
|||
|
|||
### همزمانی + موازیسازی: وب + یادگیری ماشین |
|||
|
|||
با **FastAPI** میتونی از همزمانی که برای توسعه وب خیلی رایجه (همون جذابیت اصلی NodeJS) استفاده کنی. |
|||
|
|||
ولی میتونی از فواید موازیسازی و چندپردازشی (اجرای چند پروسه بهصورت موازی) برای کارای **CPU bound** مثل سیستمهای یادگیری ماشین هم بهره ببری. |
|||
|
|||
این، بهعلاوه این واقعیت ساده که پایتون زبون اصلی برای **علم داده**، یادگیری ماشین و بهخصوص یادگیری عمیقه، باعث میشه FastAPI یه انتخاب خیلی خوب برای APIها و برنامههای وب علم داده / یادگیری ماشین باشه (بین خیلی چیزای دیگه). |
|||
|
|||
برای دیدن اینکه چطور توی محیط واقعی به این موازیسازی برسی، بخش [استقرار](deployment/index.md){.internal-link target=_blank} رو ببین. |
|||
|
|||
## `async` و `await` |
|||
|
|||
نسخههای مدرن پایتون یه راه خیلی ساده و قابلفهم برای تعریف کد ناهمزمان دارن. این باعث میشه مثل کد "ترتیبی" معمولی به نظر بیاد و توی لحظههای درست "انتظار" رو برات انجام بده. |
|||
|
|||
وقتی یه عملیاتی هست که قبل از دادن نتیجهها نیاز به انتظار داره و از این قابلیتهای جدید پایتون پشتیبانی میکنه، میتونی اینجوری کدنویسیش کنی: |
|||
|
|||
```Python |
|||
burgers = await get_burgers(2) |
|||
``` |
|||
|
|||
نکته کلیدی اینجا `await` هست. به پایتون میگه که باید ⏸ منتظر بمونه تا `get_burgers(2)` کارش 🕙 تموم بشه قبل از اینکه نتیجهها رو توی `burgers` ذخیره کنه. با این، پایتون میدونه که میتونه بره یه کار دیگه 🔀 ⏯ توی این مدت بکنه (مثل گرفتن یه درخواست دیگه). |
|||
|
|||
برای اینکه `await` کار کنه، باید توی یه تابع باشه که از این ناهمزمانی پشتیبانی کنه. برای این کار، فقط با `async def` تعریفش میکنی: |
|||
|
|||
```Python hl_lines="1" |
|||
async def get_burgers(number: int): |
|||
# یه سری کار ناهمزمان برای ساختن برگرها انجام بده |
|||
return burgers |
|||
``` |
|||
|
|||
...به جای `def`: |
|||
|
|||
```Python hl_lines="2" |
|||
# این ناهمزمان نیست |
|||
def get_sequential_burgers(number: int): |
|||
# یه سری کار ترتیبی برای ساختن برگرها انجام بده |
|||
return burgers |
|||
``` |
|||
|
|||
با `async def`، پایتون میدونه که توی اون تابع باید حواسش به عبارتهای `await` باشه، و میتونه اجرای اون تابع رو "موقتاً متوقف" ⏸ کنه و بره یه کار دیگه 🔀 قبل از برگشتن بکنه. |
|||
|
|||
وقتی میخوای یه تابع `async def` رو صدا کنی، باید "منتظرش" بمونی. پس این کار نمیکنه: |
|||
|
|||
```Python |
|||
# این کار نمیکنه، چون get_burgers با async def تعریف شده |
|||
burgers = get_burgers(2) |
|||
``` |
|||
|
|||
--- |
|||
|
|||
پس، اگه از یه کتابخونه استفاده میکنی که بهت میگه میتونی با `await` صداش کنی، باید *توابع عملیات مسیرت* که ازش استفاده میکنن رو با `async def` بسازی، مثل: |
|||
|
|||
```Python hl_lines="2-3" |
|||
@app.get('/burgers') |
|||
async def read_burgers(): |
|||
burgers = await get_burgers(2) |
|||
return burgers |
|||
``` |
|||
|
|||
### جزئیات فنیتر |
|||
|
|||
شاید متوجه شده باشی که `await` فقط توی توابعی که با `async def` تعریف شدن میتونه استفاده بشه. |
|||
|
|||
ولی در عین حال، توابعی که با `async def` تعریف شدن باید "منتظر"شون بمونی. پس توابع با `async def` فقط توی توابعی که با `async def` تعریف شدن میتونن صدا زده بشن. |
|||
|
|||
حالا، قضیه مرغ و تخممرغ چیه، چطور اولین تابع `async` رو صدا میکنی؟ |
|||
|
|||
اگه با **FastAPI** کار میکنی، لازم نیست نگران این باشی، چون اون "اولین" تابع، *تابع عملیات مسیرت* هست، و FastAPI میدونه چطور کار درست رو بکنه. |
|||
|
|||
ولی اگه بخوای بدون FastAPI از `async` / `await` استفاده کنی، اینم ممکنه. |
|||
|
|||
### کد ناهمزمان خودت رو بنویس |
|||
|
|||
Starlette (و **FastAPI**) بر پایه <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a> هستن، که باعث میشه با کتابخونه استاندارد پایتون <a href="https://docs.python.org/3/library/asyncio-task.html" class="external-link" target="_blank">asyncio</a> و <a href="https://trio.readthedocs.io/en/stable/" class="external-link" target="_blank">Trio</a> سازگار باشه. |
|||
|
|||
بهخصوص، میتونی مستقیماً از <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a> برای موارد استفاده پیشرفته همزمانی که نیاز به الگوهای پیچیدهتر توی کد خودت دارن استفاده کنی. |
|||
|
|||
و حتی اگه از FastAPI استفاده نکنی، میتونی برنامههای ناهمزمان خودت رو با <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a> بنویسی تا خیلی سازگار باشه و فوایدش رو بگیری (مثل *همزمانی ساختاریافته*). |
|||
|
|||
من یه کتابخونه دیگه روی AnyIO ساختم، یه لایه نازک روش، تا یه کم annotationهای نوع رو بهتر کنم و **تکمیل خودکار** بهتر، **خطاهای درونخطی** و غیره بگیرم. یه مقدمه و آموزش ساده هم داره که بهت کمک میکنه **بفهمی** و **کد ناهمزمان خودت رو بنویسی**: <a href="https://asyncer.tiangolo.com/" class="external-link" target="_blank">Asyncer</a>. اگه بخوای **کد ناهمزمان رو با کد معمولی** (بلاککننده/همزمان) ترکیب کنی خیلی بهدردت میخوره. |
|||
|
|||
### شکلهای دیگه کد ناهمزمان |
|||
|
|||
این سبک استفاده از `async` و `await` توی زبون نسبتاً جدیده. |
|||
|
|||
ولی کار با کد ناهمزمان رو خیلی سادهتر میکنه. |
|||
|
|||
همین سینتکس (یا تقریباً یکسان) اخیراً توی نسخههای مدرن جاوااسکریپت (توی مرورگر و NodeJS) هم اضافه شده. |
|||
|
|||
ولی قبل از اون، مدیریت کد ناهمزمان خیلی پیچیدهتر و سختتر بود. |
|||
|
|||
توی نسخههای قبلی پایتون، میتونستی از نخها یا <a href="https://www.gevent.org/" class="external-link" target="_blank">Gevent</a> استفاده کنی. ولی کد خیلی پیچیدهتر میشه برای فهمیدن، دیباگ کردن و فکر کردن بهش. |
|||
|
|||
توی نسخههای قبلی NodeJS / جاوااسکریپت مرورگر، از "کالبکها" استفاده میکردی. که میرسید به <a href="http://callbackhell.com/" class="external-link" target="_blank">جهان کالبکها</a>. |
|||
|
|||
## کروتینها |
|||
|
|||
**کروتین** فقط یه اصطلاح خیلی شیک برای چیزیه که یه تابع `async def` برمیگردونه. پایتون میدونه که این یه چیزی مثل تابع هست، میتونه شروع بشه و یه جایی تموم بشه، ولی ممکنه داخلش هم موقف ⏸ بشه، هر وقت یه `await` توش باشه. |
|||
|
|||
ولی همه این قابلیت استفاده از کد ناهمزمان با `async` و `await` خیلی وقتا خلاصه میشه به استفاده از "کروتینها". این قابل مقایسه با ویژگی اصلی Go، یعنی "Goroutineها" هست. |
|||
|
|||
## نتیجهگیری |
|||
|
|||
بیاید همون جمله از بالا رو ببینیم: |
|||
|
|||
> نسخههای مدرن پایتون از **"کد ناهمزمان"** با چیزی که بهش **"کروتین"** میگن پشتیبانی میکنن، با سینتکس **`async` و `await`**. |
|||
|
|||
حالا باید بیشتر برات معنی بده. ✨ |
|||
|
|||
همه اینا چیزیه که به FastAPI (از طریق Starlette) قدرت میده و باعث میشه عملکرد چشمگیری داشته باشه. |
|||
|
|||
## جزئیات خیلی فنی |
|||
|
|||
/// warning |
|||
|
|||
احتمالاً میتونی اینو رد کنی. |
|||
|
|||
اینا جزئیات خیلی فنی از نحوه کار **FastAPI** زیر پوستهست. |
|||
|
|||
اگه یه کم دانش فنی (کروتینها، نخها، بلاک کردن و غیره) داری و کنجکاوی که FastAPI چطور `async def` رو در مقابل `def` معمولی مدیریت میکنه، ادامه بده. |
|||
|
|||
/// |
|||
|
|||
### توابع عملیات مسیر |
|||
|
|||
وقتی یه *تابع عملیات مسیر* رو با `def` معمولی به جای `async def` تعریف میکنی، توی یه استخر نخ خارجی اجرا میشه که بعدش منتظرش میمونن، به جای اینکه مستقیم صداش کنن (چون سرور رو بلاک میکنه). |
|||
|
|||
اگه از یه فریمورک ناهمزمان دیگه میای که به روش بالا کار نمیکنه و عادت داری *توابع عملیات مسیر* ساده فقط محاسباتی رو با `def` معمولی برای یه سود کوچیک عملکرد (حدود 100 نانوثانیه) تعریف کنی، توجه کن که توی **FastAPI** اثرش کاملاً برعکسه. توی این موارد، بهتره از `async def` استفاده کنی مگه اینکه *توابع عملیات مسیرت* کدی داشته باشن که عملیات <abbr title="ورودی/خروجی: خوندن یا نوشتن دیسک، ارتباطات شبکه">I/O</abbr> بلاککننده انجام بده. |
|||
|
|||
با این حال، توی هر دو موقعیت، احتمالش زیاده که **FastAPI** هنوز [سریعتر](index.md#performance){.internal-link target=_blank} از فریمورک قبلیات باشه (یا حداقل قابل مقایسه باهاش). |
|||
|
|||
### وابستگیها |
|||
|
|||
همین برای [وابستگیها](tutorial/dependencies/index.md){.internal-link target=_blank} هم صدق میکنه. اگه یه وابستگی یه تابع `def` معمولی به جای `async def` باشه، توی استخر نخ خارجی اجرا میشه. |
|||
|
|||
### زیروابستگیها |
|||
|
|||
میتونی چند تا وابستگی و [زیروابستگی](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} داشته باشی که همدیگه رو نیاز دارن (بهعنوان پارامترهای تعریف تابع)، بعضیهاشون ممکنه با `async def` ساخته بشن و بعضیها با `def` معمولی. بازم کار میکنه، و اونایی که با `def` معمولی ساخته شدن توی یه نخ خارجی (از استخر نخ) صدا زده میشن به جای اینکه "منتظرشون" بمونن. |
|||
|
|||
### توابع کاربردی دیگه |
|||
|
|||
هر تابع کاربردی دیگهای که مستقیم خودت صداش میکنی میتونه با `def` معمولی یا `async def` ساخته بشه و FastAPI رو نحوه صدازدنش تأثیر نمیذاره. |
|||
|
|||
این برخلاف توابعی هست که FastAPI برات صداشون میکنه: *توابع عملیات مسیر* و وابستگیها. |
|||
|
|||
اگه تابع کاربردیت یه تابع معمولی با `def` باشه، مستقیم صداش میکنن (همونطور که توی کدت نوشتی)، نه توی استخر نخ، اگه تابع با `async def` ساخته شده باشه، باید وقتی توی کدت صداش میکنی `await`ش کنی. |
|||
|
|||
--- |
|||
|
|||
دوباره، اینا جزئیات خیلی فنی هستن که احتمالاً اگه دنبالشون اومده باشی برات مفید باشن. |
|||
|
|||
وگرنه، با راهنماییهای بخش بالا باید خوب باشی: <a href="#in-a-hurry">عجله داری؟</a>. |
@ -0,0 +1,298 @@ |
|||
# متغیرهای محیطی |
|||
|
|||
/// tip |
|||
|
|||
اگه از قبل میدونی متغیرهای محیطی چی هستن و چطور ازشون استفاده میشه، میتونی این بخش رو رد کنی. |
|||
|
|||
/// |
|||
|
|||
یه متغیر محیطی (که بهش "**env var**" هم میگن) یه متغیریه که **خارج** از کد پایتون، توی **سیستمعامل** زندگی میکنه و میتونه توسط کد پایتونت (یا برنامههای دیگه) خونده بشه. |
|||
|
|||
متغیرهای محیطی میتونن برای مدیریت **تنظیمات** برنامه، بهعنوان بخشی از **نصب** پایتون و غیره مفید باشن. |
|||
|
|||
## ساخت و استفاده از متغیرهای محیطی |
|||
|
|||
میتونی متغیرهای محیطی رو توی **شل (ترمینال)** **بسازی** و ازشون استفاده کنی، بدون اینکه به پایتون نیاز داشته باشی: |
|||
|
|||
//// tab | لینوکس، مکاواس، ویندوز بش |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// میتونی یه متغیر محیطی به اسم MY_NAME بسازی با |
|||
$ export MY_NAME="Wade Wilson" |
|||
|
|||
// بعد میتونی با برنامههای دیگه ازش استفاده کنی، مثل |
|||
$ echo "Hello $MY_NAME" |
|||
|
|||
Hello Wade Wilson |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
//// tab | ویندوز پاورشل |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// یه متغیر محیطی به اسم MY_NAME بساز |
|||
$ $Env:MY_NAME = "Wade Wilson" |
|||
|
|||
// با برنامههای دیگه ازش استفاده کن، مثل |
|||
$ echo "Hello $Env:MY_NAME" |
|||
|
|||
Hello Wade Wilson |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
## خوندن متغیرهای محیطی توی پایتون |
|||
|
|||
میتونی متغیرهای محیطی رو **خارج** از پایتون، توی ترمینال (یا با هر روش دیگه) بسازی، و بعد توی **پایتون** اونا رو بخونی. |
|||
|
|||
مثلاً میتونی یه فایل `main.py` داشته باشی با: |
|||
|
|||
```Python hl_lines="3" |
|||
import os |
|||
|
|||
name = os.getenv("MY_NAME", "World") |
|||
print(f"Hello {name} from Python") |
|||
``` |
|||
|
|||
/// tip |
|||
|
|||
آرگومان دوم <a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> مقدار پیشفرضیه که برمیگردونه. |
|||
|
|||
اگه ندی، بهصورت پیشفرض `None` هست، اینجا ما `"World"` رو بهعنوان مقدار پیشفرض گذاشتیم. |
|||
|
|||
/// |
|||
|
|||
بعد میتونی اون برنامه پایتون رو صدا کنی: |
|||
|
|||
//// tab | لینوکس، مکاواس، ویندوز بش |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// اینجا هنوز متغیر محیطی رو تنظیم نکردیم |
|||
$ python main.py |
|||
|
|||
// چون متغیر محیطی رو تنظیم نکردیم، مقدار پیشفرض رو میگیریم |
|||
|
|||
Hello World from Python |
|||
|
|||
// ولی اگه اول یه متغیر محیطی بسازیم |
|||
$ export MY_NAME="Wade Wilson" |
|||
|
|||
// و بعد دوباره برنامه رو صدا کنیم |
|||
$ python main.py |
|||
|
|||
// حالا میتونه متغیر محیطی رو بخونه |
|||
|
|||
Hello Wade Wilson from Python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
//// tab | ویندوز پاورشل |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// اینجا هنوز متغیر محیطی رو تنظیم نکردیم |
|||
$ python main.py |
|||
|
|||
// چون متغیر محیطی رو تنظیم نکردیم، مقدار پیشفرض رو میگیریم |
|||
|
|||
Hello World from Python |
|||
|
|||
// ولی اگه اول یه متغیر محیطی بسازیم |
|||
$ $Env:MY_NAME = "Wade Wilson" |
|||
|
|||
// و بعد دوباره برنامه رو صدا کنیم |
|||
$ python main.py |
|||
|
|||
// حالا میتونه متغیر محیطی رو بخونه |
|||
|
|||
Hello Wade Wilson from Python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
چون متغیرهای محیطی میتونن خارج از کد تنظیم بشن، ولی کد میتونه اونا رو بخونه، و لازم نیست با بقیه فایلها ذخیره (کمیتی به `git`) بشن، معمولاً برای پیکربندی یا **تنظیمات** استفاده میشن. |
|||
|
|||
همچنین میتونی یه متغیر محیطی رو فقط برای **یه اجرای خاص برنامه** بسازی، که فقط برای اون برنامه و فقط برای مدت زمان اجراش در دسترسه. |
|||
|
|||
برای این کار، درست قبل از خود برنامه، توی همون خط بسازش: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// یه متغیر محیطی MY_NAME رو توی خط برای این اجرای برنامه بساز |
|||
$ MY_NAME="Wade Wilson" python main.py |
|||
|
|||
// حالا میتونه متغیر محیطی رو بخونه |
|||
|
|||
Hello Wade Wilson from Python |
|||
|
|||
// متغیر محیطی بعدش دیگه وجود نداره |
|||
$ python main.py |
|||
|
|||
Hello World from Python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
/// tip |
|||
|
|||
میتونی بیشتر در موردش توی <a href="https://12factor.net/config" class="external-link" target="_blank">برنامه دوازدهفاکتوری: پیکربندی</a> بخونی. |
|||
|
|||
/// |
|||
|
|||
## نوعها و اعتبارسنجی |
|||
|
|||
این متغیرهای محیطی فقط میتونن **رشتههای متنی** رو نگه دارن، چون خارج از پایتون هستن و باید با برنامههای دیگه و بقیه سیستم (و حتی سیستمعاملهای مختلف مثل لینوکس، ویندوز، مکاواس) سازگار باشن. |
|||
|
|||
یعنی **هر مقداری** که توی پایتون از یه متغیر محیطی خونده میشه یه `str` هست، و هر تبدیل به نوع دیگه یا هر اعتبارسنجی باید توی کد انجام بشه. |
|||
|
|||
توی [راهنمای کاربر پیشرفته - تنظیمات و متغیرهای محیطی](./advanced/settings.md){.internal-link target=_blank} بیشتر در مورد استفاده از متغیرهای محیطی برای مدیریت **تنظیمات برنامه** یاد میگیری. |
|||
|
|||
## متغیر محیطی `PATH` |
|||
|
|||
یه متغیر محیطی **خاص** به اسم **`PATH`** وجود داره که سیستمعاملها (لینوکس، مکاواس، ویندوز) ازش برای پیدا کردن برنامههایی که قراره اجرا بشن استفاده میکنن. |
|||
|
|||
مقدار متغیر `PATH` یه رشته طولانی از پوشههاست که توی لینوکس و مکاواس با دونقطه `:` و توی ویندوز با نقطهویرگول `;` از هم جدا شدن. |
|||
|
|||
مثلاً، متغیر محیطی `PATH` میتونه اینجوری باشه: |
|||
|
|||
//// tab | لینوکس، مکاواس |
|||
|
|||
```plaintext |
|||
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin |
|||
``` |
|||
|
|||
یعنی سیستم باید دنبال برنامهها توی این پوشهها بگرده: |
|||
|
|||
* `/usr/local/bin` |
|||
* `/usr/bin` |
|||
* `/bin` |
|||
* `/usr/sbin` |
|||
* `/sbin` |
|||
|
|||
//// |
|||
|
|||
//// tab | ویندوز |
|||
|
|||
```plaintext |
|||
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 |
|||
``` |
|||
|
|||
یعنی سیستم باید دنبال برنامهها توی این پوشهها بگرده: |
|||
|
|||
* `C:\Program Files\Python312\Scripts` |
|||
* `C:\Program Files\Python312` |
|||
* `C:\Windows\System32` |
|||
|
|||
//// |
|||
|
|||
وقتی یه **دستور** توی ترمینال تایپ میکنی، سیستمعامل **دنبال** برنامه توی **هر کدوم از این پوشهها** که توی متغیر محیطی `PATH` لیست شدن میگرده. |
|||
|
|||
مثلاً، وقتی توی ترمینال `python` تایپ میکنی، سیستمعامل دنبال یه برنامه به اسم `python` توی **اولین پوشه** توی اون لیست میگرده. |
|||
|
|||
اگه پیداش کنه، **استفادهش میکنه**. وگرنه توی **پوشههای بعدی** دنبالش میگرده. |
|||
|
|||
### نصب پایتون و بهروزرسانی `PATH` |
|||
|
|||
وقتی پایتون رو نصب میکنی، ممکنه ازت بپرسن آیا میخوای متغیر محیطی `PATH` رو بهروزرسانی کنی. |
|||
|
|||
//// tab | لینوکس، مکاواس |
|||
|
|||
فرض کن پایتون رو نصب کردی و توی یه پوشه `/opt/custompython/bin` قرار گرفته. |
|||
|
|||
اگه بگی بله برای بهروزرسانی متغیر محیطی `PATH`، نصاب `/opt/custompython/bin` رو به متغیر محیطی `PATH` اضافه میکنه. |
|||
|
|||
ممکنه اینجوری بشه: |
|||
|
|||
```plaintext |
|||
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin |
|||
``` |
|||
|
|||
اینجوری، وقتی توی ترمینال `python` تایپ میکنی، سیستم برنامه پایتون رو توی `/opt/custompython/bin` (آخرین پوشه) پیدا میکنه و از اون استفاده میکنه. |
|||
|
|||
//// |
|||
|
|||
//// tab | ویندوز |
|||
|
|||
فرض کن پایتون رو نصب کردی و توی یه پوشه `C:\opt\custompython\bin` قرار گرفته. |
|||
|
|||
اگه بگی بله برای بهروزرسانی متغیر محیطی `PATH`، نصاب `C:\opt\custompython\bin` رو به متغیر محیطی `PATH` اضافه میکنه. |
|||
|
|||
```plaintext |
|||
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin |
|||
``` |
|||
|
|||
اینجوری، وقتی توی ترمینال `python` تایپ میکنی، سیستم برنامه پایتون رو توی `C:\opt\custompython\bin` (آخرین پوشه) پیدا میکنه و از اون استفاده میکنه. |
|||
|
|||
//// |
|||
|
|||
پس، اگه تایپ کنی: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// tab | لینوکس، مکاواس |
|||
|
|||
سیستم برنامه `python` رو توی `/opt/custompython/bin` **پیدا** میکنه و اجراش میکنه. |
|||
|
|||
تقریباً معادل اینه که تایپ کنی: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ /opt/custompython/bin/python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
//// tab | ویندوز |
|||
|
|||
سیستم برنامه `python` رو توی `C:\opt\custompython\bin\python` **پیدا** میکنه و اجراش میکنه. |
|||
|
|||
تقریباً معادل اینه که تایپ کنی: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ C:\opt\custompython\bin\python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
این اطلاعات وقتی در مورد [محیطهای مجازی](virtual-environments.md){.internal-link target=_blank} یاد میگیری بهدردت میخوره. |
|||
|
|||
## نتیجهگیری |
|||
|
|||
با این باید یه درک پایهای از **متغیرهای محیطی** و نحوه استفادهشون توی پایتون داشته باشی. |
|||
|
|||
میتونی بیشتر در موردشون توی <a href="https://en.wikipedia.org/wiki/Environment_variable" class="external-link" target="_blank">ویکیپدیا برای متغیر محیطی</a> بخونی. |
|||
|
|||
توی خیلی موارد مشخص نیست که متغیرهای محیطی چطور میتونن فوری مفید و کاربردی باشن. ولی توی موقعیتهای مختلف توسعه مدام پیداشون میشه، پس خوبه که در موردشون بدونی. |
|||
|
|||
مثلاً، توی بخش بعدی در مورد [محیطهای مجازی](virtual-environments.md) به این اطلاعات نیاز داری. |
@ -0,0 +1,5 @@ |
|||
# یادگیری |
|||
|
|||
اینجا بخشهای مقدماتی و آموزشهایی هستن که برای یادگیری **FastAPI** بهت کمک میکنن. |
|||
|
|||
میتونی اینو یه **کتاب**، یه **دوره آموزشی**، یا راه **رسمی** و پیشنهادی برای یادگیری FastAPI در نظر بگیری. 😎 |
@ -0,0 +1,578 @@ |
|||
# مقدمهای بر انواع نوع در پایتون |
|||
|
|||
پایتون از "نوعنما"های اختیاری (که بهشون "type hints" یا "type annotations" هم میگن) پشتیبانی میکنه. |
|||
|
|||
این **"نوعنماها"** یا annotationها یه سینتکس خاص هستن که بهت اجازه میدن <abbr title="مثلاً: str, int, float, bool">نوع</abbr> یه متغیر رو مشخص کنی. |
|||
|
|||
با مشخص کردن نوع متغیرها، ویرایشگرها و ابزارها میتونن پشتیبانی بهتری بهت بدن. |
|||
|
|||
این فقط یه **آموزش سریع / یادآوری** در مورد نوعنماهای پایتونه. فقط حداقل چیزایی که برای استفاده ازشون با **FastAPI** لازمه رو پوشش میده... که در واقع خیلی کمه. |
|||
|
|||
**FastAPI** کاملاً بر پایه این نوعنماهاست و این بهش کلی مزیت و فایده میده. |
|||
|
|||
ولی حتی اگه هیچوقت از **FastAPI** استفاده نکنی، بازم یادگیری یه کم در موردشون به نفعته. |
|||
|
|||
/// note |
|||
|
|||
اگه حرفهای پایتونی و همهچیز رو در مورد نوعنماها میدونی، برو سراغ فصل بعدی. |
|||
|
|||
/// |
|||
|
|||
## انگیزه |
|||
|
|||
بیاید با یه مثال ساده شروع کنیم: |
|||
|
|||
{* ../../docs_src/python_types/tutorial001.py *} |
|||
|
|||
وقتی این برنامه رو اجرا کنی، خروجی اینه: |
|||
|
|||
``` |
|||
John Doe |
|||
``` |
|||
|
|||
این تابع این کارا رو میکنه: |
|||
|
|||
* یه `first_name` و `last_name` میگیره. |
|||
* حرف اول هر کدوم رو با `title()` بزرگ میکنه. |
|||
* <abbr title="اونا رو کنار هم میذاره، یکی بعد از اون یکی.">ترکیبشون</abbr> میکنه با یه فاصله وسطشون. |
|||
|
|||
{* ../../docs_src/python_types/tutorial001.py hl[2] *} |
|||
|
|||
### ویرایشش کن |
|||
|
|||
این یه برنامه خیلی سادهست. |
|||
|
|||
ولی حالا تصور کن داری از صفر مینویسیش. |
|||
|
|||
یه جایی شروع کردی به تعریف تابع، پارامترهات آمادهست... |
|||
|
|||
ولی بعد باید "اون متدی که حرف اول رو بزرگ میکنه" رو صدا کنی. |
|||
|
|||
آیا اسمش `upper` بود؟ یا `uppercase`؟ شاید `first_uppercase`؟ یا `capitalize`؟ |
|||
|
|||
بعد، با دوست قدیمی برنامهنویسا، تکمیل خودکار ویرایشگر، امتحان میکنی. |
|||
|
|||
پارامتر اول تابع، `first_name` رو تایپ میکنی، بعد یه نقطه (`.`) میذاری و `Ctrl+Space` رو میزنی تا تکمیل خودکار بیاد. |
|||
|
|||
ولی متأسفانه، چیز مفیدی نمیگیری: |
|||
|
|||
<img src="/img/python-types/image01.png"> |
|||
|
|||
### نوع اضافه کن |
|||
|
|||
بیا فقط یه خط از نسخه قبلی رو تغییر بدیم. |
|||
|
|||
دقیقاً این بخش، پارامترهای تابع رو، از: |
|||
|
|||
```Python |
|||
first_name, last_name |
|||
``` |
|||
|
|||
به: |
|||
|
|||
```Python |
|||
first_name: str, last_name: str |
|||
``` |
|||
|
|||
عوض میکنیم. |
|||
|
|||
همینه. |
|||
|
|||
اینا همون "نوعنماها" هستن: |
|||
|
|||
{* ../../docs_src/python_types/tutorial002.py hl[1] *} |
|||
|
|||
این با تعریف مقدار پیشفرض فرق داره، مثل: |
|||
|
|||
```Python |
|||
first_name="john", last_name="doe" |
|||
``` |
|||
|
|||
یه چیز متفاوته. |
|||
|
|||
ما از دونقطه (`:`) استفاده میکنیم، نه علامت مساوی (`=`). |
|||
|
|||
و اضافه کردن نوعنماها معمولاً چیزی که اتفاق میافته رو از چیزی که بدون اونا میافتاد تغییر نمیده. |
|||
|
|||
ولی حالا، دوباره تصور کن وسط ساختن اون تابع هستی، ولی این بار با نوعنماها. |
|||
|
|||
توی همون نقطه، سعی میکنی تکمیل خودکار رو با `Ctrl+Space` فعال کنی و اینو میبینی: |
|||
|
|||
<img src="/img/python-types/image02.png"> |
|||
|
|||
با این، میتونی اسکرول کنی، گزینهها رو ببینی، تا وقتی که اون چیزی که "به نظرت آشنا میاد" رو پیدا کنی: |
|||
|
|||
<img src="/img/python-types/image03.png"> |
|||
|
|||
## انگیزه بیشتر |
|||
|
|||
این تابع رو چک کن، الان نوعنما داره: |
|||
|
|||
{* ../../docs_src/python_types/tutorial003.py hl[1] *} |
|||
|
|||
چون ویرایشگر نوع متغیرها رو میدونه، فقط تکمیل خودکار نمیگیری، بلکه چک خطاها هم داری: |
|||
|
|||
<img src="/img/python-types/image04.png"> |
|||
|
|||
حالا میدونی که باید درستش کنی، `age` رو با `str(age)` به یه رشته تبدیل کنی: |
|||
|
|||
{* ../../docs_src/python_types/tutorial004.py hl[2] *} |
|||
|
|||
## تعریف نوعها |
|||
|
|||
تازه اصلیترین جا برای تعریف نوعنماها رو دیدی. بهعنوان پارامترهای تابع. |
|||
|
|||
این هم اصلیترین جاییه که با **FastAPI** ازشون استفاده میکنی. |
|||
|
|||
### نوعهای ساده |
|||
|
|||
میتونی همه نوعهای استاندارد پایتون رو تعریف کنی، نه فقط `str`. |
|||
|
|||
مثلاً میتونی از اینا استفاده کنی: |
|||
|
|||
* `int` |
|||
* `float` |
|||
* `bool` |
|||
* `bytes` |
|||
|
|||
{* ../../docs_src/python_types/tutorial005.py hl[1] *} |
|||
|
|||
### نوعهای عمومی با پارامترهای نوع |
|||
|
|||
یه سری ساختار داده هستن که میتونن مقدارهای دیگه رو نگه دارن، مثل `dict`، `list`، `set` و `tuple`. و مقدارهای داخلیشون هم میتونن نوع خودشون رو داشته باشن. |
|||
|
|||
به این نوعها که نوعهای داخلی دارن میگن "**عمومی**" یا "generic". و میشه اونا رو تعریف کرد، حتی با نوعهای داخلیشون. |
|||
|
|||
برای تعریف این نوعها و نوعهای داخلیشون، میتونی از ماژول استاندارد پایتون `typing` استفاده کنی. این ماژول مخصوص پشتیبانی از نوعنماهاست. |
|||
|
|||
#### نسخههای جدیدتر پایتون |
|||
|
|||
سینتکس با استفاده از `typing` با همه نسخهها، از پایتون 3.6 تا جدیدترینها، از جمله پایتون 3.9، 3.10 و غیره **سازگاره**. |
|||
|
|||
با پیشرفت پایتون، **نسخههای جدیدتر** پشتیبانی بهتری برای این نوعنماها دارن و توی خیلی موارد حتی لازم نیست ماژول `typing` رو وارد کنی و ازش برای تعریف نوعنماها استفاده کنی. |
|||
|
|||
اگه بتونی برای پروژهات از یه نسخه جدیدتر پایتون استفاده کنی، میتونی از این سادگی اضافه بهره ببری. |
|||
|
|||
توی همه مستندات، مثالهایی هستن که با هر نسخه پایتون سازگارن (وقتی تفاوتی هست). |
|||
|
|||
مثلاً "**Python 3.6+**" یعنی با پایتون 3.6 یا بالاتر (مثل 3.7، 3.8، 3.9، 3.10 و غیره) سازگاره. و "**Python 3.9+**" یعنی با پایتون 3.9 یا بالاتر (مثل 3.10 و غیره) سازگاره. |
|||
|
|||
اگه بتونی از **جدیدترین نسخههای پایتون** استفاده کنی، از مثالهای نسخه آخر استفاده کن، چون اونا **بهترین و سادهترین سینتکس** رو دارن، مثلاً "**Python 3.10+**". |
|||
|
|||
#### لیست |
|||
|
|||
مثلاً، بیایم یه متغیر تعریف کنیم که یه `list` از `str` باشه. |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
متغیر رو با همون سینتکس دونقطه (`:`) تعریف کن. |
|||
|
|||
بهعنوان نوع، `list` رو بذار. |
|||
|
|||
چون لیست یه نوعه که نوعهای داخلی داره، اونا رو توی کروشهها میذاری: |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../docs_src/python_types/tutorial006_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
از `typing`، `List` رو (با `L` بزرگ) وارد کن: |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../docs_src/python_types/tutorial006.py!} |
|||
``` |
|||
|
|||
متغیر رو با همون سینتکس دونقطه (`:`) تعریف کن. |
|||
|
|||
بهعنوان نوع، `List` رو که از `typing` وارد کردی بذار. |
|||
|
|||
چون لیست یه نوعه که نوعهای داخلی داره، اونا رو توی کروشهها میذاری: |
|||
|
|||
```Python hl_lines="4" |
|||
{!> ../../docs_src/python_types/tutorial006.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
/// info |
|||
|
|||
اون نوعهای داخلی توی کروشهها بهشون "پارامترهای نوع" میگن. |
|||
|
|||
توی این مورد، `str` پارامتر نوعیه که به `List` (یا `list` توی پایتون 3.9 و بالاتر) پاس داده شده. |
|||
|
|||
/// |
|||
|
|||
یعنی: "متغیر `items` یه `list` هست، و هر کدوم از آیتمهای این لیست یه `str` هستن". |
|||
|
|||
/// tip |
|||
|
|||
اگه از پایتون 3.9 یا بالاتر استفاده میکنی، لازم نیست `List` رو از `typing` وارد کنی، میتونی همون نوع معمولی `list` رو به جاش استفاده کنی. |
|||
|
|||
/// |
|||
|
|||
با این کار، ویرایشگرت حتی وقتی داری آیتمهای لیست رو پردازش میکنی بهت کمک میکنه: |
|||
|
|||
<img src="/img/python-types/image05.png"> |
|||
|
|||
بدون نوعها، رسیدن به این تقریباً غیرممکنه. |
|||
|
|||
توجه کن که متغیر `item` یکی از عناصر توی لیست `items` هست. |
|||
|
|||
و با این حال، ویرایشگر میدونه که یه `str` هست و براش پشتیبانی میده. |
|||
|
|||
#### تاپل و ست |
|||
|
|||
برای تعریف `tuple`ها و `set`ها هم همین کار رو میکنی: |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../docs_src/python_types/tutorial007_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../docs_src/python_types/tutorial007.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
یعنی: |
|||
|
|||
* متغیر `items_t` یه `tuple` با 3 تا آیتمه، یه `int`، یه `int` دیگه، و یه `str`. |
|||
* متغیر `items_s` یه `set` هست، و هر کدوم از آیتمهاش از نوع `bytes` هستن. |
|||
|
|||
#### دیکشنری |
|||
|
|||
برای تعریف یه `dict`، 2 تا پارامتر نوع میدی، که با کاما از هم جدا شدن. |
|||
|
|||
پارامتر نوع اول برای کلیدهای `dict` هست. |
|||
|
|||
پارامتر نوع دوم برای مقدارهای `dict` هست: |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../docs_src/python_types/tutorial008_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../docs_src/python_types/tutorial008.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
یعنی: |
|||
|
|||
* متغیر `prices` یه `dict` هست: |
|||
* کلیدهای این `dict` از نوع `str` هستن (مثلاً اسم هر آیتم). |
|||
* مقدارهای این `dict` از نوع `float` هستن (مثلاً قیمت هر آیتم). |
|||
|
|||
#### اتحادیه |
|||
|
|||
میتونی تعریف کنی که یه متغیر میتونه هر کدوم از **چند تا نوع** باشه، مثلاً یه `int` یا یه `str`. |
|||
|
|||
توی پایتون 3.6 و بالاتر (از جمله پایتون 3.10) میتونی از نوع `Union` توی `typing` استفاده کنی و نوعهای ممکن رو توی کروشهها بذاری. |
|||
|
|||
توی پایتون 3.10 یه **سینتکس جدید** هم هست که میتونی نوعهای ممکن رو با یه <abbr title="بهش 'عملگر بیتی یا' هم میگن، ولی اینجا معنیش مهم نیست">خط عمودی (`|`)</abbr> جدا کنی. |
|||
|
|||
//// tab | Python 3.10+ |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../docs_src/python_types/tutorial008b_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../docs_src/python_types/tutorial008b.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
توی هر دو حالت یعنی `item` میتونه یه `int` یا یه `str` باشه. |
|||
|
|||
#### شاید `None` |
|||
|
|||
میتونی تعریف کنی که یه مقدار میتونه یه نوع باشه، مثلاً `str`، ولی میتونه `None` هم باشه. |
|||
|
|||
توی پایتون 3.6 و بالاتر (از جمله پایتون 3.10) میتونی با وارد کردن و استفاده از `Optional` از ماژول `typing` اینو تعریف کنی. |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!../../docs_src/python_types/tutorial009.py!} |
|||
``` |
|||
|
|||
استفاده از `Optional[str]` به جای فقط `str` به ویرایشگر کمک میکنه خطاهایی که ممکنه فکر کنی یه مقدار همیشه `str` هست رو پیدا کنه، در حالی که میتونه `None` هم باشه. |
|||
|
|||
`Optional[Something]` در واقع میانبر برای `Union[Something, None]` هست، این دو تا معادلن. |
|||
|
|||
یعنی توی پایتون 3.10، میتونی از `Something | None` استفاده کنی: |
|||
|
|||
//// tab | Python 3.10+ |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../docs_src/python_types/tutorial009_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// 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!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
#### استفاده از `Union` یا `Optional` |
|||
|
|||
اگه از نسخه پایتون زیر 3.10 استفاده میکنی، یه نکته از دید خیلی **شخصی** خودم: |
|||
|
|||
* 🚨 از `Optional[SomeType]` استفاده نکن |
|||
* به جاش ✨ **از `Union[SomeType, None]` استفاده کن** ✨. |
|||
|
|||
هر دو معادلن و زیر پوسته یکیان، ولی من `Union` رو به `Optional` ترجیح میدم چون کلمه "**اختیاری**" انگار暗示 میکنه که مقدار اختیاریه، در حالی که در واقع یعنی "میتونه `None` باشه"، حتی اگه اختیاری نباشه و هنوز لازم باشه. |
|||
|
|||
فکر میکنم `Union[SomeType, None]` واضحتر نشون میده چی معنی میده. |
|||
|
|||
فقط بحث کلمات و اسمهاست. ولی این کلمات میتونن رو طرز فکر تو و تیمت نسبت به کد تأثیر بذارن. |
|||
|
|||
بهعنوان مثال، این تابع رو ببین: |
|||
|
|||
{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *} |
|||
|
|||
پارامتر `name` بهعنوان `Optional[str]` تعریف شده، ولی **اختیاری نیست**، نمیتونی تابع رو بدون پارامتر صدا کنی: |
|||
|
|||
```Python |
|||
say_hi() # اوه نه، این خطا میده! 😱 |
|||
``` |
|||
|
|||
پارامتر `name` **هنوز لازمه** (نه *اختیاری*) چون مقدار پیشفرض نداره. با این حال، `name` مقدار `None` رو قبول میکنه: |
|||
|
|||
```Python |
|||
say_hi(name=None) # این کار میکنه، None معتبره 🎉 |
|||
``` |
|||
|
|||
خبر خوب اینه که وقتی رو پایتون 3.10 باشی، لازم نیست نگران این باشی، چون میتونی بهسادگی از `|` برای تعریف اتحادیه نوعها استفاده کنی: |
|||
|
|||
{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} |
|||
|
|||
اون موقع دیگه لازم نیست نگران اسمهایی مثل `Optional` و `Union` باشی. 😎 |
|||
|
|||
#### نوعهای عمومی |
|||
|
|||
این نوعهایی که پارامترهای نوع رو توی کروشهها میگیرن بهشون **نوعهای عمومی** یا **Generics** میگن، مثلاً: |
|||
|
|||
//// tab | Python 3.10+ |
|||
|
|||
میتونی از همون نوعهای داخلی بهعنوان نوعهای عمومی استفاده کنی (با کروشهها و نوعها داخلشون): |
|||
|
|||
* `list` |
|||
* `tuple` |
|||
* `set` |
|||
* `dict` |
|||
|
|||
و همونطور که توی پایتون 3.8 بود، از ماژول `typing`: |
|||
|
|||
* `Union` |
|||
* `Optional` (همونطور که توی پایتون 3.8 بود) |
|||
* ...و بقیه. |
|||
|
|||
توی پایتون 3.10، بهعنوان جایگزین برای استفاده از نوعهای عمومی `Union` و `Optional`، میتونی از <abbr title="بهش 'عملگر بیتی یا' هم میگن، ولی اینجا معنیش مهم نیست">خط عمودی (`|`)</abbr> برای تعریف اتحادیه نوعها استفاده کنی، که خیلی بهتر و سادهتره. |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
میتونی از همون نوعهای داخلی بهعنوان نوعهای عمومی استفاده کنی (با کروشهها و نوعها داخلشون): |
|||
|
|||
* `list` |
|||
* `tuple` |
|||
* `set` |
|||
* `dict` |
|||
|
|||
و همونطور که توی پایتون 3.8 بود، از ماژول `typing`: |
|||
|
|||
* `Union` |
|||
* `Optional` |
|||
* ...و بقیه. |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
* `List` |
|||
* `Tuple` |
|||
* `Set` |
|||
* `Dict` |
|||
* `Union` |
|||
* `Optional` |
|||
* ...و بقیه. |
|||
|
|||
//// |
|||
|
|||
### کلاسها بهعنوان نوع |
|||
|
|||
میتونی یه کلاس رو هم بهعنوان نوع یه متغیر تعریف کنی. |
|||
|
|||
فرض کن یه کلاس `Person` داری، با یه نام: |
|||
|
|||
{* ../../docs_src/python_types/tutorial010.py hl[1:3] *} |
|||
|
|||
بعد میتونی یه متغیر رو از نوع `Person` تعریف کنی: |
|||
|
|||
{* ../../docs_src/python_types/tutorial010.py hl[6] *} |
|||
|
|||
و بعد، دوباره، همه پشتیبانی ویرایشگر رو داری: |
|||
|
|||
<img src="/img/python-types/image06.png"> |
|||
|
|||
توجه کن که این یعنی "`one_person` یه **نمونه** از کلاس `Person` هست". |
|||
|
|||
یعنی "`one_person` خود **کلاس** به اسم `Person` نیست". |
|||
|
|||
## مدلهای Pydantic |
|||
|
|||
<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> یه کتابخونه پایتونه برای اعتبارسنجی دادهها. |
|||
|
|||
"شکل" دادهها رو بهعنوان کلاسهایی با ویژگیها تعریف میکنی. |
|||
|
|||
و هر ویژگی یه نوع داره. |
|||
|
|||
بعد یه نمونه از اون کلاس رو با یه سری مقدار میسازی و اون مقدارها رو اعتبارسنجی میکنه، به نوع مناسب تبدیلشون میکنه (اگه لازم باشه) و یه شیء با همه دادهها بهت میده. |
|||
|
|||
و با اون شیء نهایی همه پشتیبانی ویرایشگر رو میگیری. |
|||
|
|||
یه مثال از مستندات رسمی Pydantic: |
|||
|
|||
//// tab | Python 3.10+ |
|||
|
|||
```Python |
|||
{!> ../../docs_src/python_types/tutorial011_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python |
|||
{!> ../../docs_src/python_types/tutorial011_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python |
|||
{!> ../../docs_src/python_types/tutorial011.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
/// info |
|||
|
|||
برای اطلاعات بیشتر در مورد <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic، مستنداتش رو چک کن</a>. |
|||
|
|||
/// |
|||
|
|||
**FastAPI** کاملاً بر پایه Pydantic هست. |
|||
|
|||
توی [آموزش - راهنمای کاربر](tutorial/index.md){.internal-link target=_blank} خیلی بیشتر از اینا رو توی عمل میبینی. |
|||
|
|||
/// tip |
|||
|
|||
Pydantic یه رفتار خاص داره وقتی از `Optional` یا `Union[Something, None]` بدون مقدار پیشفرض استفاده میکنی، میتونی توی مستندات Pydantic در مورد <a href="https://docs.pydantic.dev/2.3/usage/models/#required-fields" class="external-link" target="_blank">فیلدهای اختیاری لازم</a> بیشتر بخونی. |
|||
|
|||
/// |
|||
|
|||
## نوعنماها با Annotationهای متادیتا |
|||
|
|||
پایتون یه قابلیت هم داره که بهت اجازه میده **<abbr title="داده در مورد داده، اینجا یعنی اطلاعات در مورد نوع، مثلاً یه توضیح">متادیتا</abbr> اضافی** رو توی این نوعنماها بذاری با استفاده از `Annotated`. |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
توی پایتون 3.9، `Annotated` بخشی از کتابخونه استاندارده، پس میتونی از `typing` واردش کنی. |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../docs_src/python_types/tutorial013_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
توی نسخههای زیر پایتون 3.9، `Annotated` رو از `typing_extensions` وارد میکنی. |
|||
|
|||
با **FastAPI** از قبل نصب شده. |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../docs_src/python_types/tutorial013.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
خود پایتون با این `Annotated` کاری نمیکنه. و برای ویرایشگرها و ابزارهای دیگه، نوع هنوز `str` هست. |
|||
|
|||
ولی میتونی از این فضا توی `Annotated` استفاده کنی تا به **FastAPI** متادیتای اضافی در مورد اینکه چطور میخوای برنامهات رفتار کنه بدی. |
|||
|
|||
نکته مهم اینه که **اولین *پارامتر نوع*** که به `Annotated` میدی، **نوع واقعی** هست. بقیش فقط متادیتا برای ابزارهای دیگهست. |
|||
|
|||
الان فقط باید بدونی که `Annotated` وجود داره، و اینکه پایتون استاندارده. 😎 |
|||
|
|||
بعداً میبینی که چقدر **قوی** میتونه باشه. |
|||
|
|||
/// tip |
|||
|
|||
اینکه این **پایتون استاندارده** یعنی هنوز **بهترین تجربه توسعهدهنده** رو توی ویرایشگرت، با ابزارهایی که برای تحلیل و بازسازی کدت استفاده میکنی و غیره میگیری. ✨ |
|||
|
|||
و همینطور کدت با خیلی از ابزارها و کتابخونههای دیگه پایتون خیلی سازگار میمونه. 🚀 |
|||
|
|||
/// |
|||
|
|||
## نوعنماها توی **FastAPI** |
|||
|
|||
**FastAPI** از این نوعنماها استفاده میکنه تا چند تا کار بکنه. |
|||
|
|||
با **FastAPI** پارامترها رو با نوعنماها تعریف میکنی و اینا رو میگیری: |
|||
|
|||
* **پشتیبانی ویرایشگر**. |
|||
* **چک نوعها**. |
|||
|
|||
...و **FastAPI** از همون تعریفها برای اینا استفاده میکنه: |
|||
|
|||
* **تعریف نیازها**: از پارامترهای مسیر درخواست، پارامترهای کوئری، هدرها، بدنهها، وابستگیها و غیره. |
|||
* **تبدیل داده**: از درخواست به نوع مورد نیاز. |
|||
* **اعتبارسنجی داده**: که از هر درخواست میاد: |
|||
* تولید **خطاهای خودکار** که به کلاینت برمیگرده وقتی داده نامعتبره. |
|||
* **مستندسازی** API با استفاده از OpenAPI: |
|||
* که بعدش توسط رابطهای کاربری مستندات تعاملی خودکار استفاده میشه. |
|||
|
|||
اینا شاید همهش انتزاعی به نظر بیاد. نگران نباش. همه اینا رو توی عمل توی [آموزش - راهنمای کاربر](tutorial/index.md){.internal-link target=_blank} میبینی. |
|||
|
|||
نکته مهم اینه که با استفاده از نوعهای استاندارد پایتون، توی یه جا (به جای اضافه کردن کلاسهای بیشتر، دکوراتورها و غیره)، **FastAPI** کلی از کار رو برات انجام میده. |
|||
|
|||
/// info |
|||
|
|||
اگه همه آموزش رو گذروندی و برگشتی که بیشتر در مورد نوعها ببینی، یه منبع خوب <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">"تقلبنامه" از `mypy`</a> هست. |
|||
|
|||
/// |
@ -1,53 +1,165 @@ |
|||
# 이벤트: startup과 shutdown |
|||
# Lifespan 이벤트 |
|||
|
|||
필요에 따라 응용 프로그램이 시작되기 전이나 종료될 때 실행되는 이벤트 핸들러(함수)를 정의할 수 있습니다. |
|||
애플리케이션 **시작 전**에 실행되어야 하는 로직(코드)을 정의할 수 있습니다. 이는 이 코드가 **한 번**만 실행되며, **애플리케이션이 요청을 받기 시작하기 전**에 실행된다는 의미입니다. |
|||
|
|||
이 함수들은 `async def` 또는 평범하게 `def`으로 선언할 수 있습니다. |
|||
마찬가지로, 애플리케이션이 **종료될 때** 실행되어야 하는 로직(코드)을 정의할 수 있습니다. 이 경우, 이 코드는 **한 번**만 실행되며, **여러 요청을 처리한 후**에 실행됩니다. |
|||
|
|||
이 코드가 애플리케이션이 **요청을 받기 시작하기 전에** 실행되고, 요청 처리가 끝난 후 **종료 직전에** 실행되기 때문에 전체 애플리케이션의 **수명(Lifespan)**을 다룹니다. (잠시 후 "수명"이라는 단어가 중요해집니다 😉) |
|||
|
|||
이 방법은 전체 애플리케이션에서 사용해야 하는 **자원**을 설정하거나 요청 간에 **공유되는** 자원을 설정하고, 또는 그 후에 **정리**하는 데 매우 유용할 수 있습니다. 예를 들어, 데이터베이스 연결 풀 또는 공유되는 머신러닝 모델을 로드하는 경우입니다. |
|||
|
|||
|
|||
## 사용 사례 |
|||
|
|||
먼저 **사용 사례**를 예로 들어보고, 이를 어떻게 해결할 수 있는지 살펴보겠습니다. |
|||
|
|||
우리가 요청을 처리하기 위해 사용하고 싶은 **머신러닝 모델**이 있다고 상상해 봅시다. 🤖 |
|||
|
|||
이 모델들은 요청 간에 공유되므로, 요청마다 모델이 하나씩 있는 것이 아니라, 여러 요청에서 동일한 모델을 사용합니다. |
|||
|
|||
모델을 로드하는 데 **상당한 시간이 걸린다고 상상해 봅시다**, 왜냐하면 모델이 **디스크에서 많은 데이터를 읽어야** 하기 때문입니다. 따라서 모든 요청에 대해 모델을 매번 로드하고 싶지 않습니다. |
|||
|
|||
모듈/파일의 최상위에서 모델을 로드할 수도 있지만, 그러면 **모델을 로드하는데** 시간이 걸리기 때문에, 단순한 자동화된 테스트를 실행할 때도 모델이 로드될 때까지 기다려야 해서 **테스트 속도가 느려집니다**. |
|||
|
|||
이 문제를 해결하려고 하는 것입니다. 요청을 처리하기 전에 모델을 로드하되, 애플리케이션이 요청을 받기 시작하기 직전에만 로드하고, 코드가 로드되는 동안은 로드하지 않도록 하겠습니다. |
|||
|
|||
## Lifespan |
|||
|
|||
`FastAPI` 애플리케이션의 `lifespan` 매개변수와 "컨텍스트 매니저"를 사용하여 *시작*과 *종료* 로직을 정의할 수 있습니다. (컨텍스트 매니저가 무엇인지 잠시 후에 설명드리겠습니다.) |
|||
|
|||
예제를 통해 시작하고, 그 후에 자세히 살펴보겠습니다. |
|||
|
|||
우리는 `yield`를 사용하여 비동기 함수 `lifespan()`을 다음과 같이 생성합니다: |
|||
|
|||
{* ../../docs_src/events/tutorial003.py hl[16,19] *} |
|||
|
|||
여기서 우리는 모델을 로드하는 비싼 *시작* 작업을 시뮬레이션하고 있습니다. `yield` 앞에서 (가짜) 모델 함수를 머신러닝 모델이 담긴 딕셔너리에 넣습니다. 이 코드는 **애플리케이션이 요청을 받기 시작하기 전**, *시작* 동안에 실행됩니다. |
|||
|
|||
그리고 `yield` 직후에는 모델을 언로드합니다. 이 코드는 **애플리케이션이 요청 처리 완료 후**, *종료* 직전에 실행됩니다. 예를 들어, 메모리나 GPU와 같은 자원을 해제하는 작업을 할 수 있습니다. |
|||
|
|||
/// tip | 팁 |
|||
|
|||
`shutdown`은 애플리케이션을 **종료**할 때 발생합니다. |
|||
|
|||
새로운 버전을 시작해야 하거나, 그냥 실행을 멈추고 싶을 수도 있습니다. 🤷 |
|||
|
|||
/// |
|||
|
|||
### Lifespan 함수 |
|||
|
|||
먼저 주목할 점은, `yield`를 사용하여 비동기 함수(async function)를 정의하고 있다는 것입니다. 이는 `yield`를 사용한 의존성과 매우 유사합니다. |
|||
|
|||
{* ../../docs_src/events/tutorial003.py hl[14:19] *} |
|||
|
|||
함수의 첫 번째 부분, 즉 `yield` 이전의 코드는 애플리케이션이 시작되기 **전에** 실행됩니다. |
|||
|
|||
그리고 `yield` 이후의 부분은 애플리케이션이 완료된 후 **나중에** 실행됩니다. |
|||
|
|||
### 비동기 컨텍스트 매니저 |
|||
|
|||
함수를 확인해보면, `@asynccontextmanager`로 장식되어 있습니다. |
|||
|
|||
이것은 함수를 "**비동기 컨텍스트 매니저**"라고 불리는 것으로 변환시킵니다. |
|||
|
|||
{* ../../docs_src/events/tutorial003.py hl[1,13] *} |
|||
|
|||
파이썬에서 **컨텍스트 매니저**는 `with` 문에서 사용할 수 있는 것입니다. 예를 들어, `open()`은 컨텍스트 매니저로 사용할 수 있습니다: |
|||
|
|||
```Python |
|||
with open("file.txt") as file: |
|||
file.read() |
|||
``` |
|||
최근 버전의 파이썬에서는 **비동기 컨텍스트 매니저**도 있습니다. 이를 `async with`와 함께 사용합니다: |
|||
|
|||
```Python |
|||
async with lifespan(app): |
|||
await do_stuff() |
|||
``` |
|||
|
|||
컨텍스트 매니저나 위와 같은 비동기 컨텍스트 매니저를 만들면, `with` 블록에 들어가기 전에 `yield` 이전의 코드가 실행되고, `with` 블록을 벗어난 후에는 `yield` 이후의 코드가 실행됩니다. |
|||
|
|||
위의 코드 예제에서는 직접 사용하지 않고, FastAPI에 전달하여 사용하도록 합니다. |
|||
|
|||
`FastAPI` 애플리케이션의 `lifespan` 매개변수는 **비동기 컨텍스트 매니저**를 받기 때문에, 새로운 `lifespan` 비동기 컨텍스트 매니저를 FastAPI에 전달할 수 있습니다. |
|||
|
|||
{* ../../docs_src/events/tutorial003.py hl[22] *} |
|||
|
|||
## 대체 이벤트 (사용 중단) |
|||
|
|||
/// warning | 경고 |
|||
|
|||
이벤트 핸들러는 주 응용 프로그램에서만 작동합니다. [하위 응용 프로그램 - 마운트](./sub-applications.md){.internal-link target=_blank}에서는 작동하지 않습니다. |
|||
*시작*과 *종료*를 처리하는 권장 방법은 위에서 설명한 대로 `FastAPI` 애플리케이션의 `lifespan` 매개변수를 사용하는 것입니다. `lifespan` 매개변수를 제공하면 `startup`과 `shutdown` 이벤트 핸들러는 더 이상 호출되지 않습니다. `lifespan`을 사용할지, 모든 이벤트를 사용할지 선택해야 하며 둘 다 사용할 수는 없습니다. |
|||
|
|||
이 부분은 건너뛰셔도 좋습니다. |
|||
|
|||
/// |
|||
|
|||
## `startup` 이벤트 |
|||
*시작*과 *종료* 동안 실행될 이 로직을 정의하는 대체 방법이 있습니다. |
|||
|
|||
애플리케이션이 시작되기 전에 또는 종료될 때 실행해야 하는 이벤트 핸들러(함수)를 정의할 수 있습니다. |
|||
|
|||
응용 프로그램을 시작하기 전에 실행하려는 함수를 "startup" 이벤트로 선언합니다: |
|||
이 함수들은 `async def` 또는 일반 `def`로 선언할 수 있습니다. |
|||
|
|||
### `startup` 이벤트 |
|||
|
|||
애플리케이션이 시작되기 전에 실행되어야 하는 함수를 추가하려면, `"startup"` 이벤트로 선언합니다: |
|||
|
|||
{* ../../docs_src/events/tutorial001.py hl[8] *} |
|||
|
|||
이 경우 `startup` 이벤트 핸들러 함수는 단순히 몇 가지 값으로 구성된 `dict` 형식의 "데이터베이스"를 초기화합니다. |
|||
이 경우, `startup` 이벤트 핸들러 함수는 "database"라는 항목(단지 `dict`)을 일부 값으로 초기화합니다. |
|||
|
|||
하나 이상의 이벤트 핸들러 함수를 추가할 수도 있습니다. |
|||
여러 개의 이벤트 핸들러 함수를 추가할 수 있습니다. |
|||
|
|||
그리고 응용 프로그램은 모든 `startup` 이벤트 핸들러가 완료될 때까지 요청을 받지 않습니다. |
|||
애플리케이션은 모든 `startup` 이벤트 핸들러가 완료될 때까지 요청을 받기 시작하지 않습니다. |
|||
|
|||
## `shutdown` 이벤트 |
|||
### `shutdown` 이벤트 |
|||
|
|||
응용 프로그램이 종료될 때 실행하려는 함수를 추가하려면 `"shutdown"` 이벤트로 선언합니다: |
|||
애플리케이션이 종료될 때 실행되어야 하는 함수를 추가하려면, `"shutdown"` 이벤트로 선언합니다: |
|||
|
|||
{* ../../docs_src/events/tutorial002.py hl[6] *} |
|||
|
|||
이 예제에서 `shutdown` 이벤트 핸들러 함수는 `"Application shutdown"`이라는 텍스트가 적힌 `log.txt` 파일을 추가할 것입니다. |
|||
여기서, `shutdown` 이벤트 핸들러 함수는 `"Application shutdown"`이라는 텍스트를 `log.txt` 파일에 기록합니다. |
|||
|
|||
/// info | 정보 |
|||
|
|||
`open()` 함수에서 `mode="a"`는 "추가"를 의미합니다. 따라서 이미 존재하는 파일의 내용을 덮어쓰지 않고 새로운 줄을 추가합니다. |
|||
`open()` 함수에서 `mode="a"`는 "추가"를 의미하므로, 파일에 있는 기존 내용은 덮어쓰지 않고 새로운 줄이 추가됩니다. |
|||
|
|||
/// |
|||
|
|||
/// tip | 팁 |
|||
|
|||
이 예제에서는 파일과 상호작용 하기 위해 파이썬 표준 함수인 `open()`을 사용하고 있습니다. |
|||
이 경우, 우리는 표준 파이썬 `open()` 함수를 사용하여 파일과 상호작용하고 있습니다. |
|||
|
|||
따라서 디스크에 데이터를 쓰기 위해 "대기"가 필요한 I/O (입력/출력) 작업을 수행합니다. |
|||
따라서 I/O(입출력) 작업이 포함되어 있어 디스크에 기록되는 것을 "기다리는" 과정이 필요합니다. |
|||
|
|||
그러나 `open()`은 `async`와 `await`을 사용하지 않기 때문에 이벤트 핸들러 함수는 `async def`가 아닌 표준 `def`로 선언하고 있습니다. |
|||
하지만 `open()`은 `async`와 `await`를 사용하지 않습니다. |
|||
|
|||
그래서 우리는 이벤트 핸들러 함수를 `async def` 대신 일반 `def`로 선언합니다. |
|||
|
|||
/// |
|||
|
|||
### `startup`과 `shutdown`을 함께 사용 |
|||
|
|||
*시작*과 *종료* 로직이 연결될 가능성이 높습니다. 예를 들어, 무언가를 시작한 후 끝내거나, 자원을 획득한 후 해제하는 등의 작업을 할 수 있습니다. |
|||
|
|||
이러한 작업을 별도의 함수로 처리하면 서로 로직이나 변수를 공유하지 않기 때문에 더 어려워집니다. 값들을 전역 변수에 저장하거나 비슷한 트릭을 사용해야 할 수 있습니다. |
|||
|
|||
그렇기 때문에 위에서 설명한 대로 `lifespan`을 사용하는 것이 권장됩니다. |
|||
|
|||
## 기술적 세부사항 |
|||
|
|||
호기심 많은 분들을 위한 기술적인 세부사항입니다. 🤓 |
|||
|
|||
ASGI 기술 사양에 따르면, 이는 <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">Lifespan Protocol</a>의 일부이며, `startup`과 `shutdown`이라는 이벤트를 정의합니다. |
|||
|
|||
/// info | 정보 |
|||
|
|||
이벤트 핸들러에 관한 내용은 <a href="https://www.starlette.io/events/" class="external-link" target="_blank">Starlette 이벤트 문서</a>에서 추가로 확인할 수 있습니다. |
|||
Starlette의 `lifespan` 핸들러에 대해 더 읽고 싶다면 <a href="https://www.starlette.io/lifespan/" class="external-link" target="_blank">Starlette의 Lifespan 문서</a>에서 확인할 수 있습니다. |
|||
|
|||
이 문서에는 코드의 다른 영역에서 사용할 수 있는 lifespan 상태를 처리하는 방법도 포함되어 있습니다. |
|||
|
|||
/// |
|||
|
|||
## 서브 애플리케이션 |
|||
|
|||
🚨 이 lifespan 이벤트(`startup`과 `shutdown`)는 메인 애플리케이션에 대해서만 실행되며, [서브 애플리케이션 - Mounts](sub-applications.md){.internal-link target=_blank}에는 실행되지 않음을 유의하세요. |
|||
|
@ -0,0 +1,67 @@ |
|||
# 하위 응용프로그램 - 마운트 |
|||
|
|||
만약 각각의 독립적인 OpenAPI와 문서 UI를 갖는 두 개의 독립적인 FastAPI 응용프로그램이 필요하다면, 메인 어플리케이션에 하나 (또는 그 이상의) 하위-응용프로그램(들)을 “마운트"해서 사용할 수 있습니다. |
|||
|
|||
## **FastAPI** 응용프로그램 마운트 |
|||
|
|||
“마운트"이란 완전히 “독립적인" 응용프로그램을 특정 경로에 추가하여 해당 하위 응용프로그램에서 선언된 *경로 동작*을 통해 해당 경로 아래에 있는 모든 작업들을 처리할 수 있도록 하는 것을 의미합니다. |
|||
|
|||
### 최상단 응용프로그램 |
|||
|
|||
먼저, 메인, 최상단의 **FastAPI** 응용프로그램과 이것의 *경로 동작*을 생성합니다: |
|||
|
|||
{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *} |
|||
|
|||
### 하위 응용프로그램 |
|||
|
|||
다음으로, 하위 응용프로그램과 이것의 *경로 동작*을 생성합니다: |
|||
|
|||
이 하위 응용프로그램은 또 다른 표준 FastAPI 응용프로그램입니다. 다만 이것은 “마운트”될 것입니다: |
|||
|
|||
{* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *} |
|||
|
|||
### 하위 응용프로그램 마운트 |
|||
|
|||
최상단 응용프로그램, `app`에 하위 응용프로그램, `subapi`를 마운트합니다. |
|||
|
|||
이 예시에서, 하위 응용프로그램션은 `/subapi` 경로에 마운트 될 것입니다: |
|||
|
|||
{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *} |
|||
|
|||
### 자동으로 생성된 API 문서 확인 |
|||
|
|||
이제, `uvicorn`으로 메인 응용프로그램을 실행하십시오. 당신의 파일이 `main.py`라면, 이렇게 실행합니다: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ uvicorn main:app --reload |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
그리고 <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="https://fastapi.tiangolo.com//img/tutorial/sub-applications/image01.png"> |
|||
|
|||
다음으로, <a href="http://127.0.0.1:8000/subapi/docs" class="external-link" target="_blank">http://127.0.0.1:8000/subapi/docs</a>에서 하위 응용프로그램의 문서를 여십시오. |
|||
|
|||
하위 경로 접두사 `/subapi` 아래에 선언된 *경로 동작* 을 포함하는, 하위 응용프로그램에 대한 자동 API 문서를 확인할 수 있습니다: |
|||
|
|||
<img src="https://fastapi.tiangolo.com//img/tutorial/sub-applications/image02.png"> |
|||
|
|||
두 사용자 인터페이스 중 어느 하나를 사용해야하는 경우, 브라우저는 특정 응용프로그램 또는 하위 응용프로그램과 각각 통신할 수 있기 때문에 올바르게 동작할 것입니다. |
|||
|
|||
### 기술적 세부사항: `root_path` |
|||
|
|||
위에 설명된 것과 같이 하위 응용프로그램을 마운트하는 경우, FastAPI는 `root_path`라고 하는 ASGI 명세의 매커니즘을 사용하여 하위 응용프로그램에 대한 마운트 경로 통신을 처리합니다. |
|||
|
|||
이를 통해, 하위 응용프로그램은 문서 UI를 위해 경로 접두사를 사용해야 한다는 사실을 인지합니다. |
|||
|
|||
하위 응용프로그램에도 역시 다른 하위 응용프로그램을 마운트하는 것이 가능하며 FastAPI가 모든 `root_path` 들을 자동적으로 처리하기 때문에 모든 것은 올바르게 동작할 것입니다. |
|||
|
|||
`root_path`와 이것을 사용하는 방법에 대해서는 [프록시의 뒷단](./behind-a-proxy.md){.internal-link target=_blank} 섹션에서 배울 수 있습니다. |
@ -0,0 +1,223 @@ |
|||
# 추가 모델 |
|||
|
|||
지난 예제에 이어서, 연관된 모델을 여러개 갖는 것은 흔한 일입니다. |
|||
|
|||
특히 사용자 모델의 경우에 그러한데, 왜냐하면: |
|||
|
|||
* **입력 모델** 은 비밀번호를 가져야 합니다. |
|||
* **출력 모델** 은 비밀번호를 가지면 안됩니다. |
|||
* **데이터베이스 모델** 은 해시처리된 비밀번호를 가질 것입니다. |
|||
|
|||
/// danger | 위험 |
|||
|
|||
절대 사용자의 비밀번호를 평문으로 저장하지 마세요. 항상 이후에 검증 가능한 "안전한 해시(secure hash)"로 저장하세요. |
|||
|
|||
만약 이게 무엇인지 모르겠다면, [security chapters](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}.에서 비밀번호 해시에 대해 배울 수 있습니다. |
|||
|
|||
/// |
|||
|
|||
## 다중 모델 |
|||
|
|||
아래는 비밀번호 필드와 해당 필드가 사용되는 위치를 포함하여, 각 모델들이 어떤 형태를 가질 수 있는지 전반적인 예시입니다: |
|||
|
|||
{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *} |
|||
|
|||
|
|||
/// info | 정보 |
|||
|
|||
Pydantic v1에서는 해당 메서드가 `.dict()`로 불렸으며, Pydantic v2에서는 `.model_dump()`로 이름이 변경되었습니다. `.dict()`는 여전히 지원되지만 더 이상 권장되지 않습니다. |
|||
|
|||
여기에서 사용하는 예제는 Pydantic v1과의 호환성을 위해 `.dict()`를 사용하지만, Pydantic v2를 사용할 수 있다면 `.model_dump()`를 사용하는 것이 좋습니다. |
|||
|
|||
/// |
|||
|
|||
### `**user_in.dict()` 에 대하여 |
|||
|
|||
#### Pydantic의 `.dict()` |
|||
|
|||
`user_in`은 Pydantic 모델 클래스인 `UserIn`입니다. |
|||
|
|||
Pydantic 모델은 모델 데이터를 포함한 `dict`를 반환하는 `.dict()` 메서드를 제공합니다. |
|||
|
|||
따라서, 다음과 같이 Pydantic 객체 `user_in`을 생성할 수 있습니다: |
|||
|
|||
```Python |
|||
user_in = UserIn(username="john", password="secret", email="john.doe@example.com") |
|||
``` |
|||
|
|||
그 다음, 다음과 같이 호출합니다: |
|||
|
|||
```Python |
|||
user_dict = user_in.dict() |
|||
``` |
|||
|
|||
이제 변수 `user_dict`에 데이터가 포함된 `dict`를 가지게 됩니다(이는 Pydantic 모델 객체가 아닌 `dict`입니다). |
|||
|
|||
그리고 다음과 같이 호출하면: |
|||
|
|||
```Python |
|||
print(user_dict) |
|||
``` |
|||
|
|||
Python의 `dict`가 다음과 같이 출력됩니다: |
|||
|
|||
```Python |
|||
{ |
|||
'username': 'john', |
|||
'password': 'secret', |
|||
'email': 'john.doe@example.com', |
|||
'full_name': None, |
|||
} |
|||
``` |
|||
|
|||
#### `dict` 언패킹(Unpacking) |
|||
|
|||
`user_dict`와 같은 `dict`를 함수(또는 클래스)에 `**user_dict`로 전달하면, Python은 이를 "언팩(unpack)"합니다. 이 과정에서 `user_dict`의 키와 값을 각각 키-값 인자로 직접 전달합니다. |
|||
|
|||
따라서, 위에서 생성한 `user_dict`를 사용하여 다음과 같이 작성하면: |
|||
|
|||
```Python |
|||
UserInDB(**user_dict) |
|||
``` |
|||
|
|||
다음과 같은 결과를 생성합니다: |
|||
|
|||
```Python |
|||
UserInDB( |
|||
username="john", |
|||
password="secret", |
|||
email="john.doe@example.com", |
|||
full_name=None, |
|||
) |
|||
``` |
|||
|
|||
혹은 더 정확히 말하자면, `user_dict`를 직접 사용하는 것은, 나중에 어떤 값이 추가되더라도 아래와 동일한 효과를 냅니다: |
|||
|
|||
```Python |
|||
UserInDB( |
|||
username = user_dict["username"], |
|||
password = user_dict["password"], |
|||
email = user_dict["email"], |
|||
full_name = user_dict["full_name"], |
|||
) |
|||
``` |
|||
|
|||
#### 다른 모델 데이터로 새 Pydantic 모델 생성 |
|||
|
|||
위의 예제에서 `user_in.dict()`로부터 `user_dict`를 생성한 것처럼, 아래 코드는: |
|||
|
|||
```Python |
|||
user_dict = user_in.dict() |
|||
UserInDB(**user_dict) |
|||
``` |
|||
|
|||
다음과 동일합니다: |
|||
|
|||
```Python |
|||
UserInDB(**user_in.dict()) |
|||
``` |
|||
|
|||
...왜냐하면 `user_in.dict()`는 `dict`이며, 이를 `**`로 Python이 "언팩(unpack)"하도록 하여 `UserInDB`에 전달하기 때문입니다. |
|||
|
|||
따라서, 다른 Pydantic 모델의 데이터를 사용하여 새로운 Pydantic 모델을 생성할 수 있습니다. |
|||
|
|||
#### `dict` 언패킹(Unpacking)과 추가 키워드 |
|||
|
|||
그리고 다음과 같이 추가 키워드 인자 `hashed_password=hashed_password`를 추가하면: |
|||
|
|||
```Python |
|||
UserInDB(**user_in.dict(), hashed_password=hashed_password) |
|||
``` |
|||
|
|||
다음과 같은 결과를 생성합니다: |
|||
|
|||
```Python |
|||
UserInDB( |
|||
username = user_dict["username"], |
|||
password = user_dict["password"], |
|||
email = user_dict["email"], |
|||
full_name = user_dict["full_name"], |
|||
hashed_password = hashed_password, |
|||
) |
|||
``` |
|||
|
|||
/// warning | 경고 |
|||
|
|||
추가적으로 제공된 함수 `fake_password_hasher`와 `fake_save_user`는 데이터 흐름을 시연하기 위한 예제일 뿐이며, 실제 보안을 제공하지 않습니다. |
|||
|
|||
/// |
|||
|
|||
## 중복 줄이기 |
|||
|
|||
코드 중복을 줄이는 것은 **FastAPI**의 핵심 아이디어 중 하나입니다. |
|||
|
|||
코드 중복은 버그, 보안 문제, 코드 비동기화 문제(한 곳은 업데이트되었지만 다른 곳은 업데이트되지 않는 문제) 등의 가능성을 증가시킵니다. |
|||
|
|||
그리고 이 모델들은 많은 데이터를 공유하면서 속성 이름과 타입을 중복하고 있습니다. |
|||
|
|||
더 나은 방법이 있습니다. |
|||
|
|||
`UserBase` 모델을 선언하여 다른 모델들의 기본(base)으로 사용할 수 있습니다. 그런 다음 이 모델을 상속받아 속성과 타입 선언(유형 선언, 검증 등)을 상속하는 서브클래스를 만들 수 있습니다. |
|||
|
|||
모든 데이터 변환, 검증, 문서화 등은 정상적으로 작동할 것입니다. |
|||
|
|||
이렇게 하면 각 모델 간의 차이점만 선언할 수 있습니다(평문 `password`가 있는 경우, `hashed_password`만 있는 경우, 혹은 비밀번호가 없는 경우): |
|||
|
|||
{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *} |
|||
|
|||
## `Union` 또는 `anyOf` |
|||
|
|||
두 가지 이상의 타입을 포함하는 `Union`으로 응답을 선언할 수 있습니다. 이는 응답이 그 중 하나의 타입일 수 있음을 의미합니다. |
|||
|
|||
OpenAPI에서는 이를 `anyOf`로 정의합니다. |
|||
|
|||
이를 위해 표준 Python 타입 힌트인 <a href="https://docs.python.org/3/library/typing.html#typing.Union" class="external-link" target="_blank">`typing.Union`</a>을 사용할 수 있습니다: |
|||
|
|||
/// note | 참고 |
|||
|
|||
<a href="https://docs.pydantic.dev/latest/concepts/types/#unions" class="external-link" target="_blank">`Union`</a>을 정의할때는 더 구체적인 타입을 먼저 포함하고, 덜 구체적인 타입을 그 뒤에 나열해야합니다. 아래 예제에서는 `Union[PlaneItem, CarItem]` 를 보면, 더 구체적인 `PlaneItem`이 `CarItem`보다 앞에 위치합니다. |
|||
|
|||
/// |
|||
|
|||
{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *} |
|||
|
|||
|
|||
### Python 3.10에서 `Union` |
|||
|
|||
위의 예제에서는 `response_model` 인자 값으로 `Union[PlaneItem, CarItem]`을 전달합니다. |
|||
|
|||
이 경우, 이를 **타입 어노테이션(type annotation)** 이 아닌 **인자 값(argument value)** 으로 전달하고 있기 때문에 Python 3.10에서도 `Union`을 사용해야 합니다. |
|||
|
|||
만약 타입 어노테이션에 사용한다면, 다음과 같이 수직 막대(|)를 사용할 수 있습니다: |
|||
|
|||
```Python |
|||
some_variable: PlaneItem | CarItem |
|||
``` |
|||
|
|||
하지만 이를 `response_model=PlaneItem | CarItem`과 같이 할당하면 에러가 발생합니다. 이는 Python이 이를 타입 어노테이션으로 해석하지 않고, `PlaneItem`과 `CarItem` 사이의 **잘못된 연산(invalid operation)**을 시도하기 때문입니다 |
|||
|
|||
## 모델 리스트 |
|||
|
|||
마찬가지로, 객체 리스트 형태의 응답을 선언할 수도 있습니다. |
|||
|
|||
이를 위해 표준 Python의 `typing.List`를 사용하세요(또는 Python 3.9 이상에서는 단순히 `list`를 사용할 수 있습니다): |
|||
|
|||
{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *} |
|||
|
|||
|
|||
## 임의의 `dict` 응답 |
|||
|
|||
Pydantic 모델을 사용하지 않고, 키와 값의 타입만 선언하여 평범한 임의의 `dict`로 응답을 선언할 수도 있습니다. |
|||
|
|||
이는 Pydantic 모델에 필요한 유효한 필드/속성 이름을 사전에 알 수 없는 경우에 유용합니다. |
|||
|
|||
이 경우, `typing.Dict`를 사용할 수 있습니다(또는 Python 3.9 이상에서는 단순히 `dict`를 사용할 수 있습니다): |
|||
|
|||
{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *} |
|||
|
|||
|
|||
## 요약 |
|||
|
|||
여러 Pydantic 모델을 사용하고, 각 경우에 맞게 자유롭게 상속하세요. |
|||
|
|||
엔터티가 서로 다른 "상태"를 가져야 하는 경우, 엔터티당 단일 데이터 모델을 사용할 필요는 없습니다. 예를 들어, 사용자 "엔터티"가 `password`, `password_hash`, 또는 비밀번호가 없는 상태를 포함할 수 있는 경우처럼 말입니다. |
@ -0,0 +1,846 @@ |
|||
# 가상 환경 |
|||
|
|||
Python 프로젝트를 작업할 때는 **가상 환경** (또는 이와 유사한 도구)을 사용하는 것이 좋습니다. 각 프로젝트 마다 설치하는 패키지를 분리하여 관리할 수 있습니다. |
|||
|
|||
/// info | 정보 |
|||
|
|||
이미 가상 환경에 대해 잘 알고 있다면, 이 섹션은 건너 뛰어도 괜찮습니다. 🤓 |
|||
|
|||
/// |
|||
|
|||
/// tip | 팁 |
|||
|
|||
**가상 환경(Virtual Environment)** 은 **환경 변수(Environment Variable)** 와 다릅니다. |
|||
|
|||
**환경 변수**는 시스템에 존재하며, 프로그램이 사용할 수 있는 변수입니다. |
|||
|
|||
**가상 환경**은 몇몇 파일로 구성된 하나의 디렉터리입니다. |
|||
|
|||
/// |
|||
|
|||
/// info | 정보 |
|||
|
|||
이 페이지에서는 **가상 환경**의 사용 방법과 작동 방식을 설명합니다. |
|||
|
|||
만약 **모든 것을 관리해주는 도구** (Python 설치까지 포함)를 사용하고 싶다면 <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>를 사용해보세요. |
|||
|
|||
/// |
|||
|
|||
## 프로젝트 생성 |
|||
|
|||
먼저, 프로젝트를 위한 디렉터리를 하나 생성합니다. |
|||
|
|||
보통 사용자 홈 디렉터리 안에 `code`라는 디렉터리를 만들고, 그 안에 프로젝트마다 하나씩 디렉터리를 만들어 관리합니다. |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// 홈 디렉터리로 이동 |
|||
$ cd |
|||
// 모든 코드 프로젝트를 위한 디렉터리 생성 |
|||
$ mkdir code |
|||
// code 디렉터리로 이동 |
|||
$ cd code |
|||
// 이번 프로젝트를 위한 디렉터리 생성 |
|||
$ mkdir awesome-project |
|||
// 해당 프로젝트 디렉터리로 이동 |
|||
$ cd awesome-project |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
## 가상 환경 생성 |
|||
|
|||
Python 프로젝트를 **처음 시작할 때**, 가상 환경을 **<abbr title="다른 방법들도 있지만, 이건 간단한 가이드라인입니다">프로젝트 내부</abbr>**에 생성합니다. |
|||
|
|||
/// tip | 팁 |
|||
|
|||
이 작업은 **프로젝트를 처음 설정할 때 한번만** 해주면 됩니다. 이후 작업할 때 반복할 필요는 없습니다. |
|||
|
|||
/// |
|||
|
|||
//// tab | `venv` |
|||
|
|||
Python 표준 라이브러리에 포함된 venv 모듈을 사용해 가상 환경을 생성할 수 있습니다. |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ python -m venv .venv |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
/// details | 명령어 상세 설명 |
|||
|
|||
* `python`: `python` 프로그램을 실행합니다. |
|||
* `-m`: 특정 모듈을 스크립트처럼 실행합니다. 대상 모듈을 바로 뒤에 지정합니다. |
|||
* `venv`: Python 표준 라이브러리에 포함된 `venv` 모듈을 실행합니다. |
|||
* `.venv`: 가상 환경을 `.venv` 디렉터리에 생성합니다. |
|||
|
|||
/// |
|||
|
|||
//// |
|||
|
|||
//// tab | `uv` |
|||
|
|||
<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>가 설치되어 있다면, uv를 통해 가상 환경을 생성할 수 있습니다. |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ uv venv |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
/// tip | 팁 |
|||
|
|||
`uv`는 기본적으로 `.venv` 디렉터리에 가상 환경을 생성합니다. |
|||
|
|||
별도로 디렉터리 이름을 추가 인자로 넘겨 주면 경로를 지정 할 수 있습니다. |
|||
|
|||
/// |
|||
|
|||
//// |
|||
|
|||
해당 명령어는 `.venv` 디렉터리에 새로운 가상 환경을 생성합니다. |
|||
|
|||
/// details | `.venv` 또는 다른 이름 |
|||
|
|||
가상 환경을 다른 디렉터리에 생성할 수도 있지만, 관례적으로 `.venv` 디렉터리 이름을 사용합니다. |
|||
|
|||
/// |
|||
|
|||
## 가상 환경 활성화 |
|||
|
|||
이후 실행하는 Python 명령어와 패키지 설치가 가상 환경을 따르도록, 가상 환경을 활성화하세요. |
|||
|
|||
/// tip | 팁 |
|||
|
|||
**터미널을 새로 열고** 프로젝트 작업을 시작할 때는, **항상 이 작업을** 해주세요. |
|||
|
|||
/// |
|||
|
|||
//// tab | Linux, macOS |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ source .venv/bin/activate |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
//// tab | Windows PowerShell |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ .venv\Scripts\Activate.ps1 |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
//// tab | Windows Bash |
|||
|
|||
Windows에서 Bash(예: <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>)를 사용하는 경우: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ source .venv/Scripts/activate |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
/// tip | 팁 |
|||
|
|||
가상 환경에 새로운 패키지를 설치할 때마다, 해당 환경을 다시 활성화하세요. |
|||
|
|||
이렇게 하면 해당 패키지로 설치된 **터미널(<abbr title="command line interface">CLI</abbr>) 프로그램**을 사용할 때, 전역에 설치된 다른 버전이 아니라, 가상 환경 안에 설치된 정확한 버전을 사용합니다. |
|||
|
|||
/// |
|||
|
|||
## 가상 환경이 활성화 여부 확인 |
|||
|
|||
가상 환경이 활성화되었는지 확인합니다. (이전 명령어가 제대로 작동했는지 확인합니다). |
|||
|
|||
/// tip | 팁 |
|||
|
|||
이 단계는 **선택 사항**이지만, 모든 것이 예상대로 작동하고 있는지, 그리고 의도한 가상 환경이 활성화 되었는 지 **확인**하는 좋은 방법입니다. |
|||
|
|||
/// |
|||
|
|||
//// tab | Linux, macOS, Windows Bash |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ which python |
|||
|
|||
/home/user/code/awesome-project/.venv/bin/python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
`python` 위치가 프로젝트 내부(이 예시에서는 `awesome-project`)의 `.venv/bin/python` 경로로 표시된다면 성공입니다. 🎉 |
|||
|
|||
//// |
|||
|
|||
//// tab | Windows PowerShell |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ Get-Command python |
|||
|
|||
C:\Users\user\code\awesome-project\.venv\Scripts\python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
`python` 위치가 프로젝트 내부(이 예시에서는 `awesome-project`)의 `.venv\bin\python` 경로로 표시된다면 성공입니다. 🎉 |
|||
|
|||
//// |
|||
|
|||
## pip 업그레이드 |
|||
|
|||
/// tip | 팁 |
|||
|
|||
<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>를 사용한다면, `pip` 대신 `uv`로 패키지를 설치하게 되므로 `pip`을 업그레이드할 필요가 없습니다. 😎 |
|||
|
|||
/// |
|||
|
|||
`pip`을 사용하여 패키지를 설치하는 경우 (Python 표준 라이브러리에 포함되어 있습니다), **최신 버전으로 업그레이드**하는 것이 좋습니다. |
|||
|
|||
패키지 설치 중 발생하는 다양하고 특이한 에러들은 `pip` 업그레이드로 쉽게 해결되는 경우가 많습니다. |
|||
|
|||
/// tip | 팁 |
|||
|
|||
이 작업은 보통 가상 환경을 생성한 **직후 한 번만** 하면 됩니다. |
|||
|
|||
/// |
|||
|
|||
가상 환경이 활성화된 상태인지 확인한 후(앞서 설명한 명령어 사용), 아래 명령어를 실행하세요: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ python -m pip install --upgrade pip |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
## `.gitignore` 추가하기 |
|||
|
|||
**Git**을 사용하고 있다면 (사용하는 것이 좋습니다), `.gitignore` 파일을 추가해서 `.venv` 디렉터리 전체를 Git에서 제외하세요. |
|||
|
|||
/// tip | 팁 |
|||
|
|||
<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>를 사용해 가상 환경을 생성했다면, 이미 이 작업이 자동으로 처리되어 있으므로 이 단계는 건너뛰어도 됩니다. 😎 |
|||
|
|||
/// |
|||
|
|||
/// tip | 팁 |
|||
|
|||
이 작업도 마찬가지로, 가상 환경을 생성한 **직후 한 번만** 하면 됩니다. |
|||
|
|||
/// |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ echo "*" > .venv/.gitignore |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
/// details | 명령어 상세 설명 |
|||
|
|||
* `echo "*"`: 터미널에 `*` 텍스트를 "출력"합니다 (다음 설명에서 조금 바뀝니다) |
|||
* `>`: 왼쪽 명령어의 출력 내용을 터미널에 출력하지 않고, 오른쪽에 지정된 파일로 **기록(write)** 하라는 의미입니다. |
|||
* `.gitignore`: 출력된 텍스트가 기록될 파일 이름입니다. |
|||
|
|||
그리고 Git에서 `*`는 "모든 것"을 의미합니다. 따라서 `.venv` 디렉터리 안의 모든 것을 무시하게 됩니다. |
|||
|
|||
이 명령어는 다음과 같은 내용을 가진 `.gitignore` 파일을 생성합니다: |
|||
|
|||
|
|||
```gitignore |
|||
* |
|||
``` |
|||
|
|||
/// |
|||
|
|||
## 패키지 설치 |
|||
|
|||
가상 환경을 활성화한 후, 그 안에 필요한 패키지들을 설치할 수 있습니다. |
|||
|
|||
/// tip | 팁 |
|||
|
|||
프로젝트에서 필요한 패키지를 설치하거나 업그레이드할 때는 이 작업을 **한 번만** 하면 됩니다. |
|||
|
|||
만약 특정 패키지의 버전을 업그레이드하거나, 새로운 패키지를 추가할 필요가 생기면 **다시 이 작업을 반복**하면 됩니다. |
|||
|
|||
/// |
|||
|
|||
### 패키지 직접 설치 |
|||
|
|||
급하게 작업하거나, 프로젝트에 필요한 패키지 목록을 따로 파일로 관리하고 싶지 않은 경우, 패키지를 직접 설치할 수도 있습니다. |
|||
|
|||
/// tip | 팁 |
|||
|
|||
패키지 이름과 버전 정보를 파일에 정리해두는 것(예: `requirements.txt` 또는 `pyproject.toml`)은 (매우) 좋은 생각입니다. |
|||
|
|||
/// |
|||
|
|||
//// tab | `pip` |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install "fastapi[standard]" |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
//// tab | `uv` |
|||
|
|||
<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>를 사용하는 경우: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ uv pip install "fastapi[standard]" |
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
### `requirements.txt`에서 설치 |
|||
|
|||
`requirements.txt` 파일이 있다면, 그 안에 명시된 패키지들을 한 번에 설치할 수 있습니다. |
|||
|
|||
//// tab | `pip` |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install -r requirements.txt |
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
//// tab | `uv` |
|||
|
|||
<a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>를 사용하는 경우: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ uv pip install -r requirements.txt |
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
/// details | `requirements.txt` |
|||
|
|||
다음은 몇 가지 패키지를 포함한 `requirements.txt`의 예시입니다: |
|||
|
|||
```requirements.txt |
|||
fastapi[standard]==0.113.0 |
|||
pydantic==2.8.0 |
|||
``` |
|||
|
|||
/// |
|||
|
|||
## 프로그램 실행 |
|||
|
|||
가상 환경을 활성화한 후에는 프로그램을 실행할 수 있습니다. 이때 해당 가상 환경에 설치된 Python과 패키지들이 사용됩니다. |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ python main.py |
|||
|
|||
Hello World |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
## 에디터 설정 |
|||
|
|||
에디터를 사용할 경우, 앞서 만든 가상 환경을 사용하도록 설정하는 것이 좋습니다. (대부분의 에디터는 자동으로 감지하기도 합니다.) |
|||
이렇게 하면 자동 완성 기능이나 코드 내 오류 표시 기능을 제대로 사용할 수 있습니다. |
|||
|
|||
예시: |
|||
|
|||
* <a href="https://code.visualstudio.com/docs/python/environments#_select-and-activate-an-environment" class="external-link" target="_blank">VS Code</a> |
|||
* <a href="https://www.jetbrains.com/help/pycharm/creating-virtual-environment.html" class="external-link" target="_blank">PyCharm</a> |
|||
|
|||
/// tip | 팁 |
|||
|
|||
이 설정은 보통 가상 환경을 **처음 만들었을 때 한 번만** 해주면 됩니다. |
|||
|
|||
/// |
|||
|
|||
## 가상 환경 비활성화 |
|||
|
|||
프로젝트 작업이 끝났다면, 가상 환경을 **비활성화**할 수 있습니다. |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ deactivate |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
이렇게 하면 이후에 `python` 명령어를 실행했을 때, 가상 환경의 Python이나 그 안에 설치된 패키지들을 사용하지 않게 됩니다. |
|||
|
|||
## 이제 작업할 준비가 되었습니다 |
|||
|
|||
이제 프로젝트 작업을 시작할 준비가 완료되었습니다. |
|||
|
|||
|
|||
/// tip | 팁 |
|||
|
|||
위 내용을 더 깊이 이해하고 싶으신가요? |
|||
|
|||
그렇다면 계속 읽어 주세요. 👇🤓 |
|||
|
|||
/// |
|||
|
|||
## 가상 환경을 왜 사용하는가 |
|||
|
|||
FastAPI를 사용하려면 먼저 <a href="https://www.python.org/" class="external-link" target="_blank">Python</a>을 설치해야 합니다. |
|||
|
|||
그 후에는 FastAPI와 함께 사용할 **기타 패키지들**을 **설치**해야 합니다. |
|||
|
|||
패키지를 설치할 때 보통 Python에 기본 포함된 `pip` 명령어(또는 유사한 도구)를 사용합니다. |
|||
|
|||
하지만 `pip`을 그냥 직접 사용하면, 해당 패키지들은 **전역 Python 환경**(시스템 전체에 설치된 Python)에 설치됩니다. |
|||
|
|||
### 문제점 |
|||
|
|||
그렇다면, 전역 Python 환경에 패키지를 설치하면 어떤 문제가 발생할까요? |
|||
|
|||
어느 시점이 되면, **서로 다른 패키지들**에 의존하는 여러 개의 프로그램을 작성하게 될 것입니다. 그리고 이들 중 일부는 **같은 패키지의 서로 다른 버전**을 필요로 할 수 있습니다. 😱 |
|||
|
|||
예를 들어, `마법사의 돌(philosophers-stone)` 프로젝트를 만들었다고 가정해봅시다. 이 프로그램은 `해리 포터(harry)`라는 패키지의 `v1` 버전을 **의존**합니다. 따라서 `harry`를 설치해야 합니다. |
|||
|
|||
```mermaid |
|||
flowchart LR |
|||
stone(philosophers-stone) -->|requires| harry-1[harry v1] |
|||
``` |
|||
|
|||
그런데 나중에 `아즈카반의 죄수(prisoner-of-azkaban)`이라는 또 다른 프로젝트를 만들게 되었고, 이 프로젝트도 역시 `harry` 패키지를 사용합니다. 그런데 이 프로젝트는 `harry`의 `v3` 버전이 필요합니다. |
|||
|
|||
```mermaid |
|||
flowchart LR |
|||
azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3] |
|||
``` |
|||
|
|||
하지만 이제 문제가 생깁니다. 로컬 가상 환경 대신에 전역 환경에 패키지를 설치하게 되면, 어떤 버전의 `harry`를 설치할지를 선택해야 하기 때문입니다. |
|||
|
|||
예를 들어, `마법사의 돌(philosophers-stone)`을 실행하고 싶다면 먼저 `harry` `v1` 버전을 다음과 같이 설치 해야 합니다: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install "harry==1" |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
그러면 결국 전역 Python 환경에는 `harry` `v1`버전이 설치된 상태가 됩니다. |
|||
|
|||
```mermaid |
|||
flowchart LR |
|||
subgraph global[global env] |
|||
harry-1[harry v1] |
|||
end |
|||
subgraph stone-project[philosophers-stone project] |
|||
stone(philosophers-stone) -->|requires| harry-1 |
|||
end |
|||
``` |
|||
|
|||
하지만 이제 `아즈카반의 죄수(prisoner-of-azkaban)`을 실행하고 싶다면, `harry` `v1`버전을 제거하고 `harry` `v3`버전을 설치해야 합니다. (또는 단순히 `v3`버전을 설치하는 것만으로도 기존의 `v1`버전이 자동으로 제거됩니다.) |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install "harry==3" |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
그렇게 하면 이제 전역 Python 환경에는 `harry` `v3`버전이 설치된 상태가 됩니다. |
|||
|
|||
그리고 다시 `마법사의 돌(philosophers-stone)`을 실행하려고 하면, **작동하지** 않을 수 있습니다. 왜냐하면 이 프로그램은 `harry` `v1`버전을 필요로 하기 때문입니다. |
|||
|
|||
```mermaid |
|||
flowchart LR |
|||
subgraph global[global env] |
|||
harry-1[<strike>harry v1</strike>] |
|||
style harry-1 fill:#ccc,stroke-dasharray: 5 5 |
|||
harry-3[harry v3] |
|||
end |
|||
subgraph stone-project[philosophers-stone project] |
|||
stone(philosophers-stone) -.-x|⛔️| harry-1 |
|||
end |
|||
subgraph azkaban-project[prisoner-of-azkaban project] |
|||
azkaban(prisoner-of-azkaban) --> |requires| harry-3 |
|||
end |
|||
``` |
|||
|
|||
/// tip | 팁 |
|||
|
|||
Python 패키지들은 **새 버전**에서 **호환성 문제(breaking changes)**가 발생하지 않도록 최대한 노력하는 것이 일반적입니다. 하지만 그래도 안전하게 작업하려면, 테스트를 실행해보면서 새 버전을 의도적으로 설치하는 것이 좋습니다. |
|||
|
|||
/// |
|||
|
|||
이제, 이런 일이 여러분의 **모든 프로젝트**가 사용하는 **수많은 패키지들**에서 동시에 발생한다고 상상해보세요. 이는 매우 관리하기 어려우며, 결국 **서로 호환되지 않는 버전**의 패키지로 프로젝트를 실행하게 될 가능성이 높고, 그로 인해 어떤 문제가 왜 발생하는지 알 수 없게 될 수 있습니다. |
|||
|
|||
또한 사용하는 운영체제(Linux, Windows, macOS 등)에 따라 Python이 **미리 설치되어 있을 수도** 있습니다. 이런 경우에는 운영체제의 동작에 필요한 특정 버전의 패키지들이 함께 설치되어 있을 수 있습니다. 이 상태에서 전역 Python 환경에 임의의 패키지를 설치하면, 운영체제에 포함된 프로그램 일부가 **깨질 위험**도 있습니다. |
|||
|
|||
## 패키지들은 어디에 설치되는가 |
|||
|
|||
Python을 설치하면, 컴퓨터에 여러 디렉터리와 파일들이 생성됩니다. |
|||
|
|||
이 중 일부 디렉터리는 사용자가 설치한 패키지들을 보관하는 역할을 합니다. |
|||
|
|||
예를 들어, 아래 명령어를 실행하면: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// 지금 실행하지 않아도 됩니다, 그냥 예제일 뿐이에요 🤓 |
|||
$ pip install "fastapi[standard]" |
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
해당 명령어는 FastAPI 코드를 포함한 압축 파일을 다운로드합니다. 이 파일은 보통 <a href="https://pypi.org/project/fastapi/" class="external-link" target="_blank">PyPI</a>에서 받아옵니다. |
|||
|
|||
또한 FastAPI가 의존하는 다른 패키지들도 함께 **다운로드**됩니다. |
|||
|
|||
그리고 그 모든 파일들을 **압축 해제**한 뒤, 컴퓨터의 특정 디렉터리에 저장합니다. |
|||
|
|||
기본적으로 이 파일들은 Python이 설치된 디렉터리 안, 즉 **전역 환경**에 내의 디렉터리에 저장됩니다. |
|||
|
|||
## 가상 환경이란 |
|||
|
|||
전역 환경에 모든 패키지를 설치하면서 발생하는 문제에 대한 해결책은, 작업하는 **각 프로젝트마다 가상 환경**을 사용하는 것입니다. |
|||
|
|||
가상 환경은 전역 환경과 매우 유사한 하나의 **디렉터리**이며, 그 안에 해당 프로젝트를 위한 패키지들을 설치할 수 있습니다. |
|||
|
|||
이렇게 하면 각 프로젝트는 자체적인 가상 환경(`.venv` 디렉터리)을 가지게 되며, 그 안에 해당 프로젝트 전용 패키지들을 보유하게 됩니다. |
|||
|
|||
|
|||
```mermaid |
|||
flowchart TB |
|||
subgraph stone-project[philosophers-stone project] |
|||
stone(philosophers-stone) --->|requires| harry-1 |
|||
subgraph venv1[.venv] |
|||
harry-1[harry v1] |
|||
end |
|||
end |
|||
subgraph azkaban-project[prisoner-of-azkaban project] |
|||
azkaban(prisoner-of-azkaban) --->|requires| harry-3 |
|||
subgraph venv2[.venv] |
|||
harry-3[harry v3] |
|||
end |
|||
end |
|||
stone-project ~~~ azkaban-project |
|||
``` |
|||
|
|||
## 가상 환경 활성화 의미 |
|||
|
|||
가상 환경을 활성화한다는 것은, 예를 들어 다음과 같은 명령어를 실행하는 것을 의미합니다: |
|||
|
|||
//// tab | Linux, macOS |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ source .venv/bin/activate |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
//// tab | Windows PowerShell |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ .venv\Scripts\Activate.ps1 |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
//// tab | Windows Bash |
|||
|
|||
Windows에서 Bash(예: <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>)를 사용하는 경우: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ source .venv/Scripts/activate |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
이 명령어는 이후에 실행될 명령어에서 사용될 [환경 변수](environment-variables.md){.internal-link target=_blank} 몇 개를 생성하거나 수정합니다. |
|||
|
|||
이 변수들 중 하나가 바로 `PATH` 변수입니다. |
|||
|
|||
/// tip | 팁 |
|||
|
|||
`PATH` 환경 변수에 대해 더 알고 싶다면 [환경 변수 문서의 PATH 환경 변수 섹션](environment-variables.md#path-environment-variable){.internal-link target=_blank}을 참고하세요. |
|||
|
|||
/// |
|||
|
|||
가상 환경을 활성화하면, 가상 환경의 경로인 `.venv/bin` (Linux와 macOS) 또는 `.venv\Scripts`(Windows)를 `PATH` 환경 변수에 추가됩니다. |
|||
|
|||
예를 들어, 가상 환경을 활성화하기 전의 `PATH` 변수는 다음과 같았다고 가정해봅시다: |
|||
|
|||
//// tab | Linux, macOS |
|||
|
|||
```plaintext |
|||
/usr/bin:/bin:/usr/sbin:/sbin |
|||
``` |
|||
|
|||
시스템은 다음 경로들에서 프로그램을 찾게 됩니다: |
|||
|
|||
* `/usr/bin` |
|||
* `/bin` |
|||
* `/usr/sbin` |
|||
* `/sbin` |
|||
|
|||
//// |
|||
|
|||
//// tab | Windows |
|||
|
|||
```plaintext |
|||
C:\Windows\System32 |
|||
``` |
|||
|
|||
시스템은 다음 경로들에서 프로그램을 찾게 됩니다: |
|||
|
|||
* `C:\Windows\System32` |
|||
|
|||
//// |
|||
|
|||
가상 환경을 활성화한 후에는, `PATH` 변수는 다음과 같은 형태가 됩니다: |
|||
|
|||
//// tab | Linux, macOS |
|||
|
|||
```plaintext |
|||
/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin |
|||
``` |
|||
|
|||
시스템은 가장 먼저 다음 경로에서 프로그램을 찾기 시작합니다: |
|||
|
|||
```plaintext |
|||
/home/user/code/awesome-project/.venv/bin |
|||
``` |
|||
|
|||
그 후에 다른 디렉터리들을 탐색합니다. |
|||
|
|||
따라서 터미널에 `python`을 입력하면, 시스템은 다음 위치에 있는 Python 프로그램을 찾게 됩니다: |
|||
|
|||
```plaintext |
|||
/home/user/code/awesome-project/.venv/bin/python |
|||
``` |
|||
|
|||
그리고 해당 Python을 사용하게 됩니다. |
|||
|
|||
//// |
|||
|
|||
//// tab | Windows |
|||
|
|||
```plaintext |
|||
C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 |
|||
``` |
|||
|
|||
시스템은 가장 먼저 다음 경로에서 프로그램을 찾기 시작합니다: |
|||
|
|||
```plaintext |
|||
C:\Users\user\code\awesome-project\.venv\Scripts |
|||
``` |
|||
|
|||
그 후에 다른 디렉터리들을 탐색합니다. |
|||
|
|||
따라서 터미널에 `python`을 입력하면, 시스템은 다음 경로에 있는 Python 프로그램을 찾게 됩니다: |
|||
|
|||
```plaintext |
|||
C:\Users\user\code\awesome-project\.venv\Scripts\python |
|||
``` |
|||
|
|||
그리고 해당 Python을 사용하게 됩니다. |
|||
|
|||
//// |
|||
|
|||
중요한 세부 사항 중 하나는, 가상 환경의 경로가 `PATH` 변수의 가장 **앞**에 추가된다는 점입니다. 시스템은 사용 가능한 다른 Python들보다 **먼저** 이 경로를 찾습니다. 그래서 터미널에서 `python`을 실행하면, 전역 환경의 Python이 아닌 **가상 환경에 있는** Python이 사용됩니다. (예: 전역 환경에 설치된 `python`이 있더라도 그보다 우선합니다.) |
|||
|
|||
가상 환경을 활성화하면 이 외에도 몇 가지 다른 것들이 변경되지만, 이는 그중에서도 가장 중요한 변화 중 하나입니다. |
|||
|
|||
## 가상 환경 확인하기 |
|||
|
|||
가상 환경이 활성화 되었는지 확인하려면, 아래 명령어를 사용할 수 있습니다: |
|||
|
|||
//// tab | Linux, macOS, Windows Bash |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ which python |
|||
|
|||
/home/user/code/awesome-project/.venv/bin/python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
//// tab | Windows PowerShell |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ Get-Command python |
|||
|
|||
C:\Users\user\code\awesome-project\.venv\Scripts\python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
즉, 현재 사용되는 `python` 프로그램은 **가상 환경 내부에 있는 것**입니다. |
|||
|
|||
Linux와 macOS에서는 `which`, Windows PowerShell에서는 `Get-Command` 명령어를 사용합니다. |
|||
|
|||
이 명령어는 `PATH` 환경 변수에 지정된 경로들을 **순서대로 탐색**하면서 `python`이라는 이름의 프로그램을 찾습니다. |
|||
찾는 즉시, 해당 프로그램의 **경로를 출력**합니다. |
|||
|
|||
중요한 점은 터미널에서 `python`을 실행했을 때, 실제로 실행되는 "`python`"이 어떤 것인지 정확히 알 수 있다는 것입니다. |
|||
|
|||
따라서 현재 올바른 가상 환경에 있는지 확인할 수 있습니다. |
|||
|
|||
/// tip | 팁 |
|||
|
|||
하나의 가상 환경을 활성화한 뒤, 해당 Python을 가진 상태에서 **또 다른 프로젝트**로 이동하는 것은 흔히 발생합니다. |
|||
|
|||
하지만 이때 이전 프로젝트의 가상 환경에 있는 **잘못된 Python 실행 파일**을 사용하게 되어 새 프로젝트가 **정상 작동하지 않을 수 있습니다.** |
|||
|
|||
그래서 현재 어떤 `python`이 사용되고 있는지 확인할 수 있는 능력은 매우 유용합니다. 🤓 |
|||
|
|||
/// |
|||
|
|||
## 가상 환경을 비활성화하는 이유 |
|||
|
|||
예를 들어 `마법사의 돌(philosophers-stone)`이라는 프로젝트에서 작업 중이라고 해보겠습니다. 이때 해당 **가상 환경을 활성화**하고, 필요한 패키지를 설치하며 작업을 진행합니다. |
|||
|
|||
그런데 이제는 **다른 프로젝트**인 `아즈카반의 죄수(prisoner-of-azkaban)`을 작업하고 싶어졌습니다. |
|||
|
|||
그래서 그 프로젝트 디렉터리로 이동합니다: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ cd ~/code/prisoner-of-azkaban |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
만약 `마법사의 돌(philosophers-stone)`의 가상 환경을 비활성화하지 않았다면, 터미널에서 `python`을 실행할 때 여전히 `마법사의 돌(philosophers-stone)` 가상 환경의 Python을 사용하게 됩니다. |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ cd ~/code/prisoner-of-azkaban |
|||
|
|||
$ python main.py |
|||
|
|||
// sirius를 임포트하는 데 실패했습니다. 설치되어 있지 않아요 😱 |
|||
Traceback (most recent call last): |
|||
File "main.py", line 1, in <module> |
|||
import sirius |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
하지만 `마법사의 돌(philosophers-stone)`의 가상 환경을 비활성화한 다음, `아즈카반의 죄수(prisoner-of-azkaban)` 프로젝트의 가상 환경을 활성화하면, 이제 `python` 명령어는 `아즈카반의 죄수(prisoner-of-azkaban)` 가상 환경의 Python을 사용하게 됩니다. |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ cd ~/code/prisoner-of-azkaban |
|||
|
|||
// 이전 디렉터리에 있을 필요 없이, 어디서든 가상 환경을 비활성화할 수 있습니다. 다른 프로젝트 디렉터리로 이동한 후에도 괜찮아요 😎 |
|||
$ deactivate |
|||
|
|||
// prisoner-of-azkaban/.venv 가상 환경을 활성화합니다 🚀 |
|||
$ source .venv/bin/activate |
|||
|
|||
// 이제 python을 실행하면, 이 가상 환경에 설치된 sirius 패키지를 찾게 됩니다 ✨ |
|||
$ python main.py |
|||
|
|||
못된 짓을 꾸미고 있음을 엄숙히 맹세합니다.🧙 |
|||
ImportError는 이제 없습니다. 🐺 |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
## 대안들 |
|||
|
|||
이 문서는 여러분이 Python 프로젝트를 시작하고, **그 내부에서** 어떻게 돌아가는지 알려주는 간단한 가이드입니다. |
|||
|
|||
가상 환경, 패키지 의존성(Requirements), 프로젝트를 관리하는 방법에는 이 외에도 다양한 **대안**들이 존재합니다. |
|||
|
|||
만약 준비가 되었다면, **프로젝트 전체**, 패키지 의존성, 가상 환경 등을 통합적으로 **관리**할 수 있는 도구를 써보는 것도 좋습니다. 그럴 때 추천하는 도구가 바로 <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>입니다. |
|||
|
|||
`uv`는 다양한 기능을 지원합니다: |
|||
|
|||
* 다양한 버전의 **Python 설치** |
|||
* 각 프로젝트 별 **가상 환경 관리** |
|||
* **패키지 설치** |
|||
* 프로젝트의 **의존성과 버전** 관리 |
|||
* 설치된 패키지들과 그 버전을 **정확히 고정(lock)**해서,개발 환경과 운영 환경이 완전히 동일하게 작동할 수 있도록 보장 |
|||
* 이 외에도 다양한 기능을 지원 |
|||
|
|||
## 결론 |
|||
|
|||
여기까지 모두 읽고 이해했다면, 이제 많은 개발자들보다 가상 환경을 **훨씬 더 깊이 있게 이해**하게 되셨습니다. 🤓 |
|||
|
|||
이런 세부적인 내용을 알고 있으면, 언젠가 복잡해 보이는 문제를 디버깅할 때 분명히 큰 도움이 될 것입니다. 이제는 **이 모든 것들이 내부에서 어떻게 작동하는지** 알고 있기 때문입니다. 😎 |
@ -1,84 +1,28 @@ |
|||
# Geração de Projetos - Modelo |
|||
|
|||
Você pode usar um gerador de projetos para começar, por já incluir configurações iniciais, segurança, banco de dados e os primeiros _endpoints_ API já feitos para você. |
|||
|
|||
Um gerador de projetos sempre terá uma pré-configuração que você pode atualizar e adaptar para suas próprias necessidades, mas pode ser um bom ponto de partida para seu projeto. |
|||
|
|||
## Full Stack FastAPI PostgreSQL |
|||
|
|||
GitHub: <a href="https://github.com/tiangolo/full-stack-fastapi-postgresql" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-fastapi-postgresql</a> |
|||
|
|||
### Full Stack FastAPI PostgreSQL - Recursos |
|||
|
|||
* Integração completa **Docker**. |
|||
* Modo de implantação Docker Swarm. |
|||
* Integração e otimização **Docker Compose** para desenvolvimento local. |
|||
* **Pronto para Produção** com servidor _web_ usando Uvicorn e Gunicorn. |
|||
* _Backend_ <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">**FastAPI**</a> Python: |
|||
* **Rápido**: Alta performance, no nível de **NodeJS** e **Go** (graças ao Starlette e Pydantic). |
|||
* **Intuitivo**: Ótimo suporte de editor. <abbr title="também conhecido como auto-complete, auto completação, IntelliSense">_Auto-Complete_</abbr> em todo lugar. Menos tempo _debugando_. |
|||
* **Fácil**: Projetado para ser fácil de usar e aprender. Menos tempo lendo documentações. |
|||
* **Curto**: Minimize duplicação de código. Múltiplos recursos para cada declaração de parâmetro. |
|||
* **Robusto**: Tenha código pronto para produção. Com documentação interativa automática. |
|||
* **Baseado em Padrões**: Baseado em (e completamente compatível com) padrões abertos para APIs: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> e <a href="http://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>. |
|||
* <a href="https://fastapi.tiangolo.com/features/" class="external-link" target="_blank">**Muitos outros recursos**</a> incluindo validação automática, serialização, documentação interativa, autenticação com _tokens_ OAuth2 JWT etc. |
|||
* **Senha segura** _hashing_ por padrão. |
|||
* Autenticação **Token JWT**. |
|||
* Modelos **SQLAlchemy** (independente de extensões Flask, para que eles possam ser usados com _workers_ Celery diretamente). |
|||
* Modelos básicos para usuários (modifique e remova conforme suas necessidades). |
|||
* Migrações **Alembic**. |
|||
* **CORS** (_Cross Origin Resource Sharing_ - Compartilhamento de Recursos Entre Origens). |
|||
* _Worker_ **Celery** que pode importar e usar modelos e códigos do resto do _backend_ seletivamente. |
|||
* Testes _backend_ _REST_ baseados no **Pytest**, integrados com Docker, então você pode testar a interação completa da API, independente do banco de dados. Como roda no Docker, ele pode construir um novo repositório de dados do zero toda vez (assim você pode usar ElasticSearch, MongoDB, CouchDB, ou o que quiser, e apenas testar que a API esteja funcionando). |
|||
* Fácil integração com Python através dos **Kernels Jupyter** para desenvolvimento remoto ou no Docker com extensões como Atom Hydrogen ou Visual Studio Code Jupyter. |
|||
* _Frontend_ **Vue**: |
|||
* Gerado com Vue CLI. |
|||
* Controle de **Autenticação JWT**. |
|||
* Visualização de _login_. |
|||
* Após o _login_, visualização do painel de controle principal. |
|||
* Painel de controle principal com criação e edição de usuário. |
|||
* Edição do próprio usuário. |
|||
* **Vuex**. |
|||
* **Vue-router**. |
|||
* **Vuetify** para belos componentes _material design_. |
|||
* **TypeScript**. |
|||
* Servidor Docker baseado em **Nginx** (configurado para rodar "lindamente" com Vue-router). |
|||
* Construção multi-estágio Docker, então você não precisa salvar ou _commitar_ código compilado. |
|||
* Testes _frontend_ rodados na hora da construção (pode ser desabilitado também). |
|||
* Feito tão modular quanto possível, então ele funciona fora da caixa, mas você pode gerar novamente com Vue CLI ou criar conforme você queira, e reutilizar o que quiser. |
|||
* **PGAdmin** para banco de dados PostgreSQL, você pode modificar para usar PHPMyAdmin e MySQL facilmente. |
|||
* **Flower** para monitoração de tarefas Celery. |
|||
* Balanceamento de carga entre _frontend_ e _backend_ com **Traefik**, então você pode ter ambos sob o mesmo domínio, separados por rota, mas servidos por diferentes containers. |
|||
* Integração Traefik, incluindo geração automática de certificados **HTTPS** Let's Encrypt. |
|||
* GitLab **CI** (integração contínua), incluindo testes _frontend_ e _backend_. |
|||
|
|||
## Full Stack FastAPI Couchbase |
|||
|
|||
GitHub: <a href="https://github.com/tiangolo/full-stack-fastapi-couchbase" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-fastapi-couchbase</a> |
|||
|
|||
⚠️ **WARNING** ⚠️ |
|||
|
|||
Se você está iniciando um novo projeto do zero, verifique as alternativas aqui. |
|||
|
|||
Por exemplo, o gerador de projetos <a href="https://github.com/tiangolo/full-stack-fastapi-postgresql" class="external-link" target="_blank">Full Stack FastAPI PostgreSQL</a> pode ser uma alternativa melhor, como ele é ativamente mantido e utilizado. E ele inclui todos os novos recursos e melhorias. |
|||
|
|||
Você ainda é livre para utilizar o gerador baseado em Couchbase se quiser, ele provavelmente ainda funciona bem, e você já tem um projeto gerado com ele que roda bem também (e você provavelmente já atualizou ele para encaixar nas suas necessidades). |
|||
|
|||
Você pode ler mais sobre nas documentaçãoes do repositório. |
|||
|
|||
## Full Stack FastAPI MongoDB |
|||
|
|||
...pode demorar, dependendo do meu tempo disponível e outros fatores. 😅 🎉 |
|||
|
|||
## Modelos de Aprendizado de Máquina com spaCy e FastAPI |
|||
|
|||
GitHub: <a href="https://github.com/microsoft/cookiecutter-spacy-fastapi" class="external-link" target="_blank">https://github.com/microsoft/cookiecutter-spacy-fastapi</a> |
|||
|
|||
### Modelos de Aprendizado de Máquina com spaCy e FastAPI - Recursos |
|||
|
|||
* Integração com modelo NER **spaCy**. |
|||
* Formato de requisição **Busca Cognitiva Azure** acoplado. |
|||
* Servidor Python _web_ **Pronto para Produção** usando Uvicorn e Gunicorn. |
|||
* Implantação **Azure DevOps** Kubernetes (AKS) CI/CD acoplada. |
|||
* **Multilingual** facilmente escolhido como uma das linguagens spaCy acopladas durante a configuração do projeto. |
|||
* **Facilmente extensível** para outros modelos de _frameworks_ (Pytorch, Tensorflow), não apenas spaCy. |
|||
# Full Stack FastAPI Template |
|||
|
|||
_Templates_, embora tipicamente venham com alguma configuração específica, são desenhados para serem flexíveis e customizáveis. Isso permite que você os modifique e adapte para as especificações do seu projeto, fazendo-os um excelente ponto de partida. 🏁 |
|||
|
|||
Você pode usar esse _template_ para começar, já que ele inclui várias configurações iniciais, segurança, banco de dados, e alguns _endpoints_ de API já feitos para você. |
|||
|
|||
Repositório GitHub: <a href="https://github.com/tiangolo/full-stack-fastapi-template" class="external-link" target="_blank">Full Stack FastAPI Template</a> |
|||
|
|||
## Full Stack FastAPI Template - Pilha de Tecnologias e Recursos |
|||
|
|||
- ⚡ [**FastAPI**](https://fastapi.tiangolo.com) para a API do backend em Python. |
|||
- 🧰 [SQLModel](https://sqlmodel.tiangolo.com) para as interações do Python com bancos de dados SQL (ORM). |
|||
- 🔍 [Pydantic](https://docs.pydantic.dev), usado pelo FastAPI, para validação de dados e gerenciamento de configurações. |
|||
- 💾 [PostgreSQL](https://www.postgresql.org) como banco de dados SQL. |
|||
- 🚀 [React](https://react.dev) para o frontend. |
|||
- 💃 Usando TypeScript, hooks, [Vite](https://vitejs.dev), e outras partes de uma _stack_ frontend moderna. |
|||
- 🎨 [Chakra UI](https://chakra-ui.com) para os componentes de frontend. |
|||
- 🤖 Um cliente frontend automaticamente gerado. |
|||
- 🧪 [Playwright](https://playwright.dev) para testes Ponta-a-Ponta. |
|||
- 🦇 Suporte para modo escuro. |
|||
- 🐋 [Docker Compose](https://www.docker.com) para desenvolvimento e produção. |
|||
- 🔒 _Hash_ seguro de senhas por padrão. |
|||
- 🔑 Autenticação por token JWT. |
|||
- 📫 Recuperação de senhas baseada em email. |
|||
- ✅ Testes com [Pytest](https://pytest.org). |
|||
- 📞 [Traefik](https://traefik.io) como proxy reverso / balanceador de carga. |
|||
- 🚢 Instruções de _deployment_ usando Docker Compose, incluindo como configurar um proxy frontend com Traefik para gerenciar automaticamente certificados HTTPS. |
|||
- 🏭 CI (Integração Contínua) e CD (_Deploy_ Contínuo) baseado em GitHub Actions. |
|||
|
@ -0,0 +1,41 @@ |
|||
# Дополнительные статус коды |
|||
|
|||
По умолчанию **FastAPI** возвращает ответы, используя `JSONResponse`, помещая содержимое, которое вы возвращаете из вашей *операции пути*, внутрь этого `JSONResponse`. |
|||
|
|||
Он будет использовать код статуса по умолчанию или тот, который вы укажете в вашей *операции пути*. |
|||
|
|||
## Дополнительные статус коды |
|||
|
|||
Если вы хотите возвращать дополнительный статус код помимо основного, вы можете сделать это, возвращая объект `Response` напрямую, как `JSONResponse`, и устанавливая нужный статус код напрямую. |
|||
|
|||
Например, скажем, вы хотите создать *операцию пути*, которая позволяет обновлять элементы и возвращает HTTP-код 200 "OK" при успешном выполнении. |
|||
|
|||
Но вы также хотите, чтобы она принимала новые элементы. И если элемент ранее не существовал, он создаётся, и возвращался HTTP-код 201 "Created". |
|||
|
|||
Чтобы реализовать это, импортируйте `JSONResponse` и возвращайте ваш контент напрямую, устанавливая нужный `status_code`: |
|||
|
|||
{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *} |
|||
|
|||
/// warning | Внимание |
|||
|
|||
Когда вы возвращаете объект `Response` напрямую, как в примере выше, он будет возвращён как есть. |
|||
|
|||
Он не будет сериализован при помощи модели и т.д. |
|||
|
|||
Убедитесь, что в нём содержатся именно те данные, которые вы хотите, и что значения являются валидным JSON (если вы используете `JSONResponse`). |
|||
|
|||
/// |
|||
|
|||
/// note | Технические детали |
|||
|
|||
Вы также можете использовать `from starlette.responses import JSONResponse`. |
|||
|
|||
**FastAPI** предоставляет тот же `starlette.responses` через `fastapi.responses` просто для вашего удобства, как разработчика. Но большинство доступных Response-классов поступают напрямую из Starlette. То же самое касается и `status`. |
|||
|
|||
/// |
|||
|
|||
## OpenAPI и документация API |
|||
|
|||
Если вы возвращаете дополнительные коды статусов и ответы напрямую, они не будут включены в схему OpenAPI (документацию API), потому что FastAPI не может заранее знать, что вы собираетесь вернуть. |
|||
|
|||
Но вы можете задокументировать это в вашем коде, используя: [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}. |
@ -0,0 +1,21 @@ |
|||
# Расширенное руководство пользователя |
|||
|
|||
## Дополнительные возможности |
|||
|
|||
Основное [Учебник - Руководство пользователя](../tutorial/index.md){.internal-link target=_blank} должно быть достаточно, чтобы познакомить вас со всеми основными функциями **FastAPI**. |
|||
|
|||
В следующих разделах вы увидите другие варианты, конфигурации и дополнительные возможности. |
|||
|
|||
/// tip |
|||
|
|||
Следующие разделы **не обязательно являются "продвинутыми"**. |
|||
|
|||
И вполне возможно, что для вашего случая использования решение находится в одном из них. |
|||
|
|||
/// |
|||
|
|||
## Сначала прочитайте Учебник - Руководство пользователя |
|||
|
|||
Вы все еще можете использовать большинство функций **FastAPI** со знаниями из [Учебник - Руководство пользователя](../tutorial/index.md){.internal-link target=_blank}. |
|||
|
|||
И следующие разделы предполагают, что вы уже прочитали его, и предполагают, что вы знаете эти основные идеи. |
@ -0,0 +1,31 @@ |
|||
# Response - Изменение cтатус кода |
|||
|
|||
Вы, вероятно, уже читали о том, что можно установить [Состояние ответа по умолчанию](../tutorial/response-status-code.md){.internal-link target=_blank}. |
|||
|
|||
Но в некоторых случаях вам нужно вернуть код состояния, отличный от установленного по умолчанию. |
|||
|
|||
## Пример использования |
|||
|
|||
Например, представьте, что вы хотите возвращать HTTP код состояния "OK" `200` по умолчанию. |
|||
|
|||
Но если данные не существовали, вы хотите создать их и вернуть HTTP код состояния "CREATED" `201`. |
|||
|
|||
При этом вы всё ещё хотите иметь возможность фильтровать и преобразовывать возвращаемые данные с помощью `response_model`. |
|||
|
|||
Для таких случаев вы можете использовать параметр `Response`. |
|||
|
|||
## Использование параметра `Response` |
|||
|
|||
Вы можете объявить параметр типа `Response` в вашей *функции обработки пути* (так же как для cookies и headers). |
|||
|
|||
И затем вы можете установить `status_code` в этом *временном* объекте ответа. |
|||
|
|||
{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *} |
|||
|
|||
После этого вы можете вернуть любой объект, который вам нужен, как обычно (`dict`, модель базы данных и т.д.). |
|||
|
|||
И если вы объявили `response_model`, он всё равно будет использоваться для фильтрации и преобразования возвращаемого объекта. |
|||
|
|||
**FastAPI** будет использовать этот *временный* ответ для извлечения кода состояния (а также cookies и headers) и поместит их в финальный ответ, который содержит возвращаемое вами значение, отфильтрованное любым `response_model`. |
|||
|
|||
Вы также можете объявить параметр `Response` в зависимостях и установить код состояния в них. Но помните, что последнее установленное значение будет иметь приоритет. |
@ -0,0 +1,65 @@ |
|||
# Возврат ответа напрямую |
|||
|
|||
Когда вы создаёте **FastAPI** *операцию пути*, вы можете возвращать из неё любые данные: `dict`, `list`, Pydantic-модель, модель базы данных и т.д. |
|||
|
|||
По умолчанию **FastAPI** автоматически преобразует возвращаемое значение в JSON с помощью `jsonable_encoder`, как описано в [JSON кодировщик](../tutorial/encoder.md){.internal-link target=_blank}. |
|||
|
|||
Затем "под капотом" эти данные, совместимые с JSON (например `dict`), помещаются в `JSONResponse`, который используется для отправки ответа клиенту. |
|||
|
|||
Но вы можете возвращать `JSONResponse` напрямую из ваших *операций пути*. |
|||
|
|||
Это может быть полезно, например, если нужно вернуть пользовательские заголовки или куки. |
|||
|
|||
## Возврат `Response` |
|||
|
|||
На самом деле, вы можете возвращать любой объект `Response` или его подкласс. |
|||
|
|||
/// tip | Подсказка |
|||
|
|||
`JSONResponse` сам по себе является подклассом `Response`. |
|||
|
|||
/// |
|||
|
|||
И когда вы возвращаете `Response`, **FastAPI** передаст его напрямую. |
|||
|
|||
Это не приведет к преобразованию данных с помощью Pydantic-моделей, содержимое не будет преобразовано в какой-либо тип и т.д. |
|||
|
|||
Это даёт вам большую гибкость. Вы можете возвращать любые типы данных, переопределять любые объявления или валидацию данных и т.д. |
|||
|
|||
## Использование `jsonable_encoder` в `Response` |
|||
|
|||
Поскольку **FastAPI** не изменяет объект `Response`, который вы возвращаете, вы должны убедиться, что его содержимое готово к отправке. |
|||
|
|||
Например, вы не можете поместить Pydantic-модель в `JSONResponse`, не преобразовав её сначала в `dict` с помощью преобразования всех типов данных (таких как `datetime`, `UUID` и т.д.) в совместимые с JSON типы. |
|||
|
|||
В таких случаях вы можете использовать `jsonable_encoder` для преобразования данных перед передачей их в ответ: |
|||
|
|||
{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *} |
|||
|
|||
/// note | Технические детали |
|||
|
|||
Вы также можете использовать `from starlette.responses import JSONResponse`. |
|||
|
|||
**FastAPI** предоставляет `starlette.responses` через `fastapi.responses` просто для вашего удобства, как разработчика. Но большинство доступных Response-классов поступают напрямую из Starlette. |
|||
|
|||
/// |
|||
|
|||
## Возврат пользовательского `Response` |
|||
|
|||
Пример выше показывает все необходимые части, но он пока не очень полезен, так как вы могли бы просто вернуть `item` напрямую, и **FastAPI** поместил бы его в `JSONResponse`, преобразовав в `dict` и т.д. Всё это происходит по умолчанию. |
|||
|
|||
Теперь давайте посмотрим, как можно использовать это для возврата пользовательского ответа. |
|||
|
|||
Допустим, вы хотите вернуть ответ в формате <a href="https://en.wikipedia.org/wiki/XML" class="external-link" target="_blank">XML</a>. |
|||
|
|||
Вы можете поместить ваш XML-контент в строку, поместить её в `Response` и вернуть: |
|||
|
|||
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} |
|||
|
|||
## Примечания |
|||
|
|||
Когда вы возвращаете объект `Response` напрямую, его данные не валидируются, не преобразуются (не сериализуются) и не документируются автоматически. |
|||
|
|||
Но вы всё равно можете задокументировать это, как описано в [Дополнительные ответы в OpenAPI](additional-responses.md){.internal-link target=_blank}. |
|||
|
|||
В следующих разделах вы увидите, как использовать/объявлять такие кастомные `Response`, при этом сохраняя автоматическое преобразование данных, документацию и т.д. |
@ -0,0 +1,76 @@ |
|||
# Модели параметров cookie |
|||
|
|||
Если у вас есть группа **cookies**, которые связаны между собой, вы можете создать **Pydantic-модель** для их объявления. 🍪 |
|||
|
|||
Это позволит вам **переиспользовать модель** в **разных местах**, а также объявить проверки и метаданные сразу для всех параметров. 😎 |
|||
|
|||
/// note | Заметка |
|||
|
|||
Этот функционал доступен с версии `0.115.0`. 🤓 |
|||
|
|||
/// |
|||
|
|||
/// tip | Совет |
|||
|
|||
Такой же подход применяется для `Query`, `Cookie`, и `Header`. 😎 |
|||
|
|||
/// |
|||
|
|||
## Pydantic-модель для cookies |
|||
|
|||
Объявите параметры **cookie**, которые вам нужны, в **Pydantic-модели**, а затем объявите параметр как `Cookie`: |
|||
|
|||
{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *} |
|||
|
|||
**FastAPI** **извлечёт** данные для **каждого поля** из **cookies**, полученных в запросе, и выдаст вам объявленную Pydantic-модель. |
|||
|
|||
## Проверка сгенерированной документации |
|||
|
|||
Вы можете посмотреть объявленные cookies в графическом интерфейсе Документации по пути `/docs`: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/cookie-param-models/image01.png"> |
|||
</div> |
|||
|
|||
/// info | Дополнительная информация |
|||
|
|||
Имейте в виду, что, поскольку **браузеры обрабатывают cookies** особым образом и под капотом, они **не** позволят **JavaScript** легко получить доступ к ним. |
|||
|
|||
Если вы перейдёте к **графическому интерфейсу документации API** по пути `/docs`, то сможете увидеть **документацию** по cookies для ваших *операций путей*. |
|||
|
|||
Но даже если вы **заполните данные** и нажмёте "Execute", поскольку графический интерфейс Документации работает с **JavaScript**, cookies не будут отправлены, и вы увидите сообщение об **ошибке** как будто не указывали никаких значений. |
|||
|
|||
/// |
|||
|
|||
## Запрет дополнительных cookies |
|||
|
|||
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** cookies, которые вы хотите получать. |
|||
|
|||
Теперь ваш API сам решает, <abbr title="Это шутка, на всякий случай. Это не имеет никакого отношения к согласию на использование cookie, но забавно, что даже API теперь может отклонять несчастные cookies. Съешьте печеньку. 🍪">принимать ли cookies</abbr>. 🤪🍪 |
|||
|
|||
Вы можете сконфигурировать Pydantic-модель так, чтобы запретить (`forbid`) любые дополнительные (`extra`) поля: |
|||
|
|||
{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *} |
|||
|
|||
Если клиент попробует отправить **дополнительные cookies**, то в ответ он получит **ошибку**. |
|||
|
|||
Бедные баннеры cookies, они всеми силами пытаются получить ваше согласие — и всё ради того, чтобы <abbr title="Это ещё одна шутка. Не обращайте на меня внимания. Выпейте кофе со своей печенькой. ☕">API его отклонил</abbr>. 🍪 |
|||
|
|||
Например, если клиент попытается отправить cookie `santa_tracker` со значением `good-list-please`, то в ответ он получит **ошибку**, сообщающую ему, что cookie `santa_tracker` <abbr title="Санта не одобряет пропажу печенья. 🎅 Ладно, больше никаких шуток про печенье.">не разрешён</abbr>: |
|||
|
|||
```json |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "extra_forbidden", |
|||
"loc": ["cookie", "santa_tracker"], |
|||
"msg": "Extra inputs are not permitted", |
|||
"input": "good-list-please" |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
## Заключение |
|||
|
|||
Вы можете использовать **Pydantic-модели** для объявления <abbr title="Съешьте последнюю печеньку, прежде чем уйти. 🍪">**cookies**</abbr> в **FastAPI**. 😎 |
@ -0,0 +1,78 @@ |
|||
# Модели форм |
|||
|
|||
Вы можете использовать **Pydantic-модели** для объявления **полей форм** в FastAPI. |
|||
|
|||
/// info | Дополнительная информация |
|||
|
|||
Чтобы использовать формы, сначала установите <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>. |
|||
|
|||
Убедитесь, что вы создали и активировали [виртуальное окружение](../virtual-environments.md){.internal-link target=_blank}, а затем установите пакет, например: |
|||
|
|||
```console |
|||
$ pip install python-multipart |
|||
``` |
|||
|
|||
/// |
|||
|
|||
/// note | Заметка |
|||
|
|||
Этот функционал доступен с версии `0.113.0`. 🤓 |
|||
|
|||
/// |
|||
|
|||
## Pydantic-модель для формы |
|||
|
|||
Вам просто нужно объявить **Pydantic-модель** с полями, которые вы хотите получить как **поля формы**, а затем объявить параметр как `Form`: |
|||
|
|||
{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *} |
|||
|
|||
**FastAPI** **извлечёт** данные для **каждого поля** из **данных формы** в запросе и выдаст вам объявленную Pydantic-модель. |
|||
|
|||
## Проверка сгенерированной документации |
|||
|
|||
Вы можете посмотреть поля формы в графическом интерфейсе Документации по пути `/docs`: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/request-form-models/image01.png"> |
|||
</div> |
|||
|
|||
## Запрет дополнительных полей формы |
|||
|
|||
В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** поля формы только теми, которые объявлены в Pydantic-модели. И **запретить** любые **дополнительные** поля. |
|||
|
|||
/// note | Заметка |
|||
|
|||
Этот функционал доступен с версии `0.114.0`. 🤓 |
|||
|
|||
/// |
|||
|
|||
Вы можете сконфигурировать Pydantic-модель так, чтобы запретить (`forbid`) все дополнительные (`extra`) поля: |
|||
|
|||
{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *} |
|||
|
|||
Если клиент попробует отправить дополнительные данные, то в ответ он получит **ошибку**. |
|||
|
|||
Например, если клиент попытается отправить поля формы: |
|||
|
|||
* `username`: `Rick` |
|||
* `password`: `Portal Gun` |
|||
* `extra`: `Mr. Poopybutthole` |
|||
|
|||
То в ответ он получит **ошибку**, сообщающую ему, что поле `extra` не разрешено: |
|||
|
|||
```json |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "extra_forbidden", |
|||
"loc": ["body", "extra"], |
|||
"msg": "Extra inputs are not permitted", |
|||
"input": "Mr. Poopybutthole" |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
## Заключение |
|||
|
|||
Вы можете использовать Pydantic-модели для объявления полей форм в FastAPI. 😎 |
@ -0,0 +1,85 @@ |
|||
# Фонові задачі |
|||
|
|||
Ви можете створювати фонові задачі, які будуть виконуватися *після* повернення відповіді. |
|||
|
|||
Це корисно для операцій, які потрібно виконати після обробки запиту, але клієнту не обов’язково чекати завершення цієї операції перед отриманням відповіді. |
|||
|
|||
Приклади використання: |
|||
|
|||
* Надсилання email-сповіщень після виконання певної дії: |
|||
* Підключення до поштового сервера та надсилання листа може займати кілька секунд. Ви можете відразу повернути відповідь, а email відправити у фоні. |
|||
* Обробка даних: |
|||
* Наприклад, якщо отримано файл, який потрібно обробити довготривалим процесом, можна повернути відповідь "Accepted" ("Прийнято", HTTP 202) і виконати обробку файлу у фоні. |
|||
|
|||
## Використання `BackgroundTasks` |
|||
|
|||
Спочатку імпортуйте `BackgroundTasks` і додайте його як параметр у Вашу *функцію операції шляху* (path operation function) до `BackgroundTasks`: |
|||
|
|||
{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *} |
|||
|
|||
**FastAPI** автоматично створить об'єкт `BackgroundTasks` і передасть його у цей параметр. |
|||
|
|||
|
|||
## Створення функції задачі |
|||
|
|||
Створіть функцію, яка буде виконувати фонову задачу. |
|||
|
|||
Це звичайна функція, яка може отримувати параметри. |
|||
|
|||
Вона може бути асинхронною `async def` або звичайною `def` функцією – **FastAPI** обробить її правильно. |
|||
|
|||
У нашому випадку функція записує у файл (імітуючи надсилання email). |
|||
|
|||
І оскільки операція запису не використовує `async` та `await`, ми визначаємо функцію як звичайну `def`: |
|||
|
|||
{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *} |
|||
|
|||
## Додавання фонової задачі |
|||
|
|||
Усередині Вашої *функції обробки шляху*, передайте функцію задачі в об'єкт *background tasks*, використовуючи метод `.add_task()`: |
|||
|
|||
{* ../../docs_src/background_tasks/tutorial001.py hl[14] *} |
|||
|
|||
`.add_task()` приймає аргументи: |
|||
|
|||
* Функція задача, яка буде виконуватися у фоновому режимі (`write_notification`). Зверніть увагу, що передається обʼєкт без дужок. |
|||
* Будь-яка послідовність аргументів, які потрібно передати у функцію завдання у відповідному порядку (`email`). |
|||
* Будь-які іменовані аргументи, які потрібно передати у функцію задачу (`message="some notification"`). |
|||
|
|||
## Впровадження залежностей |
|||
|
|||
Використання `BackgroundTasks` також працює з системою впровадження залежностей. Ви можете оголосити параметр типу `BackgroundTasks` на різних рівнях: у *функції операції шляху*, у залежності (dependable), у під залежності тощо. |
|||
|
|||
**FastAPI** знає, як діяти в кожному випадку і як повторно використовувати один і той самий об'єкт, щоб усі фонові задачі були об’єднані та виконувалися у фоновому режимі після завершення основного запиту. |
|||
|
|||
{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *} |
|||
|
|||
У цьому прикладі повідомлення будуть записані у файл `log.txt` *після* того, як відповідь буде надіслана. |
|||
|
|||
Якщо у запиті був переданий query-параметр, він буде записаний у лог у фоновій задачі. |
|||
|
|||
А потім інша фонова задача, яка створюється у *функції операції шляху*, запише повідомлення з використанням path параметра `email`. |
|||
|
|||
## Технічні деталі |
|||
|
|||
Клас `BackgroundTasks` походить безпосередньо з <a href="https://www.starlette.io/background/" class="external-link" target="_blank">`starlette.background`</a>. |
|||
|
|||
Він імпортується безпосередньо у FastAPI, щоб Ви могли використовувати його з `fastapi` і випадково не імпортували `BackgroundTask` (без s в кінці) з `starlette.background`. |
|||
|
|||
Якщо використовувати лише `BackgroundTasks` (а не `BackgroundTask`), то його можна передавати як параметр у *функції операції шляху*, і **FastAPI** подбає про все інше, так само як і про використання об'єкта `Request`. |
|||
|
|||
Також можна використовувати `BackgroundTask` окремо в FastAPI, але для цього Вам доведеться створити об'єкт у коді та повернути Starlette `Response`, включаючи його. |
|||
|
|||
Детальніше можна почитати в <a href="https://www.starlette.io/background/" class="external-link" target="_blank">офіційній документації Starlette про фонові задачі </a>. |
|||
|
|||
## Застереження |
|||
|
|||
Якщо Вам потрібно виконувати складні фонові обчислення, і при цьому нема потреби запускати їх у тому ж процесі (наприклад, не потрібно спільного доступу до пам’яті чи змінних), можливо, варто скористатися більш потужними інструментами, такими як <a href="https://docs.celeryq.dev" class="external-link" target="_blank">Celery</a>. |
|||
|
|||
Такі інструменти зазвичай потребують складнішої конфігурації та менеджера черги повідомлень/завдань, наприклад, RabbitMQ або Redis. Однак вони дозволяють виконувати фонові задачі в кількох процесах і навіть на кількох серверах. |
|||
|
|||
Якщо ж Вам потрібно отримати доступ до змінних і об’єктів із тієї ж **FastAPI** - програми або виконувати невеликі фонові завдання (наприклад, надсилати сповіщення електронною поштою), достатньо просто використовувати `BackgroundTasks`. |
|||
|
|||
## Підсумок |
|||
|
|||
Імпортуйте та використовуйте `BackgroundTasks` як параметр у *функціях операції шляху* та залежностях, щоб додавати фонові задачі. |
@ -0,0 +1,116 @@ |
|||
# Тіло – Оновлення |
|||
|
|||
## Оновлення з використанням `PUT` |
|||
|
|||
Щоб оновити елемент, Ви можете використати <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`. |
|||
|
|||
{* ../../docs_src/body_updates/tutorial001_py310.py hl[28:33] *} |
|||
|
|||
`PUT` використовується для отримання даних, які мають замінити чинні дані. |
|||
|
|||
### Попередження про заміну |
|||
|
|||
Це означає, що якщо Ви хочете оновити елемент `bar`, використовуючи `PUT` з тілом: |
|||
|
|||
```Python |
|||
{ |
|||
"name": "Barz", |
|||
"price": 3, |
|||
"description": None, |
|||
} |
|||
``` |
|||
|
|||
оскільки він не містить вже збереженого атрибута `"tax": 20.2`, модель введення прийме значення за замовчуванням `"tax": 10.5`. |
|||
|
|||
І дані будуть збережені з цим "новим" значенням `tax` = `10.5`. |
|||
|
|||
## Часткові оновлення з `PATCH` |
|||
|
|||
Ви також можете використовувати операцію <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> для *часткового* оновлення даних. |
|||
|
|||
Це означає, що Ви можете надіслати лише ті дані, які хочете оновити, залишаючи інші без змін. |
|||
|
|||
/// note | Примітка |
|||
|
|||
`PATCH` менш відомий і рідше використовується, ніж `PUT`. |
|||
|
|||
І багато команд використовують лише `PUT`, навіть для часткових оновлень. |
|||
|
|||
Ви **вільні** використовувати їх так, як хочете, **FastAPI** не накладає обмежень. |
|||
|
|||
Але цей посібник показує Вам більш-менш як їх задумано використовувати. |
|||
|
|||
/// |
|||
|
|||
### Використання параметра `exclude_unset` у Pydantic |
|||
|
|||
Якщо Ви хочете отримати часткові оновлення, дуже зручно використовувати параметр `exclude_unset` у методі `.model_dump()` моделі Pydantic. |
|||
|
|||
Наприклад: `item.model_dump(exclude_unset=True)`. |
|||
|
|||
/// info | Інформація |
|||
|
|||
У 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 |
|||
|
|||
Тепер Ви можете створити копію наявної моделі за допомогою `.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] *} |
|||
|
|||
### Підсумок часткових оновлень |
|||
|
|||
У підсумку, щоб застосувати часткові оновлення, Ви: |
|||
|
|||
* (Опціонально) використовуєте `PATCH` замість `PUT`. |
|||
* Отримуєте збережені дані. |
|||
* Поміщаєте ці дані в модель Pydantic. |
|||
* Генеруєте `dict` без значень за замовчуванням з моделі введення (використовуючи `exclude_unset`). |
|||
* Таким чином Ви оновите лише ті значення, які були явно задані користувачем, замість того, щоб перезаписувати вже збережені значення значеннями за замовчуванням з вашої моделі. |
|||
* Створюєте копію збереженої моделі, оновлюючи її атрибути отриманими частковими оновленнями (використовуючи параметр `update`). |
|||
* Перетворюєте скопійовану модель на щось, що можна зберегти у вашу БД (наприклад, використовуючи `jsonable_encoder`). |
|||
* Це можна порівняти з повторним використанням методу `.model_dump()` моделі, але це гарантує (і перетворює) значення у типи даних, які можна перетворити на JSON, наприклад, `datetime` на `str`. |
|||
* Зберігаєте дані у вашу БД. |
|||
* Повертаєте оновлену модель. |
|||
|
|||
{* ../../docs_src/body_updates/tutorial002_py310.py hl[28:35] *} |
|||
|
|||
/// tip | Порада |
|||
|
|||
Насправді Ви можете використовувати цю саму техніку і з операцією HTTP `PUT`. |
|||
|
|||
Але приклад тут використовує `PATCH`, тому що він був створений саме для таких випадків. |
|||
|
|||
/// |
|||
|
|||
/// note | Примітка |
|||
|
|||
Зверніть увагу, що модель запиту все ще проходить валідацію. |
|||
|
|||
Тож, якщо Ви хочете отримувати часткові оновлення, які можуть не містити жодного атрибута, Вам потрібно мати модель, де всі атрибути позначені як необов’язкові (зі значеннями за замовчуванням або `None`). |
|||
|
|||
Щоб розрізняти моделі з усіма необов’язковими значеннями для **оновлення** і моделі з обов’язковими значеннями для **створення**, Ви можете скористатись ідеями, описаними у [Додаткові моделі](extra-models.md){.internal-link target=_blank}. |
|||
|
|||
/// |
@ -0,0 +1,89 @@ |
|||
# CORS (Обмін ресурсами між різними джерелами) |
|||
|
|||
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">CORS або "Обмін ресурсами між різними джерелами"</a> є ситуація, коли фронтенд, що працює в браузері, містить JavaScript-код, який взаємодіє з бекендом, розташованим в іншому "джерелі" (origin). |
|||
|
|||
## Джерело (Origin) |
|||
|
|||
Джерело визначається комбінацією протоколу (`http`, `https`), домену (`myapp.com`, `localhost`, `localhost.tiangolo.com`), порту (`80`, `443`, `8080`). |
|||
|
|||
|
|||
Наприклад, такі адреси вважаються різними джерелами: |
|||
|
|||
* `http://localhost` |
|||
* `https://localhost` |
|||
* `http://localhost:8080` |
|||
|
|||
Навіть якщо вони всі містять `localhost`, вони мають різні протоколи або порти, що робить їх окремими "джерелами". |
|||
|
|||
## Кроки |
|||
|
|||
Припустимо, що Ваш фронтенд працює в браузері на `http://localhost:8080`, а його JavaScript намагається відправити запит до бекенду, який працює на `http://localhost` (Оскільки ми не вказуємо порт, браузер за замовчуванням припускає порт `80`). |
|||
|
|||
Потім браузер надішле HTTP-запит `OPTIONS` до бекенду на порту `:80`, і якщо бекенд надішле відповідні заголовки, що дозволяють комунікацію з цього іншого джерела (`http://localhost:8080`), тоді браузер на порту `:8080` дозволить JavaScript у фронтенді надіслати свій запит до бекенду на порту `:80`. |
|||
|
|||
Щоб досягти цього, бекенд на порту `:80` повинен мати список "дозволених джерел". |
|||
|
|||
У цьому випадку список має містити `http://localhost:8080`, щоб фронтенд на порту `:8080` працював коректно. |
|||
|
|||
## Символьне підставляння |
|||
|
|||
Можна також оголосити список як `"*"` ("символьне підставляння"), що означає дозвіл для всіх джерел. |
|||
|
|||
Однак це дозволить лише певні типи комунікації, виключаючи все, що пов'язане з обліковими даними: Cookies, заголовки авторизації, такі як ті, що використовуються з Bearer токенами тощо. |
|||
|
|||
Тому для коректної роботи краще явно вказувати дозволені джерела. |
|||
|
|||
## Використання `CORSMiddleware` |
|||
|
|||
Ви можете налаштувати це у Вашому додатку **FastAPI** за допомогою `CORSMiddleware`. |
|||
|
|||
* Імпортуйте `CORSMiddleware`. |
|||
* Створіть список дозволених джерел (у вигляді рядків). |
|||
* Додайте його як "middleware" у Ваш додаток **FastAPI**. |
|||
|
|||
|
|||
Також можна вказати, чи дозволяє Ваш бекенд: |
|||
|
|||
* Облікові дані (заголовки авторизації, сookies, тощо). |
|||
* Конкретні HTTP-методи (`POST`, `PUT`) або всі за допомогою `"*"` |
|||
* Конкретні HTTP-заголовки або всі за допомогою `"*"`. |
|||
|
|||
|
|||
{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *} |
|||
|
|||
Параметри за замовчуванням у `CORSMiddleware` є досить обмеженими, тому Вам потрібно явно вказати конкретні джерела, методи або заголовки, щоб браузери могли використовувати їх у контексті запитів між різними доменами. |
|||
|
|||
|
|||
Підтримуються такі аргументи: |
|||
|
|||
* `allow_origins` - Список джерел, яким дозволено здійснювати міждоменні запити. Наприклад `['https://example.org', 'https://www.example.org']`. Ви можете використовувати ['*'], щоб дозволити всі джерела. |
|||
* `allow_origin_regex` - Рядок регулярного виразу для відповідності джерелам, яким дозволено здійснювати міждоменні запити. Наприклад, `'https://.*\.example\.org'`. |
|||
* `allow_methods` - Список HTTP-методів, дозволених для міждоменних запитів. За замовчуванням `['GET']`. Ви можете використовувати `['*']`, щоб дозволити всі стандартні методи. |
|||
* `allow_headers` - Список HTTP-заголовків, які підтримуються для міждоменних запитів. За замовчуванням `[]`. Ви можете використовувати `['*']`, щоб дозволити всі заголовки. Заголовки `Accept`, `Accept-Language`, `Content-Language` і `Content-Type` завжди дозволені для <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-запити (preflight requests) |
|||
|
|||
Це будь-які `OPTIONS` - запити, що містять заголовки `Origin` та `Access-Control-Request-Method`. |
|||
|
|||
У такому випадку middleware перехопить вхідний запит і відповість відповідними CORS-заголовками, повертаючи або `200`, або `400` для інформаційних цілей. |
|||
|
|||
### Прості запити |
|||
|
|||
Будь-які запити із заголовком `Origin`. У цьому випадку middleware пропустить запит як звичайний, але додасть відповідні CORS-заголовки у відповідь. |
|||
|
|||
## Додаткова інформація |
|||
|
|||
Більше про <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 | Технічні деталі |
|||
|
|||
Також можна використовувати `from starlette.middleware.cors import CORSMiddleware`. |
|||
|
|||
**FastAPI** надає кілька middleware у `fastapi.middleware` для зручності розробників. Але більшість доступних middleware походять безпосередньо зі Starlette. |
|||
|
|||
/// |
@ -0,0 +1,255 @@ |
|||
# Обробка Помилок |
|||
|
|||
Є багато ситуацій, коли потрібно повідомити клієнта, який використовує Ваш API, про помилку. |
|||
|
|||
Цим клієнтом може бути браузер із фронтендом, код іншого розробника, IoT-пристрій тощо. |
|||
|
|||
Можливо, Вам потрібно повідомити клієнта, що: |
|||
|
|||
* У нього недостатньо прав для виконання цієї операції. |
|||
* Він не має доступу до цього ресурсу. |
|||
* Елемент, до якого він намагається отримати доступ, не існує. |
|||
* тощо. |
|||
|
|||
У таких випадках зазвичай повертається **HTTP статус-код** в діапазоні **400** (від 400 до 499). |
|||
|
|||
Це схоже на HTTP статус-коди 200 (від 200 до 299). Ці "200" статус-коди означають, що запит пройшов успішно. |
|||
|
|||
Статус-коди в діапазоні 400 означають, що сталася помилка з боку клієнта. |
|||
|
|||
Пам'ятаєте всі ці помилки **404 Not Found** (і жарти про них)? |
|||
|
|||
## Використання `HTTPException` |
|||
|
|||
Щоб повернути HTTP-відповіді з помилками клієнту, використовуйте `HTTPException`. |
|||
|
|||
### Імпорт `HTTPException` |
|||
|
|||
{* ../../docs_src/handling_errors/tutorial001.py hl[1] *} |
|||
|
|||
### Використання `HTTPException` у коді |
|||
|
|||
`HTTPException` — це звичайна помилка Python із додатковими даними, які стосуються API. |
|||
|
|||
Оскільки це помилка Python, Ви не `повертаєте` його, а `генеруєте` (генеруєте помилку). |
|||
|
|||
Це також означає, що якщо Ви перебуваєте всередині допоміжної функції, яку викликаєте всередині своєї *функції операції шляху*, і там генеруєте `HTTPException`, всередині цієї допоміжної функції, то решта коду в *функції операції шляху* не буде виконана. Запит одразу завершиться, і HTTP-помилка з `HTTPException` буде надіслана клієнту. |
|||
|
|||
Перевага використання `генерації` (raise) помилки замість `повернення` значення (return) стане більш очевидним в розділі про Залежності та Безпеку. |
|||
|
|||
У цьому прикладі, якщо клієнт запитує елемент за ID, якого не існує, буде згенеровано помилку зі статус-кодом `404`: |
|||
|
|||
{* ../../docs_src/handling_errors/tutorial001.py hl[11] *} |
|||
|
|||
### Отримана відповідь |
|||
|
|||
Якщо клієнт робить запит за шляхом `http://example.com/items/foo` (де `item_id` `"foo"`), він отримає статус-код 200 і JSON відповідь: |
|||
|
|||
```JSON |
|||
{ |
|||
"item": "The Foo Wrestlers" |
|||
} |
|||
``` |
|||
|
|||
Але якщо клієнт робить запит на `http://example.com/items/bar` (де `item_id` має не існуюче значення `"bar"`), то отримає статус-код 404 (помилка "не знайдено") та відповідь: |
|||
|
|||
```JSON |
|||
{ |
|||
"detail": "Item not found" |
|||
} |
|||
``` |
|||
|
|||
/// tip | Порада |
|||
|
|||
Під час виклику `HTTPException` Ви можете передати будь-яке значення, яке може бути перетворене в JSON, як параметр `detail`, а не лише рядок (`str`). |
|||
|
|||
Ви можете передати `dict`, `list` тощо. |
|||
|
|||
Вони обробляються автоматично за допомогою **FastAPI** та перетворюються в JSON. |
|||
|
|||
/// |
|||
|
|||
## Додавання власних заголовків |
|||
|
|||
Іноді потрібно додати власні заголовки до HTTP-помилки, наприклад, для певних типів безпеки. |
|||
|
|||
Ймовірно, Вам не доведеться використовувати це безпосередньо у своєму коді. |
|||
|
|||
Але якщо Вам знадобиться це для складного сценарію, Ви можете додати власні заголовки: |
|||
|
|||
{* ../../docs_src/handling_errors/tutorial002.py hl[14] *} |
|||
|
|||
## Встановлення власних обробників помилок |
|||
|
|||
Ви можете додати власні обробники помилок за допомогою <a href="https://www.starlette.io/exceptions/" class="external-link" target="_blank">тих самих утиліт обробки помилок зі Starlette</a>. |
|||
|
|||
Припустимо, у Вас є власний обʼєкт помилки `UnicornException`, яке Ви (або бібліотека, яку Ви використовуєте) може `згенерувати` (`raise`). |
|||
|
|||
І Ви хочете обробляти це виключення глобально за допомогою FastAPI. |
|||
|
|||
Ви можете додати власний обробник виключень за допомогою `@app.exception_handler()`: |
|||
|
|||
{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *} |
|||
|
|||
Тут, якщо Ви звернетеся до `/unicorns/yolo`, то згенерується помилка `UnicornException`. |
|||
|
|||
Але вона буде оброблена функцією-обробником `unicorn_exception_handler`. |
|||
|
|||
Отже, Ви отримаєте зрозумілу помилку зі HTTP-статусом `418` і JSON-відповіддю: |
|||
|
|||
```JSON |
|||
{"message": "Oops! yolo did something. There goes a rainbow..."} |
|||
``` |
|||
|
|||
/// note | Технічні деталі |
|||
|
|||
Ви також можете використовувати `from starlette.requests import Request` і `from starlette.responses import JSONResponse`. |
|||
|
|||
**FastAPI** надає ті самі `starlette.responses`, що й `fastapi.responses`, просто для зручності розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette. Те ж саме стосується і `Request`. |
|||
|
|||
/// |
|||
|
|||
## Перевизначення обробників помилок за замовчуванням |
|||
|
|||
**FastAPI** має кілька обробників помилок за замовчуванням. |
|||
|
|||
Ці обробники відповідають за повернення стандартних JSON-відповідей, коли Ви `генеруєте` (`raise`) `HTTPException`, а також коли запит містить некоректні дані. |
|||
|
|||
Ви можете перевизначити ці обробники, створивши власні. |
|||
|
|||
### Перевизначення помилок валідації запиту |
|||
|
|||
Коли запит містить некоректні дані, **FastAPI** генерує `RequestValidationError`. |
|||
|
|||
І також включає обробник помилок за замовчуванням для нього. |
|||
|
|||
Щоб перевизначити його, імпортуйте `RequestValidationError` і використовуйте його з `@app.exception_handler(RequestValidationError)` для декорування обробника помилок. |
|||
|
|||
Обробник помилок отримує `Request` і саму помилку. |
|||
|
|||
{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *} |
|||
|
|||
Тепер, якщо Ви перейдете за посиланням `/items/foo`, замість того, щоб отримати стандартну JSON-помилку: |
|||
|
|||
```JSON |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"loc": [ |
|||
"path", |
|||
"item_id" |
|||
], |
|||
"msg": "value is not a valid integer", |
|||
"type": "type_error.integer" |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
Ви отримаєте текстову версію: |
|||
|
|||
``` |
|||
1 validation error |
|||
path -> item_id |
|||
value is not a valid integer (type=type_error.integer) |
|||
``` |
|||
|
|||
#### `RequestValidationError` проти `ValidationError` |
|||
|
|||
/// warning | Увага |
|||
|
|||
Це технічні деталі, які Ви можете пропустити, якщо вони зараз не важливі для Вас. |
|||
|
|||
/// |
|||
|
|||
`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. |
|||
|
|||
/// |
|||
|
|||
### Використання тіла `RequestValidationError` |
|||
|
|||
`RequestValidationError` містить `body`, який він отримав із некоректними даними. |
|||
|
|||
Ви можете використовувати це під час розробки свого додатка, щоб логувати тіло запиту та налагоджувати його, повертати користувачеві тощо. |
|||
|
|||
{* ../../docs_src/handling_errors/tutorial005.py hl[14] *} |
|||
|
|||
Тепер спробуйте надіслати некоректний елемент, наприклад: |
|||
|
|||
```JSON |
|||
{ |
|||
"title": "towel", |
|||
"size": "XL" |
|||
} |
|||
``` |
|||
Ви отримаєте відповідь, яка повідомить Вам, які саме дані є некоректні у вашому тілі запиту: |
|||
|
|||
|
|||
```JSON hl_lines="12-15" |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"loc": [ |
|||
"body", |
|||
"size" |
|||
], |
|||
"msg": "value is not a valid integer", |
|||
"type": "type_error.integer" |
|||
} |
|||
], |
|||
"body": { |
|||
"title": "towel", |
|||
"size": "XL" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
#### `HTTPException` FastAPI проти `HTTPException` Starlette |
|||
|
|||
**FastAPI** має власний `HTTPException`. |
|||
|
|||
І клас помилки `HTTPException` в **FastAPI** успадковується від класу помилки `HTTPException` в Starlette. |
|||
|
|||
Єдина різниця полягає в тому, що `HTTPException` в **FastAPI** приймає будь-які дані, які можна перетворити на JSON, для поля `detail`, тоді як `HTTPException` у Starlette приймає тільки рядки. |
|||
|
|||
Отже, Ви можете продовжувати використовувати `HTTPException` в **FastAPI** як зазвичай у своєму коді. |
|||
|
|||
Але коли Ви реєструєте обробник виключень, слід реєструвати його для `HTTPException` зі Starlette. |
|||
|
|||
Таким чином, якщо будь-яка частина внутрішнього коду Starlette або розширення чи плагін Starlette згенерує (raise) `HTTPException`, Ваш обробник зможе перехопити та обробити її. |
|||
|
|||
У цьому прикладі, щоб мати можливість використовувати обидва `HTTPException` в одному коді, помилка Starlette перейменовується на `StarletteHTTPException`: |
|||
|
|||
```Python |
|||
from starlette.exceptions import HTTPException as StarletteHTTPException |
|||
``` |
|||
|
|||
### Повторне використання обробників помилок **FastAPI** |
|||
|
|||
Якщо Ви хочете використовувати помилки разом із такими ж обробниками помилок за замовчуванням, як у **FastAPI**, Ви можете імпортувати та повторно використовувати їх із `fastapi.exception_handlers`: |
|||
|
|||
{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *} |
|||
|
|||
У цьому прикладі Ви просто використовуєте `print` для виведення дуже інформативного повідомлення, але Ви зрозуміли основну ідею. Ви можете обробити помилку та повторно використовувати обробники помилок за замовчуванням. |
@ -0,0 +1,75 @@ |
|||
# Middleware (Проміжний шар) |
|||
|
|||
У **FastAPI** можна додавати middleware (проміжний шар). |
|||
|
|||
"Middleware" — це функція, яка працює з кожним **запитом** перед його обробкою будь-якою конкретною *операцією шляху* (*path operation*), а також з кожною **відповіддю** перед її поверненням. |
|||
|
|||
* Middleware отримує кожен **запит**, що надходить до Вашого застосунку. |
|||
* Може виконати певні дії із цим **запитом** або запустити необхідний код. |
|||
* Далі передає **запит** для обробки основним застосунком (*операцією шляху*). |
|||
* Отримує **відповідь**, сформовану застосунком (*операцією шляху*). |
|||
* Може змінити цю **відповідь** або виконати додатковий код. |
|||
* Повертає **відповідь** клієнту. |
|||
|
|||
/// note | Технічні деталі |
|||
|
|||
Якщо у Вас є залежності з `yield`, код виходу виконається *після* middleware. |
|||
|
|||
Якщо були заплановані фонові задачі (background tasks - розглянуто далі), вони виконаються *після* всіх middleware. |
|||
|
|||
/// |
|||
|
|||
## Створення middleware |
|||
|
|||
Щоб створити middleware, Ви використовуєте декоратор `@app.middleware("http")` на функції. |
|||
|
|||
Функція middleware отримує: |
|||
|
|||
* `Запит`. |
|||
* Функцію `call_next`, яка приймає `запит` як параметр. |
|||
* Ця функція передає `запит` відповідній *операції шляху*. |
|||
* Потім вона повертає `відповідь`, згенеровану цією *операцією шляху*. |
|||
|
|||
* Ви можете ще змінити `відповідь` перед тим, як повернути її. |
|||
|
|||
|
|||
{* ../../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>. |
|||
|
|||
Але якщо у Вас є власні заголовки, які Ви хочете, щоб браузерний клієнт міг побачити, потрібно додати їх до Вашої конфігурації CORS (див. [CORS (Обмін ресурсами між різними джерелами)](cors.md){.internal-link target=_blank} за допомогою параметра `expose_headers`, описаного в <a href="https://www.starlette.io/middleware/#corsmiddleware" class="external-link" target="_blank">документації Starlette по CORS</a>. |
|||
|
|||
/// |
|||
|
|||
/// note | Технічні деталі |
|||
|
|||
Ви також можете використати `from starlette.requests import Request`. |
|||
|
|||
**FastAPI** надає це для Вашої зручності як розробника. Але він походить безпосередньо зі Starlette. |
|||
|
|||
/// |
|||
|
|||
### До і після `response`(`відповіді`) |
|||
|
|||
Ви можете додати код, який буде виконуватися з `запитом` (`request`), до того, як його обробить будь-яка *операція шляху* (*path operation*). |
|||
|
|||
Також Ви можете додати код, який буде виконуватися після того, як `відповідь` (`response`) буде згенеровано, перед тим як його повернути. |
|||
|
|||
Наприклад, Ви можете додати власний заголовок `X-Process-Time`, який міститиме час у секундах, який витратився на обробку запиту та генерацію відповіді: |
|||
|
|||
{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *} |
|||
|
|||
|
|||
/// 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()` оскільки він може бути більш точним для таких випадків. 🤓 |
|||
|
|||
/// |
|||
|
|||
## Інші middlewares |
|||
|
|||
Ви можете пізніше прочитати більше про інші middlewares в [Advanced User Guide: Advanced Middleware](../advanced/middleware.md){.internal-link target=_blank}. |
|||
|
|||
Ви дізнаєтесь, як обробляти <abbr title="Cross-Origin Resource Sharing">CORS</abbr> за допомогою middleware в наступному розділі. |
@ -0,0 +1,165 @@ |
|||
# Path Параметри та валідація числових даних |
|||
|
|||
Так само як Ви можете оголошувати додаткові перевірки та метадані для query параметрів за допомогою `Query`, Ви можете оголошувати той самий тип перевірок і метаданих для параметрів шляху за допомогою `Path`. |
|||
|
|||
## Імпорт Path |
|||
|
|||
Спочатку імпортуйте `Path` з `fastapi` і імпортуйте `Annotated`: |
|||
|
|||
{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[1,3] *} |
|||
|
|||
/// info | Інформація |
|||
|
|||
FastAPI додав підтримку `Annotated` (і почав рекомендувати його використання) у версії 0.95.0. |
|||
|
|||
Якщо у Вас стара версія, при спробі використати `Annotated` можуть виникати помилки. |
|||
|
|||
Переконайтеся, що Ви [оновили версію FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} принаймні до версії 0.95.1 перед використанням `Annotated`. |
|||
|
|||
/// |
|||
|
|||
## Оголошення метаданих |
|||
|
|||
Ви можете оголошувати всі ті ж параметри, що і для `Query`. |
|||
|
|||
Наприклад, щоб оголосити значення метаданих `title` для параметра шляху `item_id`, Ви можете написати: |
|||
|
|||
{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *} |
|||
|
|||
/// note | Примітка |
|||
|
|||
Параметр шляху завжди є обов’язковим, оскільки він має бути частиною шляху. Навіть якщо Ви оголосите його зі значенням `None` або встановите значення за замовчуванням — він все одно залишатиметься обов’язковим. |
|||
|
|||
/// |
|||
|
|||
## Упорядковуйте параметри, як Вам потрібно |
|||
|
|||
/// tip | Підказка |
|||
|
|||
Це, мабуть, не настільки важливо або необхідно, якщо Ви використовуєте `Annotated`. |
|||
|
|||
/// |
|||
|
|||
Припустимо, Ви хочете оголосити параметр запиту `q` як обов’язковий `str`. |
|||
|
|||
І Вам не потрібно оголошувати нічого іншого для цього параметра, тому немає потреби використовувати `Query`. |
|||
|
|||
Але Вам все одно потрібно використовувати `Path` для параметра шляху `item_id`. І з певних причин Ви не хочете використовувати `Annotated`. |
|||
|
|||
Python видасть помилку, якщо розмістити значення з "default" перед значенням, яке не має "default". |
|||
|
|||
Але Ви можете змінити порядок і розмістити значення без значення за замовчуванням (параметр запиту `q`) першим. |
|||
|
|||
|
|||
Для **FastAPI** порядок не має значення. Він визначає параметри за їх іменами, типами та значеннями за замовчуванням (`Query`, `Path` тощо) і не звертає уваги на порядок. |
|||
|
|||
Тому Ви можете оголосити Вашу функцію так: |
|||
|
|||
//// tab | Python 3.8 non-Annotated |
|||
|
|||
/// tip | Підказка |
|||
|
|||
За можливості віддавайте перевагу версії з використанням `Annotated`. |
|||
|
|||
/// |
|||
|
|||
{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *} |
|||
|
|||
//// |
|||
|
|||
Але майте на увазі, що якщо Ви використовуєте `Annotated`, ця проблема не виникне, оскільки Ви не використовуєте значення за замовчуванням для параметрів `Query()` або `Path()`. |
|||
|
|||
{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} |
|||
|
|||
## Упорядковуйте параметри за потребою, хитрощі |
|||
|
|||
/// tip | Підказка |
|||
|
|||
Це, мабуть, не настільки важливо або необхідно, якщо Ви використовуєте `Annotated`. |
|||
|
|||
/// |
|||
|
|||
Ось **невелика хитрість**, яка може стати в пригоді, хоча вона рідко знадобиться. |
|||
|
|||
Якщо Ви хочете: |
|||
|
|||
* оголосити параметр запиту `q` без використання `Query` або значення за замовчуванням |
|||
* оголосити параметр шляху `item_id`, використовуючи `Path` |
|||
* розмістити їх у різному порядку |
|||
* не використовувати `Annotated` |
|||
|
|||
...у Python є спеціальний синтаксис для цього. |
|||
|
|||
Передайте `*` як перший параметр функції. |
|||
|
|||
Python нічого не зробить із цією `*`, але розпізнає, що всі наступні параметри слід викликати як аргументи за ключовим словом (пари ключ-значення), також відомі як <abbr title="From: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>. Навіть якщо вони не мають значення за замовчуванням. |
|||
|
|||
{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *} |
|||
|
|||
### Краще з `Annotated` |
|||
|
|||
Майте на увазі, якщо Ви використовуєте `Annotated`, оскільки Ви не використовуєте значення за замовчуванням для параметрів функції, цієї проблеми не виникне, і, швидше за все, Вам не потрібно буде використовувати `*`. |
|||
|
|||
{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *} |
|||
|
|||
## Валідація числових даних: більше або дорівнює |
|||
|
|||
За допомогою `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] *} |
|||
|
|||
## Валідація числових даних: більше ніж і менше або дорівнює |
|||
|
|||
Те саме застосовується до: |
|||
|
|||
* `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, більше ніж і менше ніж |
|||
|
|||
Валідація чисел також працює для значень типу `float`. |
|||
|
|||
Ось де стає важливо мати можливість оголошувати <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>. |
|||
|
|||
{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} |
|||
|
|||
## Підсумок |
|||
|
|||
За допомогою `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 (менше або дорівнює) |
|||
|
|||
/// info | Інформація |
|||
|
|||
`Query`, `Path` та інші класи, які Ви побачите пізніше, є підкласами спільного класу `Param`. |
|||
|
|||
Всі вони мають однакові параметри для додаткових перевірок і метаданих, які Ви вже бачили. |
|||
|
|||
/// |
|||
|
|||
/// note | Технічні деталі |
|||
|
|||
Коли Ви імпортуєте `Query`, `Path` та інші з `fastapi`, насправді це функції. |
|||
|
|||
При виклику вони повертають екземпляри класів з такими ж іменами. |
|||
|
|||
Тобто Ви імпортуєте `Query`, яка є функцією. А коли Ви її викликаєте, вона повертає екземпляр класу, який теж називається `Query`. |
|||
|
|||
Ці функції створені таким чином (замість використання класів напряму), щоб Ваш редактор не відзначав їхні типи як помилки. |
|||
|
|||
Таким чином, Ви можете користуватися своїм звичайним редактором і інструментами для програмування без додаткових налаштувань для ігнорування таких помилок. |
|||
|
|||
/// |
@ -0,0 +1,68 @@ |
|||
# Моделі Query параметрів |
|||
|
|||
Якщо у Вас є група **query параметрів**, які пов’язані між собою, Ви можете створити **Pydantic-модель** для їх оголошення. |
|||
|
|||
Це дозволить Вам **повторно використовувати модель** у **різних місцях**, а також оголошувати перевірки та метадані для всіх параметрів одночасно. 😎 |
|||
|
|||
/// note | Примітка |
|||
|
|||
Ця можливість підтримується, починаючи з версії FastAPI `0.115.0`. 🤓 |
|||
|
|||
/// |
|||
|
|||
## Query параметри з Pydantic-моделлю |
|||
|
|||
Оголосіть **query параметри**, які Вам потрібні, у **Pydantic-моделі**, а потім оголосіть цей параметр як `Query`: |
|||
|
|||
{* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *} |
|||
|
|||
**FastAPI** буде **витягувати** дані для **кожного поля** з **query параметрів** у запиті та передавати їх у визначену вами Pydantic-модель. |
|||
|
|||
## Перевірте документацію |
|||
|
|||
Ви можете побачити параметри запиту в UI документації за `/docs`: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/query-param-models/image01.png"> |
|||
</div> |
|||
|
|||
## Заборона зайвих Query параметрів |
|||
|
|||
У деяких особливих випадках (ймовірно, не дуже поширених) Ви можете захотіти **обмежити** query параметри, які дозволено отримувати. |
|||
|
|||
Ви можете використати конфігурацію моделі Pydantic, щоб заборонити (`forbid`) будь-які зайві (`extra`) поля: |
|||
|
|||
{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *} |
|||
|
|||
Якщо клієнт спробує надіслати **зайві** дані у **query параметрах**, він отримає **помилку**. |
|||
|
|||
Наприклад, якщо клієнт спробує надіслати query параметр `tool` зі значенням `plumbus`, як у цьому запиті: |
|||
|
|||
```http |
|||
https://example.com/items/?limit=10&tool=plumbus |
|||
``` |
|||
|
|||
Він отримає відповідь з **помилкою**, яка повідомить, що query параметр `tool ` не дозволено: |
|||
|
|||
```json |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "extra_forbidden", |
|||
"loc": ["query", "tool"], |
|||
"msg": "Extra inputs are not permitted", |
|||
"input": "plumbus" |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
## Підсумок |
|||
|
|||
Ви можете використовувати **Pydantic-моделі** для оголошення **query параметрів** у **FastAPI**. 😎 |
|||
|
|||
/// tip | Підказка |
|||
|
|||
Спойлер: Ви також можете використовувати Pydantic-моделі для оголошення cookie та заголовків, але про це Ви дізнаєтеся пізніше в цьому посібнику. 🤫 |
|||
|
|||
/// |
@ -0,0 +1,491 @@ |
|||
# Query параметри та валідація рядків |
|||
|
|||
**FastAPI** дозволяє оголошувати додаткову інформацію та виконувати валідацію для Ваших параметрів. |
|||
|
|||
Розглянемо цей додаток як приклад: |
|||
|
|||
{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *} |
|||
|
|||
Query параметр `q` має тип `str | None`, що означає, що він може бути як `str`, так і `None`. За замовчуванням він має значення `None`, тому FastAPI розуміє, що цей параметр не є обов'язковим. |
|||
|
|||
/// note | Примітка |
|||
|
|||
FastAPI знає, що `q` не є обов’язковим, завдяки значенню за замовчуванням `= None`. |
|||
|
|||
Використання `str | None` дозволить Вашому редактору коду надавати кращу підтримку та виявляти помилки. |
|||
|
|||
/// |
|||
|
|||
## Додаткова валідація |
|||
|
|||
Ми хочемо, щоб навіть якщо `q` є необов’язковим, **його довжина не перевищувала 50 символів**, якщо він все ж буде переданий. |
|||
|
|||
### Імпорт `Query` та `Annotated` |
|||
|
|||
Щоб це зробити, спочатку імпортуємо: |
|||
|
|||
* `Query` з `fastapi` |
|||
* `Annotated` з `typing` |
|||
|
|||
{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *} |
|||
|
|||
/// info | Інформація |
|||
|
|||
FastAPI додав підтримку `Annotated` (і почав рекомендувати його) у версії 0.95.0. |
|||
|
|||
Якщо у Вас старіша версія, під час використання `Annotated` можуть виникати помилки. |
|||
|
|||
Переконайтеся, що Ви [оновили версію FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} до принаймні 0.95.1, перш ніж використовувати `Annotated`. |
|||
|
|||
/// |
|||
|
|||
## Використання `Annotated` у типі параметра `q` |
|||
|
|||
Пам’ятаєте, як я раніше розповідав, що `Annotated` можна використовувати для додавання метаданих до параметрів у [Вступі до типів Python](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}? |
|||
|
|||
Зараз саме час використати його разом із FastAPI. 🚀 |
|||
|
|||
Раніше ми мали таку анотацію типу: |
|||
|
|||
//// tab | Python 3.10+ |
|||
|
|||
```Python |
|||
q: str | None = None |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python |
|||
q: Union[str, None] = None |
|||
``` |
|||
|
|||
//// |
|||
|
|||
Тепер ми загорнемо її у `Annotated`, і отримаємо: |
|||
|
|||
//// tab | Python 3.10+ |
|||
|
|||
```Python |
|||
q: Annotated[str | None] = None |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python |
|||
q: Annotated[Union[str, None]] = None |
|||
``` |
|||
|
|||
//// |
|||
|
|||
Обидві ці версії означають одне й те саме: `q` — це параметр, який може бути `str` або `None`, і за замовчуванням має значення `None`. |
|||
|
|||
А тепер переходимо до цікавого! 🎉 |
|||
|
|||
## Додавання `Query` до `Annotated` у параметр `q` |
|||
|
|||
Тепер, коли у нас є `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 символів. 😎 |
|||
|
|||
/// tip | Підказка |
|||
|
|||
Ми використовуємо `Query()`, оскільки це **query параметр**. Далі ми розглянемо інші варіанти, як-от `Path()`, `Body()`, `Header()` та `Cookie()`, які приймають ті самі аргументи, що й `Query()`. |
|||
|
|||
/// |
|||
|
|||
Тепер FastAPI: |
|||
|
|||
* **Перевірить** дані, щоб переконатися, що їхня довжина не перевищує 50 символів |
|||
* Покажe **чітку помилку** клієнту, якщо дані недійсні |
|||
* **Задокументує** параметр в OpenAPI-схемі *операції шляху* (що відобразиться в **автоматично згенерованій документації**) |
|||
|
|||
## Альтернативний (застарілий) метод: Query як значення за замовчуванням |
|||
|
|||
У попередніх версіях FastAPI (до <abbr title="до 2023-03">0.95.0</abbr>) `Query` використовувався як значення за замовчуванням для параметра, а не всередині `Annotated`. Ви, ймовірно, побачите код, який використовує цей підхід, тому варто розглянути його. |
|||
|
|||
/// tip | Підказка |
|||
|
|||
Для нового коду та коли це можливо, використовуйте `Annotated`, як показано вище. Це має багато переваг (пояснених нижче) і не має недоліків. 🍰 |
|||
|
|||
/// |
|||
|
|||
Раніше ми писали `Query()` як значення за замовчуванням для параметра функції, встановлюючи `max_length` у 50: |
|||
|
|||
{* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *} |
|||
|
|||
Оскільки в цьому випадку (без `Annotated`) нам потрібно замінити `None` у функції на `Query()`, тепер ми повинні явно встановити значення за замовчуванням через параметр `Query(default=None)`. Це виконує ту саму роль визначення значення за замовчуванням (принаймні для FastAPI). |
|||
|
|||
Таким чином: |
|||
|
|||
```Python |
|||
q: str | None = Query(default=None) |
|||
``` |
|||
|
|||
...робить параметр необов’язковим зі значенням за замовчуванням `None`, що еквівалентно: |
|||
|
|||
|
|||
```Python |
|||
q: str | None = None |
|||
``` |
|||
Але у версії з `Query` ми явно вказуємо, що це query параметр. |
|||
|
|||
Далі ми можемо передавати `Query` додаткові параметри, зокрема `max_length`, який застосовується до рядків: |
|||
|
|||
```Python |
|||
q: str | None = Query(default=None, max_length=50) |
|||
``` |
|||
|
|||
Це забезпечить валідацію даних, виведе зрозумілу помилку у разі недійсних даних і задокументує параметр у схемі OpenAPI *операції шляху*. |
|||
|
|||
### `Query` як значення за замовчуванням або всередині `Annotated` |
|||
|
|||
Важливо пам’ятати, якщо використовувати `Query` всередині `Annotated`, не можна задавати параметр `default` у `Query`. |
|||
|
|||
Замість цього використовуйте значення за замовчуванням у самій функції. Інакше це буде нелогічно. |
|||
|
|||
Наприклад, цей варіант є некоректним: |
|||
|
|||
```Python |
|||
q: Annotated[str, Query(default="rick")] = "morty" |
|||
``` |
|||
|
|||
...тому, що не зрозуміло, яке значення має бути значенням за замовчуванням: `"rick"` чи `"morty"`. |
|||
|
|||
Коректні варіанти: |
|||
|
|||
```Python |
|||
q: Annotated[str, Query()] = "rick" |
|||
``` |
|||
|
|||
...або у старих кодових базах Ви знайдете: |
|||
|
|||
```Python |
|||
q: str = Query(default="rick") |
|||
``` |
|||
|
|||
### Переваги використання `Annotated` |
|||
|
|||
**Використання `Annotated` є рекомендованим** замість задання значення за замовчуванням у параметрах функції, оскільки воно **краще** з кількох причин. 🤓 |
|||
|
|||
Значення **за замовчуванням** параметра **функції** є його **фактичним значенням за замовчуванням**, що є більш інтуїтивним у Python загалом. 😌 |
|||
|
|||
Ви можете **викликати** ту саму функцію **в інших місцях** без FastAPI, і вона **працюватиме очікувано**. Якщо параметр є **обов’язковим** (без значення за замовчуванням), Ваш **редактор** повідомить про помилку, а **Python** також видасть помилку, якщо Ви виконаєте функцію без передавання цього параметра. |
|||
|
|||
Якщо Ви не використовуєте `Annotated`, а використовуєте **(старий) стиль значень за замовчуванням**, то при виклику цієї функції без FastAPI **в інших місцях**, потрібно **не забути** передати їй аргументи, інакше значення будуть відрізнятися від очікуваних (наприклад, Ви отримаєте `QueryInfo` або подібне замість `str`). Ваш редактор не повідомить про помилку, і Python також не видасть помилку при запуску функції, поки не виникне помилка під час виконання операцій усередині. |
|||
|
|||
Оскільки `Annotated` може містити кілька анотацій метаданих, Ви навіть можете використовувати ту саму функцію з іншими інструментами, такими як <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">Typer</a>. 🚀 |
|||
|
|||
## Додавання додаткових валідацій |
|||
|
|||
Ви також можете додати параметр `min_length`: |
|||
|
|||
{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *} |
|||
|
|||
## Додавання регулярних виразів |
|||
|
|||
Ви можете визначити <abbr title="Регулярний вираз (regex або regexp) — це послідовність символів, яка визначає шаблон для пошуку в рядках.">регулярний вираз</abbr> pattern, якому має відповідати параметр: |
|||
|
|||
{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} |
|||
|
|||
Цей конкретний шаблон регулярного виразу перевіряє, що отримане значення параметра: |
|||
|
|||
* `^`: починається з наступних символів, перед якими немає інших символів. |
|||
* `fixedquery`: точно відповідає значенню `fixedquery`. |
|||
* `$`: закінчується тут, після `fixedquery` немає жодних символів. |
|||
|
|||
Якщо Ви почуваєтеся розгублено щодо **"регулярних виразів"**, не хвилюйтеся. Вони є складною темою для багатьох людей. Ви все одно можете зробити багато речей без їх використання. |
|||
|
|||
Але тепер Ви знаєте, що коли вони знадобляться, їх можна застосовувати у **FastAPI**. |
|||
|
|||
### Pydantic v1 `regex` замість `pattern` |
|||
|
|||
До версії Pydantic 2 і FastAPI 0.100.0 параметр називався `regex` замість `pattern`, але тепер він застарів. |
|||
|
|||
Ви все ще можете зустріти код, який використовує його: |
|||
|
|||
//// 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 | Технічні деталі |
|||
|
|||
Наявність значення за замовчуванням будь-якого типу, включаючи `None`, робить параметр необов’язковим (not required). |
|||
|
|||
/// |
|||
|
|||
## Обов’язкові параметри |
|||
|
|||
Якщо нам не потрібно вказувати додаткові перевірки або метадані, ми можемо зробити параметр `q` обов’язковим, просто не оголошуючи значення за замовчуванням, наприклад: |
|||
|
|||
```Python |
|||
q: str |
|||
``` |
|||
|
|||
замість: |
|||
|
|||
```Python |
|||
q: str | None = None |
|||
``` |
|||
|
|||
Але тепер ми оголошуємо його з `Query`, наприклад: |
|||
|
|||
//// tab | Annotated |
|||
|
|||
```Python |
|||
q: Annotated[str | None, Query(min_length=3)] = None |
|||
``` |
|||
|
|||
//// |
|||
|
|||
Тому, якщо Вам потрібно зробити значення обов’язковим, використовуючи `Query`, просто не вказуйте значення за замовчуванням: |
|||
|
|||
{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} |
|||
|
|||
### Обов’язкове значення, яке може бути `None` |
|||
|
|||
Ви можете вказати, що параметр може приймати `None`, але при цьому залишається обов’язковим. Це змусить клієнтів надіслати значення, навіть якщо воно дорівнює `None`. |
|||
|
|||
Щоб зробити це, оголосіть, що `None` є допустимим типом, але не вказуйте значення за замовчуванням: |
|||
|
|||
{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *} |
|||
|
|||
## Список параметрів запиту / кілька значень |
|||
|
|||
Якщо Ви визначаєте параметр запиту за допомогою `Query`, Ви також можете дозволити отримання списку значень, тобто дозволити отримання кількох значень. |
|||
|
|||
Наприклад, щоб дозволити параметру запиту `q` з'являтися кілька разів в URL, можна написати: |
|||
|
|||
{* ../../docs_src/query_params_str_validations/tutorial011_an_py310.py hl[9] *} |
|||
|
|||
Тоді, у випадку запиту за URL: |
|||
|
|||
``` |
|||
http://localhost:8000/items/?q=foo&q=bar |
|||
``` |
|||
|
|||
Ви отримаєте кілька значень *query параметра* `q` (`foo` і `bar`) у вигляді списку `list` в Python у Вашій *функції обробки шляху*, у *параметрі функції* `q`. |
|||
|
|||
Отже, відповідь на цей URL буде: |
|||
|
|||
```JSON |
|||
{ |
|||
"q": [ |
|||
"foo", |
|||
"bar" |
|||
] |
|||
} |
|||
``` |
|||
|
|||
/// tip | Підказка |
|||
|
|||
Щоб оголосити параметр запиту з типом `list`, як у наведеному вище прикладі, потрібно явно використовувати `Query`, інакше він буде інтерпретований як тіло запиту. |
|||
|
|||
/// |
|||
|
|||
Інтерактивна API-документація оновиться відповідно, дозволяючи передавати кілька значень: |
|||
|
|||
<img src="/img/tutorial/query-params-str-validations/image02.png"> |
|||
|
|||
### Список параметрів запиту / кілька значень за замовчуванням |
|||
|
|||
Ви також можете визначити значення за замовчуванням для `list`, якщо жодне значення не було передане: |
|||
|
|||
{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} |
|||
|
|||
Якщо Ви перейдете за посиланням: |
|||
|
|||
``` |
|||
http://localhost:8000/items/ |
|||
``` |
|||
|
|||
то значення `q` за замовчуванням буде: `["foo", "bar"]`, і Ваша відповідь виглядатиме так: |
|||
|
|||
```JSON |
|||
{ |
|||
"q": [ |
|||
"foo", |
|||
"bar" |
|||
] |
|||
} |
|||
``` |
|||
|
|||
#### Використання тільки `list` |
|||
|
|||
Ви також можете використовувати `list` без уточнення типу, замість `list[str]`: |
|||
|
|||
{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} |
|||
|
|||
/// note | Технічні деталі |
|||
|
|||
Майте на увазі, що в цьому випадку FastAPI не перевірятиме вміст списку. |
|||
|
|||
Наприклад, `list[int]` перевірятиме (і документуватиме), що всі елементи списку є цілими числами. Але `list` без уточнення цього не робитиме. |
|||
|
|||
/// |
|||
|
|||
## Додавання додаткових метаданих |
|||
|
|||
Ви можете додати більше інформації про параметр. |
|||
|
|||
Ця інформація буде включена у згенерований OpenAPI та використана в інтерфейсах документації та зовнішніх інструментах. |
|||
|
|||
/// note | Технічні деталі |
|||
|
|||
Майте на увазі, що різні інструменти можуть мати різний рівень підтримки OpenAPI. |
|||
|
|||
Деякі з них можуть ще не відображати всю додаткову інформацію, хоча в більшості випадків ця функція вже запланована для розробки. |
|||
|
|||
/// |
|||
|
|||
Ви можете додати `title` : |
|||
|
|||
{* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *} |
|||
|
|||
А також `description`: |
|||
|
|||
{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *} |
|||
|
|||
## Аліаси параметрів |
|||
|
|||
Уявіть, що Ви хочете, щоб параметр називався `item-query`. |
|||
|
|||
Наприклад: |
|||
|
|||
``` |
|||
http://127.0.0.1:8000/items/?item-query=foobaritems |
|||
``` |
|||
|
|||
Але `item-query` — це некоректна назва змінної в Python. |
|||
|
|||
Найближчий допустимий варіант — `item_query`. |
|||
|
|||
Проте Вам потрібно, щоб параметр залишався саме `item-query`... |
|||
|
|||
У такому випадку можна оголосити `alias`, і саме він буде використовуватися для отримання значення параметра: |
|||
|
|||
{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *} |
|||
|
|||
## Виведення параметрів як застарілих |
|||
|
|||
Припустимо, що Ви більше не хочете використовувати цей параметр. |
|||
|
|||
Вам потрібно залишити його на деякий час, оскільки ним користуються клієнти, але Ви хочете, щоб документація чітко показувала, що він є <abbr title="застарілий, не рекомендується до використання">застарілим</abbr>. |
|||
|
|||
Тоді Ви можете передати параметр `deprecated=True` до `Query`: |
|||
|
|||
{* ../../docs_src/query_params_str_validations/tutorial010_an_py310.py hl[19] *} |
|||
|
|||
Документація буде показувати це таким чином: |
|||
|
|||
<img src="/img/tutorial/query-params-str-validations/image01.png"> |
|||
|
|||
## Виняток параметрів з OpenAPI |
|||
|
|||
Щоб виключити параметр запиту зі згенерованої схеми OpenAPI (і, таким чином, з автоматичних систем документації), встановіть параметр `include_in_schema` для `Query` в `False`: |
|||
|
|||
{* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *} |
|||
|
|||
## Кастомна валідація |
|||
|
|||
Можуть бути випадки, коли Вам потрібно провести **кастомну валідацію**, яку не можна реалізувати за допомогою параметрів, показаних вище. |
|||
|
|||
У таких випадках ви можете використати **кастомну функцію валідації**, яка буде застосована після звичайної валідації (наприклад, після перевірки, що значення є типом `str`). |
|||
|
|||
Це можна досягти за допомогою <a href="https://docs.pydantic.dev/latest/concepts/validators/#field-after-validator" class="external-link" target="_blank">Pydantic's `AfterValidator`</a> в середині `Annotated`. |
|||
|
|||
/// 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 означає Міжнародний стандартний номер книги">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] *} |
|||
|
|||
/// info | Інформація |
|||
|
|||
Це доступно з версії Pydantic 2 або вище. 😎 |
|||
|
|||
/// |
|||
|
|||
/// tip | Підказка |
|||
|
|||
Якщо Вам потрібно виконати будь-яку валідацію, яка вимагає взаємодії з будь-яким **зовнішнім компонентом**, таким як база даних чи інший API, ви повинні замість цього використовувати **FastAPI Dependencies**. Ви дізнаєтесь про них пізніше. |
|||
|
|||
Ці кастомні валідатори використовуються для речей, які можна перевірити лише з **тими даними**, що надані в запиті. |
|||
|
|||
/// |
|||
|
|||
### Зрозумійте цей код |
|||
|
|||
Головний момент – це використання **`AfterValidator` з функцією всередині `Annotated`**. Можете пропустити цю частину, якщо хочете. 🤸 |
|||
|
|||
--- |
|||
|
|||
Але якщо Вам цікаво розібратися в цьому конкретному прикладі коду і Вам ще не набридло, ось кілька додаткових деталей. |
|||
|
|||
#### Рядок із `value.startswith()` |
|||
|
|||
Звернули увагу? Рядок із `value.startswith()` може приймати кортеж, і тоді він перевірятиме кожне значення в кортежі: |
|||
|
|||
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[16:19] hl[17] *} |
|||
|
|||
#### Випадковий елемент |
|||
|
|||
За допомогою `data.items()` ми отримуємо <abbr title="Об'єкт, який можна перебирати в циклі, як-от список чи множину.">ітерабельний об'єкт</abbr> із кортежами, що містять ключ і значення для кожного елемента словника. |
|||
|
|||
Ми перетворюємо цей ітерабельний об'єкт у звичайний `list` за допомогою `list(data.items())`. |
|||
|
|||
Потім, використовуючи `random.choice()`, ми можемо отримати випадкове значення зі списку, тобто отримуємо кортеж із `(id, name)`. Це може бути щось на зразок `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`. |
|||
|
|||
Далі ми **присвоюємо ці два значення** кортежу змінним `id` і `name`. |
|||
|
|||
Тож, якщо користувач не вказав ID елемента, він все одно отримає випадкову рекомендацію. |
|||
|
|||
...і все це реалізовано в **одному рядку коду**. 🤯 Хіба не прекрасний Python? 🐍 |
|||
|
|||
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *} |
|||
|
|||
## Підсумок |
|||
|
|||
Ви можете оголошувати додаткові валідації та метаінформацію для своїх параметрів. |
|||
|
|||
Загальні валідації та метаінформація: |
|||
|
|||
* `alias` |
|||
* `title` |
|||
* `description` |
|||
* `deprecated` |
|||
|
|||
Валідації, специфічні для рядків: |
|||
|
|||
* `min_length` |
|||
* `max_length` |
|||
* `pattern` |
|||
|
|||
Кастомні валідації за допомогою `AfterValidator`. |
|||
|
|||
У цих прикладах Ви побачили, як оголошувати валідації для значень `str`. |
|||
|
|||
Дивіться наступні розділи, щоб дізнатися, як оголошувати валідації для інших типів, наприклад чисел. |
@ -0,0 +1,358 @@ |
|||
# Модель відповіді — Тип, що повертається |
|||
|
|||
Ви можете оголосити тип, який використовуватиметься у відповіді, за допомогою *анотації типу, що повертається* *функцією операцією шляху* (path operation) |
|||
|
|||
**Анотацію типу** можна вказати так само як і для вхідних **параметрів** функції: це можуть бути моделі Pydantic, списки (lists), словники (dictionaries), скалярні значення, як-от цілі числа (integers), булеві значення (booleans) тощо. |
|||
|
|||
{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *} |
|||
|
|||
FastAPI використовуватиме цей тип, щоб: |
|||
|
|||
* **Перевірити правильність** повернених даних. |
|||
* Якщо дані не валідні (наприклад, відсутнє поле), це означає, що Ваш код додатку працює некоректно і не повертає те, що повинен. У такому випадку FastAPI поверне помилку сервера, замість того щоб віддати недопустимі дані. Так Ви та Ваші клієнти будете впевнені, що отримуєте очікувані дані у правильному форматі. |
|||
|
|||
* Додати **JSON Schema** відповіді до специфікації OpenAPI в *операціях шляху*. |
|||
* Це буде використано в **автоматичній документації**. |
|||
* А також інструментами, які автоматично генерують клієнтський код. |
|||
|
|||
Але найголовніше: |
|||
|
|||
* FastAPI **обмежить та відфільтрує** вихідні дані відповідно до типу, вказаного у відповіді. |
|||
* Це особливо важливо для **безпеки**. Деталі нижче. |
|||
|
|||
## Параметр `response_model` |
|||
|
|||
Іноді Вам потрібно або зручно повертати інші типи даних, ніж ті, що зазначені як тип відповіді. |
|||
|
|||
Наприклад, Ви можете **повертати словник** або об’єкт бази даних, але **оголосити модель Pydantic** як модель відповіді. Тоді модель Pydantic автоматично оброблятиме валідацію, документацію тощо. |
|||
|
|||
Якщо Ви додасте анотацію типу для повернення, редактор коду або mypy можуть поскаржитися, що функція повертає інший тип (наприклад, dict замість Item). |
|||
|
|||
У таких випадках можна скористатися параметром `response_model` в декораторі маршруту (наприклад, @app.get()). |
|||
|
|||
Параметр `response_model` працює з будь-яким *оператором шляху*: |
|||
|
|||
* `@app.get()` |
|||
* `@app.post()` |
|||
* `@app.put()` |
|||
* `@app.delete()` |
|||
* тощо. |
|||
|
|||
{* ../../docs_src/response_model/tutorial001_py310.py hl[17,22,24:27] *} |
|||
|
|||
/// note | Примітка |
|||
|
|||
Зверніть увагу, що `response_model` є параметром методу-декоратора (`get`, `post`, тощо), а не *функцією операцією шляху* (path operation function), як це робиться з параметрами або тілом запиту. |
|||
|
|||
/// |
|||
|
|||
`response_model` приймає такий самий тип, який Ви б вказали для поля моделі Pydantic. Тобто це може бути як Pydantic-модель, так і, наприклад, `list` із моделей Pydantic — `List[Item]`. |
|||
|
|||
FastAPI використовуватиме `response_model` для створення документації, валідації даних та — найважливіше — **перетворення та фільтрації вихідних даних** згідно з оголошеним типом. |
|||
|
|||
/// tip | Порада |
|||
|
|||
Якщо у Вас увімкнено сувору перевірку типів у редакторі, mypy тощо, Ви можете оголосити тип повернення функції як `Any`. |
|||
|
|||
Таким чином, Ви повідомляєте редактору, що свідомо повертаєте будь-що. Але FastAPI усе одно виконуватиме створення документації, валідацію, фільтрацію тощо за допомогою параметра `response_model`. |
|||
|
|||
/// |
|||
|
|||
### Пріоритет `response_model` |
|||
|
|||
Якщо Ви вказуєте і тип повернення, і `response_model`, то FastAPI використовуватиме `response_model` з пріоритетом. |
|||
|
|||
Таким чином, Ви можете додати правильні анотації типів до ваших функцій, навіть якщо вони повертають тип, відмінний від `response_model`. Це буде корисно для редакторів коду та інструментів, таких як mypy. І при цьому FastAPI продовжить виконувати валідацію даних, генерувати документацію тощо на основі `response_model`. |
|||
|
|||
Ви також можете використати `response_model=None`, щоб вимкнути створення моделі відповіді для цієї *операції шляху*. Це може знадобитися, якщо Ви додаєте анотації типів до об'єктів, які не є допустимими полями Pydantic — приклад цього Ви побачите в одному з наступних розділів. |
|||
|
|||
## Повернути ті самі вхідні дані |
|||
|
|||
Тут ми оголошуємо модель `UserIn`, яка містить звичайний текстовий пароль: |
|||
|
|||
{* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *} |
|||
|
|||
/// info | Інформація |
|||
|
|||
Щоб використовувати `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}, активували його, а потім встановили пакет, наприклад: |
|||
|
|||
```console |
|||
$ pip install email-validator |
|||
``` |
|||
|
|||
or with: |
|||
|
|||
```console |
|||
$ pip install "pydantic[email]" |
|||
``` |
|||
|
|||
/// |
|||
|
|||
І ми використовуємо цю модель, щоб оголосити і вхідні, і вихідні дані: |
|||
|
|||
{* ../../docs_src/response_model/tutorial002_py310.py hl[16] *} |
|||
|
|||
Тепер, коли браузер створює користувача з паролем, API поверне той самий пароль у відповіді. |
|||
|
|||
У цьому випадку це може не бути проблемою, адже саме користувач надіслав пароль. |
|||
|
|||
Але якщо ми використаємо цю ж модель для іншої операції шляху, ми можемо випадково надіслати паролі наших користувачів кожному клієнту. |
|||
|
|||
/// danger | Обережно |
|||
|
|||
Ніколи не зберігайте пароль користувача у відкритому вигляді та не надсилайте його у відповіді, якщо тільки Ви не знаєте всі ризики і точно розумієте, що робите. |
|||
|
|||
/// |
|||
|
|||
## Додайте окрему вихідну модель |
|||
|
|||
Замість цього ми можемо створити вхідну модель з відкритим паролем і вихідну модель без нього: |
|||
|
|||
{* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *} |
|||
|
|||
Тут, навіть якщо *функція операції шляху* повертає об'єкт користувача, який містить пароль: |
|||
|
|||
{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *} |
|||
|
|||
...ми оголосили `response_model` як нашу модель `UserOut`, яка не містить пароля: |
|||
|
|||
{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *} |
|||
|
|||
Таким чином, **FastAPI** автоматично відфільтрує всі дані, які не вказані у вихідній моделі (за допомогою Pydantic). |
|||
|
|||
### `response_model` або тип повернення |
|||
|
|||
У цьому випадку, оскільки дві моделі різні, якщо ми анотуємо тип повернення функції як `UserOut`, редактор і такі інструменти, як mypy, видадуть помилку, бо фактично ми повертаємо інший тип. |
|||
|
|||
Тому в цьому прикладі ми використовуємо параметр `response_model`, а не анотацію типу повернення. |
|||
|
|||
...але читайте далі, щоб дізнатися, як обійти це обмеження. |
|||
|
|||
## Тип повернення і фільтрація даних |
|||
|
|||
Продовжимо з попереднього прикладу. Ми хотіли **анотувати функцію одним типом**, але при цьому повертати з неї більше даних. |
|||
|
|||
Ми хочемо, щоб FastAPI продовжував **фільтрувати** ці дані за допомогою response_model. Тобто навіть якщо функція повертає більше інформації, у відповіді будуть лише ті поля, які вказані у response_model. |
|||
|
|||
У попередньому прикладі, оскільки класи були різні, нам довелося використовувати параметр `response_model`. Але це означає, що ми не отримуємо підтримки з боку редактора коду та інструментів перевірки типів щодо типу, який повертає функція. |
|||
|
|||
Проте в більшості випадків, коли нам потрібно зробити щось подібне, ми просто хочемо, щоб модель **відфільтрувала або прибрала** частину даних, як у цьому прикладі. |
|||
|
|||
У таких випадках ми можемо використати класи та спадкування, щоб скористатися **анотаціями типів** функцій — це дає кращу підтримку з боку редактора та інструментів типу mypy, і при цьому FastAPI продовжує виконувати **фільтрацію даних** у відповіді. |
|||
|
|||
{* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *} |
|||
|
|||
Завдяки цьому ми отримуємо підтримку інструментів — від редакторів і mypy, оскільки цей код є коректним з точки зору типів, — але ми також отримуємо фільтрацію даних від FastAPI. |
|||
|
|||
Як це працює? Давайте розберемося. 🤓 |
|||
|
|||
### Типи та підтримка інструментів |
|||
|
|||
Спершу подивимось, як це бачать редактори, mypy та інші інструменти. |
|||
|
|||
`BaseUser` має базові поля. Потім `UserIn` успадковує `BaseUser` і додає поле `password`, отже, він матиме всі поля з обох моделей. |
|||
|
|||
Ми зазначаємо тип повернення функції як `BaseUser`, але фактично повертаємо екземпляр `UserIn`. |
|||
|
|||
Редактор, mypy та інші інструменти не скаржитимуться на це, тому що з точки зору типізації `UserIn` є підкласом `BaseUser`, а це означає, що він є `валідним` типом, коли очікується будь-що, що є `BaseUser`. |
|||
|
|||
### Фільтрація даних у FastAPI |
|||
|
|||
Тепер для FastAPI він бачить тип повернення і переконується, що те, що Ви повертаєте, містить **тільки** поля, які оголошені у цьому типі. |
|||
|
|||
FastAPI виконує кілька внутрішніх операцій з Pydantic, щоб гарантувати, що правила наслідування класів не застосовуються для фільтрації повернених даних, інакше Ви могли б повернути значно більше даних, ніж очікували. |
|||
|
|||
Таким чином, Ви отримуєте найкраще з двох світів: анотації типів **з підтримкою інструментів** і **фільтрацію даних**. |
|||
|
|||
## Подивитись у документації |
|||
|
|||
Коли Ви дивитесь автоматичну документацію, Ви можете побачити, що вхідна модель і вихідна модель мають власну JSON-схему: |
|||
|
|||
<img src="/img/tutorial/response-model/image01.png"> |
|||
|
|||
І обидві моделі використовуються для інтерактивної API-документації: |
|||
|
|||
<img src="/img/tutorial/response-model/image02.png"> |
|||
|
|||
## Інші анотації типів повернення |
|||
|
|||
Існують випадки, коли Ви повертаєте щось, що не є допустимим полем Pydantic, але анотуєте це у функції лише для того, щоб отримати підтримку від інструментів (редактора, mypy тощо). |
|||
|
|||
### Повернення Response напряму |
|||
|
|||
Найпоширенішим випадком буде [повернення Response напряму, як пояснюється пізніше у розширеній документації](../advanced/response-directly.md){.internal-link target=_blank}. |
|||
|
|||
{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *} |
|||
|
|||
Цей простий випадок автоматично обробляється FastAPI, тому що анотація типу повернення — це клас (або підклас) `Response`. |
|||
|
|||
І інструменти також будуть задоволені, бо і `RedirectResponse`, і `JSONResponse` є підкласами `Response`, отже анотація типу коректна. |
|||
|
|||
### Анотація підкласу Response |
|||
|
|||
Також можна використовувати підклас `Response` у анотації типу: |
|||
|
|||
{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *} |
|||
|
|||
Це теж працюватиме, бо `RedirectResponse` — підклас `Response`, і FastAPI автоматично обробить цей простий випадок. |
|||
|
|||
### Некоректні анотації типу повернення |
|||
|
|||
Але коли Ви повертаєте якийсь інший довільний об’єкт, що не є валідним типом Pydantic (наприклад, об’єкт бази даних), і анотуєте його так у функції, FastAPI спробує створити Pydantic модель відповіді на основі цієї анотації типу, і це завершиться помилкою. |
|||
|
|||
Те саме станеться, якщо Ви використовуєте <abbr title="Об'єднання (union) кількох типів означає: «будь-який з цих типів».">union</abbr> між різними типами, де один або більше не є валідними типами Pydantic, наприклад, це спричинить помилку 💥: |
|||
|
|||
{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} |
|||
|
|||
...це не працює, тому що тип анотації не є типом Pydantic і не є просто класом `Response` або його підкласом, а є об’єднанням (union) — або `Response`, або `dict`. |
|||
|
|||
### Відключення Моделі Відповіді |
|||
|
|||
Продовжуючи приклад вище, можливо, Ви не хочете використовувати стандартну валідацію даних, автоматичну документацію, фільтрацію тощо, які FastAPI виконує за замовчуванням. |
|||
|
|||
Але ви все одно можете залишити анотацію типу у функції, щоб зберегти підтримку з боку інструментів, таких як редактори коду або статичні перевірки типів (наприклад, mypy). |
|||
|
|||
У такому випадку ви можете вимкнути генерацію моделі відповіді, встановивши `response_model=None`: |
|||
|
|||
{* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *} |
|||
|
|||
Це змусить FastAPI пропустити генерацію моделі відповіді, і таким чином Ви зможете використовувати будь-які анотації типів повернення без впливу на вашу FastAPI аплікацію. 🤓 |
|||
|
|||
## Параметри кодування моделі відповіді |
|||
|
|||
Ваша модель відповіді може мати значення за замовчуванням, наприклад: |
|||
|
|||
{* ../../docs_src/response_model/tutorial004_py310.py hl[9,11:12] *} |
|||
|
|||
* `description: Union[str, None] = None` (або `str | None = None` у Python 3.10) має значення за замовчуванням `None`. |
|||
* `tax: float = 10.5` має значення за замовчуванням `10.5`. |
|||
* `tags: List[str] = []` має значення за замовчуванням порожній список: `[]`. |
|||
|
|||
Але Ви можете захотіти не включати їх у результат, якщо вони фактично не були збережені. |
|||
|
|||
Наприклад, якщо у Вас є моделі з багатьма необов’язковими атрибутами у NoSQL базі даних, але Ви не хочете відправляти дуже довгі JSON-відповіді, повні значень за замовчуванням. |
|||
|
|||
### Використовуйте параметр `response_model_exclude_unset` |
|||
|
|||
Ви можете встановити параметр декоратора шляху `response_model_exclude_unset=True`: |
|||
|
|||
{* ../../docs_src/response_model/tutorial004_py310.py hl[22] *} |
|||
|
|||
і ці значення за замовчуванням не будуть включені у відповідь, тільки фактично встановлені значення. |
|||
|
|||
Отже, якщо Ви надішлете запит до цього оператора шляху для елемента з item_id `foo`, відповідь (без включення значень за замовчуванням) буде: |
|||
|
|||
```JSON |
|||
{ |
|||
"name": "Foo", |
|||
"price": 50.2 |
|||
} |
|||
``` |
|||
|
|||
/// 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> for `exclude_defaults` та `exclude_none`. |
|||
|
|||
/// |
|||
|
|||
#### Дані зі значеннями для полів із типовими значеннями |
|||
|
|||
Але якщо Ваші дані мають значення для полів моделі з типовими значеннями, як у елемента з item_id `bar`: |
|||
|
|||
```Python hl_lines="3 5" |
|||
{ |
|||
"name": "Bar", |
|||
"description": "The bartenders", |
|||
"price": 62, |
|||
"tax": 20.2 |
|||
} |
|||
``` |
|||
вони будуть включені у відповідь. |
|||
|
|||
#### Дані з тими самими значеннями, що й типові |
|||
|
|||
Якщо дані мають ті самі значення, що й типові, як у елемента з item_id `baz`: |
|||
|
|||
```Python hl_lines="3 5-6" |
|||
{ |
|||
"name": "Baz", |
|||
"description": None, |
|||
"price": 50.2, |
|||
"tax": 10.5, |
|||
"tags": [] |
|||
} |
|||
``` |
|||
|
|||
FastAPI достатньо розумний (насправді, Pydantic достатньо розумний), щоб зрозуміти, що, хоча `description`, `tax` і `tags` мають ті самі значення, що й типові, вони були встановлені явно (а не взяті як значення за замовчуванням). |
|||
|
|||
Отже, вони будуть включені у JSON-відповідь. |
|||
|
|||
/// tip | Порада |
|||
|
|||
Зверніть увагу, що типові значення можуть бути будь-якими, не лише `None`. |
|||
|
|||
Це може бути list (`[]`), `float` 10.5 тощо. |
|||
|
|||
/// |
|||
|
|||
### `response_model_include` та `response_model_exclude` |
|||
|
|||
Ви також можете використовувати параметри *декоратора операції шляху* `response_model_include` та `response_model_exclude`. |
|||
|
|||
Вони приймають `set` (множину) рядків (`str`) з іменами атрибутів, які потрібно включити (пропускаючи інші) або виключити (включаючи інші). |
|||
|
|||
Це можна використовувати як швидкий спосіб, якщо у Вас є лише одна модель Pydantic і Ви хочете видалити деякі дані з виводу. |
|||
|
|||
/// tip | Порада |
|||
|
|||
Але все ж рекомендується використовувати описані вище підходи, із застосуванням кількох класів, замість цих параметрів. |
|||
|
|||
|
|||
Це тому, що JSON Schema, який генерується у вашому OpenAPI додатку (і в документації), все одно буде відповідати повній моделі, навіть якщо Ви використовуєте `response_model_include` або `response_model_exclude` для виключення деяких атрибутів. |
|||
|
|||
Це також стосується `response_model_by_alias`, який працює подібним чином. |
|||
|
|||
/// |
|||
|
|||
{* ../../docs_src/response_model/tutorial005_py310.py hl[29,35] *} |
|||
|
|||
/// tip | Порада |
|||
|
|||
Синтаксис `{"name", "description"}` створює `set` з цими двома значеннями. |
|||
|
|||
Він еквівалентний `set(["name", "description"])`. |
|||
|
|||
/// |
|||
|
|||
#### Використання `list` замість `set` |
|||
|
|||
Якщо Ви забудете використати `set` і натомість застосуєте `list` або `tuple`, FastAPI все одно перетворить це на `set`, і все працюватиме правильно: |
|||
|
|||
{* ../../docs_src/response_model/tutorial006_py310.py hl[29,35] *} |
|||
|
|||
## Підсумок |
|||
|
|||
Використовуйте параметр `response_model` *декоратора операції шляху*, щоб визначати моделі відповіді, особливо щоб гарантувати фільтрацію приватних даних. |
|||
|
|||
Використовуйте `response_model_exclude_unset`, щоб повертати лише явно встановлені значення. |
@ -0,0 +1,222 @@ |
|||
# Декларування прикладів вхідних даних |
|||
|
|||
Ви можете задати приклади даних, які Ваш застосунок може отримувати. |
|||
|
|||
Ось кілька способів, як це зробити. |
|||
|
|||
## Додаткові дані JSON-схеми в моделях Pydantic |
|||
|
|||
Ви можете задати `examples` для моделі Pydantic, які буде додано до згенерованої JSON-схеми. |
|||
|
|||
//// tab | Pydantic v2 |
|||
|
|||
{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *} |
|||
|
|||
//// |
|||
|
|||
//// 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`. |
|||
|
|||
//// |
|||
|
|||
//// 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-схему і додати власну додаткову інформацію. |
|||
|
|||
Наприклад, Ви можете використати її для додавання метаданих для інтерфейсу користувача на фронтенді тощо. |
|||
|
|||
/// |
|||
|
|||
/// info | Інформація |
|||
|
|||
OpenAPI 3.1.0 (який використовується починаючи з FastAPI 0.99.0) додав підтримку `examples`, що є частиною стандарту **JSON-схеми**. |
|||
|
|||
До цього підтримувався лише ключ `example` з одним прикладом. Він все ще підтримується в OpenAPI 3.1.0, але є застарілим і не входить до стандарту JSON Schema. Тому рекомендується перейти з `example` на `examples`. 🤓 |
|||
|
|||
Більше про це можна прочитати в кінці цієї сторінки. |
|||
|
|||
/// |
|||
|
|||
## Додаткові аргументи `Field` |
|||
|
|||
Коли ви використовуєте `Field()` у моделях Pydantic, Ви також можете вказати додаткові `examples`: |
|||
|
|||
{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *} |
|||
|
|||
## `examples` у JSON-схемі — OpenAPI |
|||
|
|||
При використанні будь-кого з наступного: |
|||
|
|||
* `Path()` |
|||
* `Query()` |
|||
* `Header()` |
|||
* `Cookie()` |
|||
* `Body()` |
|||
* `Form()` |
|||
* `File()` |
|||
|
|||
Ви також можете задати набір `examples` з додатковою інформацією, яка буде додана до їхніх **JSON-схем** у **OpenAPI**. |
|||
|
|||
### `Body` з `examples` |
|||
|
|||
Тут ми передаємо `examples`, які містять один приклад очікуваних даних у `Body()`: |
|||
|
|||
{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *} |
|||
|
|||
### Приклад у UI документації |
|||
|
|||
За допомогою будь-якого з наведених вище методів це виглядатиме так у документації за `/docs`: |
|||
|
|||
<img src="/img/tutorial/body-fields/image01.png"> |
|||
|
|||
### `Body` з кількома `examples` |
|||
|
|||
Звичайно, Ви також можете передати кілька `examples`: |
|||
|
|||
{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *} |
|||
|
|||
Коли Ви це робите, приклади будуть частиною внутрішньої **JSON-схеми** для цих даних. |
|||
|
|||
Втім, на момент написання цього (<abbr title="2023-08-26">26 серпня 2023</abbr>), Swagger UI — інструмент, який відповідає за відображення UI документації — не підтримує показ кількох прикладів у **JSON-схеми**. Але нижче можна прочитати про обхідний шлях. |
|||
|
|||
### Специфічні для OpenAPI `examples` |
|||
|
|||
Ще до того, як **JSON-схема** почала підтримувати `examples`, OpenAPI вже мала підтримку поля з такою ж назвою — `examples`. |
|||
|
|||
Це **специфічне для OpenAPI** поле `examples` розміщується в іншій частині специфікації OpenAPI — у **деталях кожної *операції шляху***, а не всередині самої JSON-схеми. |
|||
|
|||
Swagger UI вже давно підтримує це поле `examples`. Тому Ви можете використовувати його, щоб **відображати** кілька **прикладів у документації**. |
|||
|
|||
Це поле `examples` у специфікації OpenAPI — це `dict` (словник) з **кількома прикладами** (а не список `list`), кожен із яких може містити додаткову інформацію, що буде додана до **OpenAPI**. |
|||
|
|||
Воно не включається до JSON Schema кожного параметра, а розміщується зовні, безпосередньо в *операції шляху*. |
|||
|
|||
### Використання параметра `openapi_examples` |
|||
|
|||
Ви можете оголосити специфічні для OpenAPI `examples` у FastAPI за допомогою параметра `openapi_examples` для: |
|||
|
|||
* `Path()` |
|||
* `Query()` |
|||
* `Header()` |
|||
* `Cookie()` |
|||
* `Body()` |
|||
* `Form()` |
|||
* `File()` |
|||
|
|||
Ключі словника (`dict`) ідентифікують кожен приклад, а кожне значення `dict` — кожен специфічний словник `dict` в `examples` може містити: |
|||
|
|||
* `summary`: короткий опис прикладу. |
|||
* `description`: розгорнутий опис (може містити Markdown). |
|||
* `value`: сам приклад, наприклад, словник (`dict`). |
|||
* `externalValue`: альтернатива `value`, URL-адреса, що вказує на приклад. Проте ця опція може не підтримуватися більшістю інструментів, на відміну від `value`. |
|||
|
|||
Використання виглядає так: |
|||
|
|||
{* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *} |
|||
|
|||
### Приклади OpenAPI у UI документації |
|||
|
|||
З параметром `openapi_examples`, доданим до `Body()`, документація `/docs` виглядатиме так: |
|||
|
|||
<img src="/img/tutorial/body-fields/image02.png"> |
|||
|
|||
## Технічні деталі |
|||
|
|||
/// tip | Підказка |
|||
|
|||
Якщо Ви вже використовуєте **FastAPI** версії **0.99.0 або вище**, Ви можете **пропустити** цей розділ. |
|||
|
|||
Він більш актуальний для старих версій, до появи OpenAPI 3.1.0. |
|||
|
|||
Можна вважати це коротким **історичним екскурсом** у OpenAPI та JSON Schema. 🤓 |
|||
|
|||
/// |
|||
|
|||
/// warning | Попередження |
|||
|
|||
Це дуже технічна інформація про стандарти **JSON Schema** і **OpenAPI**. |
|||
|
|||
Якщо вищезгадані ідеї вже працюють у Вас — можете не заглиблюватися в ці деталі. |
|||
|
|||
/// |
|||
|
|||
До OpenAPI 3.1.0 специфікація використовувала стару та модифіковану версію **JSON Schema**. |
|||
|
|||
Оскільки JSON Schema раніше не підтримувала `examples`, OpenAPI додала власне поле `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 для: |
|||
* `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 для: |
|||
* `Body()` |
|||
* `File()` |
|||
* `Form()` |
|||
|
|||
/// info | Інформація |
|||
|
|||
Цей старий параметр `examples`, специфічний для OpenAPI, тепер називається `openapi_examples`, починаючи з FastAPI версії `0.103.0`. |
|||
|
|||
/// |
|||
|
|||
### Поле `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`. |
|||
|
|||
Тепер це поле `examples` є пріоритетним і замінює старе (і кастомне) поле `example`, яке стало застарілим. |
|||
|
|||
Нове поле `examples` у JSON Schema — це **просто список (`list`)** прикладів, без додаткових метаданих (на відміну від OpenAPI). |
|||
|
|||
/// info | Інформація |
|||
|
|||
Навіть після того, як з'явився OpenAPI 3.1.0, який підтримував examples у JSON Schema, інструмент Swagger UI ще деякий час не підтримував цю версію (підтримка з’явилась з версії 5.0.0 🎉). |
|||
|
|||
Через це версії FastAPI до 0.99.0 все ще використовували версії OpenAPI нижчі за 3.1.0. |
|||
|
|||
/// |
|||
|
|||
### `Examples` в Pydantic і FastAPI |
|||
|
|||
Коли Ви додаєте `examples` у модель Pydantic через `schema_extra` або `Field(examples=["something"])`, ці приклади додаються до **JSON Schema** цієї моделі. |
|||
|
|||
І ця **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). Натомість вони додавалися безпосередньо до опису *обробника шляху* *(path operation)* в OpenAPI (тобто поза межами частин, які використовують 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` |
|||
|
|||
Раніше (станом на 26 серпня 2023 року) Swagger UI не підтримував кілька прикладів у JSON Schema, тому користувачі не мали можливості показати декілька прикладів у документації. |
|||
|
|||
Щоб вирішити це, FastAPI починаючи з версії 0.103.0 **додав підтримку** старого **OpenAPI-специфічного** поля `examples` через новий параметр `openapi_examples`. 🤓 |
|||
|
|||
### Підсумок |
|||
|
|||
Раніше я казав, що не люблю історію... а тепер ось я — розповідаю "технічні історичні" лекції. 😅 |
|||
|
|||
Коротко: **оновіться до FastAPI 0.99.0 або вище** — і все стане значно **простішим, узгодженим та інтуїтивно зрозумілим**, і Вам не доведеться знати всі ці історичні деталі. 😎 |
@ -0,0 +1,104 @@ |
|||
# Безпека |
|||
|
|||
Існує багато способів реалізувати безпеку, автентифікацію та авторизацію. |
|||
|
|||
Це зазвичай складна і "непроста" тема. |
|||
|
|||
У багатьох фреймворках і системах забезпечення безпеки та автентифікації займає величезну частину зусиль і коду (іноді — понад 50% всього написаного коду). |
|||
|
|||
**FastAPI** надає кілька інструментів, які допоможуть Вам впоратися з **безпекою** легко, швидко, стандартним способом, без необхідності вивчати всі специфікації безпеки. |
|||
|
|||
Але спочатку — кілька коротких понять. |
|||
|
|||
## Поспішаєте? |
|||
|
|||
Якщо Вам не цікаві всі ці терміни й просто потрібно *швидко* додати автентифікацію за логіном і паролем — переходьте до наступних розділів. |
|||
|
|||
## OAuth2 |
|||
|
|||
OAuth2 — це специфікація, що описує кілька способів обробки автентифікації та авторизації. |
|||
|
|||
Це досить об'ємна специфікація, яка охоплює складні випадки використання. |
|||
|
|||
Вона включає способи автентифікації через "третю сторону". |
|||
|
|||
Саме це лежить в основі "входу через Google, Facebook, X (Twitter), GitHub" тощо. |
|||
|
|||
### OAuth 1 |
|||
|
|||
Раніше існував OAuth 1, який значно відрізняється від OAuth2 і є складнішим, оскільки містив специфікації для шифрування комунікацій. |
|||
|
|||
Зараз майже не використовується. |
|||
|
|||
OAuth2 не вказує, як саме шифрувати з'єднання — воно очікує, що ваш застосунок працює через HTTPS. |
|||
|
|||
/// tip | Порада |
|||
|
|||
У розділі про **деплой** Ви побачите, як налаштувати HTTPS безкоштовно з Traefik та Let's Encrypt. |
|||
|
|||
/// |
|||
|
|||
## OpenID Connect |
|||
|
|||
OpenID Connect — ще одна специфікація, побудована на основі **OAuth2**. |
|||
|
|||
Вона розширює OAuth2, уточнюючи деякі неоднозначності для досягнення кращої сумісності. |
|||
|
|||
Наприклад, вхід через Google використовує OpenID Connect (який базується на OAuth2). |
|||
|
|||
Але вхід через Facebook — ні. Він має власну реалізацію на базі OAuth2. |
|||
|
|||
### OpenID (не "OpenID Connect") |
|||
|
|||
Існувала також специфікація "OpenID", яка намагалася розвʼязати ті самі задачі, що й **OpenID Connect**, але не базувалась на OAuth2. |
|||
|
|||
Це була зовсім інша система, і сьогодні вона майже не використовується. |
|||
|
|||
## OpenAPI |
|||
|
|||
OpenAPI (раніше Swagger) — це специфікація для побудови API (тепер під егідою Linux Foundation). |
|||
|
|||
**FastAPI** базується на **OpenAPI**. |
|||
|
|||
Завдяки цьому Ви отримуєте автоматичну інтерактивну документацію, генерацію коду та багато іншого. |
|||
|
|||
OpenAPI дозволяє описувати різні "схеми" безпеки. |
|||
|
|||
Використовуючи їх, Ви можете скористатися всіма цими інструментами, що базуються на стандартах, зокрема інтерактивними системами документації. |
|||
|
|||
OpenAPI визначає такі схеми безпеки: |
|||
|
|||
* `apiKey`: специфічний для застосунку ключ, який може передаватися через: |
|||
* Параметр запиту. |
|||
* Заголовок. |
|||
* Cookie. |
|||
* `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. |
|||
|
|||
|
|||
/// tip | Порада |
|||
|
|||
Інтеграція інших провайдерів автентифікації/авторизації, таких як Google, Facebook, X (Twitter), GitHub тощо — також можлива і відносно проста. |
|||
|
|||
Найскладніше — це створити власного провайдера автентифікації/авторизації, як Google чи Facebook. Але **FastAPI** надає Вам інструменти, щоб зробити це легко, беручи на себе важку частину роботи. |
|||
|
|||
/// |
|||
|
|||
## Інструменти **FastAPI** |
|||
|
|||
FastAPI надає кілька інструментів для кожної з описаних схем безпеки в модулі `fastapi.security`, які спрощують використання цих механізмів захисту. |
|||
|
|||
У наступних розділах Ви побачите, як додати безпеку до свого API за допомогою цих інструментів **FastAPI**. |
|||
|
|||
А також побачите, як вона автоматично інтегрується в інтерактивну документацію вашого API. |
@ -1,4 +1,4 @@ |
|||
# For mkdocstrings and tests |
|||
httpx >=0.23.0,<0.28.0 |
|||
httpx >=0.23.0,<0.29.0 |
|||
# For linting and generating docs versions |
|||
ruff ==0.11.2 |
|||
|
@ -1,6 +1,6 @@ |
|||
PyGithub>=2.3.0,<3.0.0 |
|||
pydantic>=2.5.3,<3.0.0 |
|||
pydantic-settings>=2.1.0,<3.0.0 |
|||
httpx>=0.27.0,<0.28.0 |
|||
httpx>=0.27.0,<0.29.0 |
|||
pyyaml >=5.3.1,<7.0.0 |
|||
smokeshow |
|||
|
@ -1 +1,2 @@ |
|||
pydantic-ai==0.0.30 |
|||
GitPython==3.1.45 |
|||
|
@ -0,0 +1,31 @@ |
|||
from fastapi import FastAPI |
|||
from fastapi.testclient import TestClient |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class MyModel(BaseModel): |
|||
""" |
|||
A model with a form feed character in the title. |
|||
\f |
|||
Text after form feed character. |
|||
""" |
|||
|
|||
|
|||
@app.get("/foo") |
|||
def foo(v: MyModel): # pragma: no cover |
|||
pass |
|||
|
|||
|
|||
client = TestClient(app) |
|||
|
|||
|
|||
def test_openapi(): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
openapi_schema = response.json() |
|||
|
|||
assert openapi_schema["components"]["schemas"]["MyModel"]["description"] == ( |
|||
"A model with a form feed character in the title.\n" |
|||
) |
@ -1,19 +0,0 @@ |
|||
from fastapi.testclient import TestClient |
|||
from pytest import MonkeyPatch |
|||
|
|||
from ...utils import needs_pydanticv1 |
|||
|
|||
|
|||
@needs_pydanticv1 |
|||
def test_settings(monkeypatch: MonkeyPatch): |
|||
monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com") |
|||
from docs_src.settings.tutorial001_pv1 import app |
|||
|
|||
client = TestClient(app) |
|||
response = client.get("/info") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"app_name": "Awesome API", |
|||
"admin_email": "admin@example.com", |
|||
"items_per_user": 50, |
|||
} |
@ -0,0 +1,156 @@ |
|||
from typing import Union |
|||
|
|||
from fastapi import FastAPI, Form |
|||
from fastapi.testclient import TestClient |
|||
from pydantic import BaseModel |
|||
from typing_extensions import Annotated |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class UserForm(BaseModel): |
|||
name: str |
|||
email: str |
|||
|
|||
|
|||
class CompanyForm(BaseModel): |
|||
company_name: str |
|||
industry: str |
|||
|
|||
|
|||
@app.post("/form-union/") |
|||
def post_union_form(data: Annotated[Union[UserForm, CompanyForm], Form()]): |
|||
return {"received": data} |
|||
|
|||
|
|||
client = TestClient(app) |
|||
|
|||
|
|||
def test_post_user_form(): |
|||
response = client.post( |
|||
"/form-union/", data={"name": "John Doe", "email": "john@example.com"} |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"received": {"name": "John Doe", "email": "john@example.com"} |
|||
} |
|||
|
|||
|
|||
def test_post_company_form(): |
|||
response = client.post( |
|||
"/form-union/", data={"company_name": "Tech Corp", "industry": "Technology"} |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"received": {"company_name": "Tech Corp", "industry": "Technology"} |
|||
} |
|||
|
|||
|
|||
def test_invalid_form_data(): |
|||
response = client.post( |
|||
"/form-union/", |
|||
data={"name": "John", "company_name": "Tech Corp"}, |
|||
) |
|||
assert response.status_code == 422, response.text |
|||
|
|||
|
|||
def test_empty_form(): |
|||
response = client.post("/form-union/") |
|||
assert response.status_code == 422, response.text |
|||
|
|||
|
|||
def test_openapi_schema(): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
|
|||
assert response.json() == { |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/form-union/": { |
|||
"post": { |
|||
"summary": "Post Union Form", |
|||
"operationId": "post_union_form_form_union__post", |
|||
"requestBody": { |
|||
"content": { |
|||
"application/x-www-form-urlencoded": { |
|||
"schema": { |
|||
"anyOf": [ |
|||
{"$ref": "#/components/schemas/UserForm"}, |
|||
{"$ref": "#/components/schemas/CompanyForm"}, |
|||
], |
|||
"title": "Data", |
|||
} |
|||
} |
|||
}, |
|||
"required": True, |
|||
}, |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": {"application/json": {"schema": {}}}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
}, |
|||
"components": { |
|||
"schemas": { |
|||
"CompanyForm": { |
|||
"properties": { |
|||
"company_name": {"type": "string", "title": "Company Name"}, |
|||
"industry": {"type": "string", "title": "Industry"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["company_name", "industry"], |
|||
"title": "CompanyForm", |
|||
}, |
|||
"HTTPValidationError": { |
|||
"properties": { |
|||
"detail": { |
|||
"items": {"$ref": "#/components/schemas/ValidationError"}, |
|||
"type": "array", |
|||
"title": "Detail", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"title": "HTTPValidationError", |
|||
}, |
|||
"UserForm": { |
|||
"properties": { |
|||
"name": {"type": "string", "title": "Name"}, |
|||
"email": {"type": "string", "title": "Email"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["name", "email"], |
|||
"title": "UserForm", |
|||
}, |
|||
"ValidationError": { |
|||
"properties": { |
|||
"loc": { |
|||
"items": { |
|||
"anyOf": [{"type": "string"}, {"type": "integer"}] |
|||
}, |
|||
"type": "array", |
|||
"title": "Location", |
|||
}, |
|||
"msg": {"type": "string", "title": "Message"}, |
|||
"type": {"type": "string", "title": "Error Type"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["loc", "msg", "type"], |
|||
"title": "ValidationError", |
|||
}, |
|||
} |
|||
}, |
|||
} |
@ -1,51 +0,0 @@ |
|||
from typing import List |
|||
|
|||
from fastapi import FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class RecursiveItem(BaseModel): |
|||
sub_items: List["RecursiveItem"] = [] |
|||
name: str |
|||
|
|||
|
|||
RecursiveItem.model_rebuild() |
|||
|
|||
|
|||
class RecursiveSubitemInSubmodel(BaseModel): |
|||
sub_items2: List["RecursiveItemViaSubmodel"] = [] |
|||
name: str |
|||
|
|||
|
|||
class RecursiveItemViaSubmodel(BaseModel): |
|||
sub_items1: List[RecursiveSubitemInSubmodel] = [] |
|||
name: str |
|||
|
|||
|
|||
RecursiveSubitemInSubmodel.model_rebuild() |
|||
RecursiveItemViaSubmodel.model_rebuild() |
|||
|
|||
|
|||
@app.get("/items/recursive", response_model=RecursiveItem) |
|||
def get_recursive(): |
|||
return {"name": "item", "sub_items": [{"name": "subitem", "sub_items": []}]} |
|||
|
|||
|
|||
@app.get("/items/recursive-submodel", response_model=RecursiveItemViaSubmodel) |
|||
def get_recursive_submodel(): |
|||
return { |
|||
"name": "item", |
|||
"sub_items1": [ |
|||
{ |
|||
"name": "subitem", |
|||
"sub_items2": [ |
|||
{ |
|||
"name": "subsubitem", |
|||
"sub_items1": [{"name": "subsubsubitem", "sub_items2": []}], |
|||
} |
|||
], |
|||
} |
|||
], |
|||
} |
@ -1,12 +1,9 @@ |
|||
from fastapi.testclient import TestClient |
|||
|
|||
from ..utils import needs_pydanticv2 |
|||
from .app import app |
|||
|
|||
|
|||
@needs_pydanticv2 |
|||
def test_recursive(): |
|||
from .app_pv2 import app |
|||
|
|||
client = TestClient(app) |
|||
response = client.get("/items/recursive") |
|||
assert response.status_code == 200, response.text |
@ -1,33 +0,0 @@ |
|||
from fastapi.testclient import TestClient |
|||
|
|||
from ..utils import needs_pydanticv1 |
|||
|
|||
|
|||
@needs_pydanticv1 |
|||
def test_recursive(): |
|||
from .app_pv1 import app |
|||
|
|||
client = TestClient(app) |
|||
response = client.get("/items/recursive") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"sub_items": [{"name": "subitem", "sub_items": []}], |
|||
"name": "item", |
|||
} |
|||
|
|||
response = client.get("/items/recursive-submodel") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"name": "item", |
|||
"sub_items1": [ |
|||
{ |
|||
"name": "subitem", |
|||
"sub_items2": [ |
|||
{ |
|||
"name": "subsubitem", |
|||
"sub_items1": [{"name": "subsubsubitem", "sub_items2": []}], |
|||
} |
|||
], |
|||
} |
|||
], |
|||
} |
Loading…
Reference in new issue