committed by
GitHub
71 changed files with 4902 additions and 638 deletions
@ -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) অংশে দরকার হবে। |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 25 KiB |
@ -0,0 +1,5 @@ |
|||
# یادگیری |
|||
|
|||
اینجا بخشهای مقدماتی و آموزشهایی هستن که برای یادگیری **FastAPI** بهت کمک میکنن. |
|||
|
|||
میتونی اینو یه **کتاب**، یه **دوره آموزشی**، یا راه **رسمی** و پیشنهادی برای یادگیری FastAPI در نظر بگیری. 😎 |
@ -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)**해서,개발 환경과 운영 환경이 완전히 동일하게 작동할 수 있도록 보장 |
|||
* 이 외에도 다양한 기능을 지원 |
|||
|
|||
## 결론 |
|||
|
|||
여기까지 모두 읽고 이해했다면, 이제 많은 개발자들보다 가상 환경을 **훨씬 더 깊이 있게 이해**하게 되셨습니다. 🤓 |
|||
|
|||
이런 세부적인 내용을 알고 있으면, 언젠가 복잡해 보이는 문제를 디버깅할 때 분명히 큰 도움이 될 것입니다. 이제는 **이 모든 것들이 내부에서 어떻게 작동하는지** 알고 있기 때문입니다. 😎 |
@ -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. |
@ -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