16 KiB
Налаштування та змінні оточення
У багатьох випадках вашому застосунку можуть знадобитися зовнішні налаштування або конфігурації, наприклад секретні ключі, облікові дані бази даних, облікові дані для email-сервісів тощо.
Більшість із цих налаштувань змінні (можуть змінюватися), як-от URL-адреси баз даних. І багато з них можуть бути чутливими, як-от секрети.
З цієї причини поширено надавати їх у змінних оточення, які зчитуються застосунком.
/// tip | Порада
Щоб зрозуміти змінні оточення, ви можете прочитати Змінні оточення.
///
Типи та перевірка
Ці змінні оточення можуть містити лише текстові строки, оскільки вони зовнішні до Python і мають бути сумісні з іншими програмами та рештою системи (і навіть з різними операційними системами, як-от Linux, Windows, macOS).
Це означає, що будь-яке значення, прочитане в Python зі змінної оточення, буде str, і будь-яке перетворення в інший тип або будь-яка перевірка мають виконуватися в коді.
Pydantic Settings
На щастя, Pydantic надає чудовий інструмент для обробки цих налаштувань із змінних оточення - Pydantic: Settings management.
Встановіть pydantic-settings
Спершу переконайтеся, що ви створили віртуальне оточення, активували його, а потім встановили пакет pydantic-settings:
$ pip install pydantic-settings
---> 100%
Він також входить у склад, якщо ви встановлюєте додаткові можливості «all» за допомогою:
$ pip install "fastapi[all]"
---> 100%
Створіть об'єкт Settings
Імпортуйте BaseSettings із Pydantic і створіть підклас, дуже подібно до моделі Pydantic.
Так само, як і з моделями Pydantic, ви оголошуєте атрибути класу з анотаціями типів і, за потреби, значеннями за замовчуванням.
Ви можете використовувати всі ті самі можливості перевірки та інструменти, що й для моделей Pydantic, як-от різні типи даних і додаткові перевірки з Field().
{* ../../docs_src/settings/tutorial001_py310.py hl[2,5:8,11] *}
/// tip | Порада
Якщо вам потрібно щось швидко скопіювати й вставити, не використовуйте цей приклад, скористайтеся останнім нижче.
///
Потім, коли ви створите екземпляр цього класу Settings (у цьому випадку в об'єкті settings), Pydantic зчитуватиме змінні оточення без урахування регістру, тож верхньорегістрова змінна APP_NAME все одно буде прочитана для атрибута app_name.
Далі він перетворить і перевірить дані. Тож коли ви використовуватимете об'єкт settings, у вас будуть дані тих типів, які ви оголосили (наприклад, items_per_user буде int).
Використовуйте settings
Потім ви можете використати новий об'єкт settings у вашому застосунку:
{* ../../docs_src/settings/tutorial001_py310.py hl[18:20] *}
Запустіть сервер
Далі ви б запустили сервер, передаючи конфігурації як змінні оточення, наприклад, ви можете встановити ADMIN_EMAIL і APP_NAME так:
$ ADMIN_EMAIL="[email protected]" APP_NAME="ChimichangApp" fastapi run main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
/// tip | Порада
Щоб встановити кілька змінних оточення для однієї команди, просто розділіть їх пробілами і розмістіть усі перед командою.
///
Після цього налаштування admin_email буде встановлено в "[email protected]".
app_name буде "ChimichangApp".
А items_per_user збереже своє значення за замовчуванням 50.
Налаштування в іншому модулі
Ви можете розмістити ці налаштування в іншому модулі, як ви бачили в Більші застосунки - кілька файлів.
Наприклад, у вас може бути файл config.py з:
{* ../../docs_src/settings/app01_py310/config.py *}
А потім використати його у файлі main.py:
{* ../../docs_src/settings/app01_py310/main.py hl[3,11:13] *}
/// tip | Порада
Вам також знадобиться файл __init__.py, як ви бачили в Більші застосунки - кілька файлів.
///
Налаштування як залежність
Іноді може бути корисно надавати налаштування через залежність, замість того, щоб мати глобальний об'єкт settings, який використовується всюди.
Це може бути особливо корисно під час тестування, оскільки дуже легко переписати залежність власними налаштуваннями.
Файл конфігурації
Продовжуючи попередній приклад, ваш файл config.py може виглядати так:
{* ../../docs_src/settings/app02_an_py310/config.py hl[10] *}
Зверніть увагу, що тепер ми не створюємо екземпляр за замовчуванням settings = Settings().
Основний файл застосунку
Тепер ми створюємо залежність, яка повертає новий config.Settings().
{* ../../docs_src/settings/app02_an_py310/main.py hl[6,12:13] *}
/// tip | Порада
Ми обговоримо @lru_cache трохи згодом.
Поки що можете вважати, що get_settings() - це звичайна функція.
///
А далі ми можемо вимагати її у функції операції шляху як залежність і використовувати будь-де, де це потрібно.
{* ../../docs_src/settings/app02_an_py310/main.py hl[17,19:21] *}
Налаштування і тестування
Потім буде дуже просто надати інший об'єкт налаштувань під час тестування, створивши переписування залежності для get_settings:
{* ../../docs_src/settings/app02_an_py310/test_main.py hl[9:10,13,21] *}
У переписуванні залежності ми встановлюємо нове значення admin_email під час створення нового об'єкта Settings, а потім повертаємо цей новий об'єкт.
Після цього ми можемо перевірити, що саме він використовується.
Читання файлу .env
Якщо у вас багато налаштувань, які можуть часто змінюватися, можливо в різних оточеннях, може бути корисно розмістити їх у файлі, а потім зчитувати їх із нього так, ніби це змінні оточення.
Ця практика достатньо поширена, тож має назву - ці змінні оточення зазвичай розміщуються у файлі .env, а сам файл називається «dotenv».
/// tip | Порада
Файл, що починається з крапки (.), є прихованим у системах, подібних до Unix, як-от Linux і macOS.
Але файл dotenv не обов'язково має мати саме таку назву.
///
Pydantic має підтримку читання з таких типів файлів за допомогою зовнішньої бібліотеки. Ви можете дізнатися більше тут: Pydantic Settings: Dotenv (.env) support.
/// tip | Порада
Щоб це працювало, потрібно виконати pip install python-dotenv.
///
Файл .env
У вас може бути файл .env із вмістом:
ADMIN_EMAIL="[email protected]"
APP_NAME="ChimichangApp"
Зчитування налаштувань із .env
Потім оновіть ваш config.py так:
{* ../../docs_src/settings/app03_an_py310/config.py hl[9] *}
/// tip | Порада
Атрибут model_config використовується лише для конфігурації Pydantic. Докладніше: Pydantic: Concepts: Configuration.
///
Тут ми визначаємо конфіг env_file усередині вашого класу Pydantic Settings і задаємо значення - ім'я файла з dotenv, який ми хочемо використати.
Створення Settings лише один раз за допомогою lru_cache
Читання файла з диска зазвичай є дорогою (повільною) операцією, тож, імовірно, ви захочете робити це лише один раз і потім перевикористовувати той самий об'єкт налаштувань замість зчитування для кожного запиту.
Але щоразу, коли ми робимо:
Settings()
буде створено новий об'єкт Settings, і під час створення він знову зчитуватиме файл .env.
Якби функція залежності виглядала так:
def get_settings():
return Settings()
ми створювали б цей об'єкт для кожного запиту і читали б файл .env для кожного запиту. ⚠️
Але оскільки ми використовуємо декоратор @lru_cache зверху, об'єкт Settings буде створено лише один раз, під час першого виклику. ✔️
{* ../../docs_src/settings/app03_an_py310/main.py hl[1,11] *}
Потім для будь-якого подальшого виклику get_settings() у залежностях для наступних запитів, замість виконання внутрішнього коду get_settings() і створення нового об'єкта Settings, він повертатиме той самий об'єкт, що був повернутий під час першого виклику, знову і знову.
Технічні деталі lru_cache
@lru_cache модифікує функцію, яку він декорує, так, щоб вона повертала те саме значення, що й уперше, замість повторного обчислення, виконуючи код функції щоразу.
Тобто функція під ним буде виконана один раз для кожної комбінації аргументів. А потім значення, повернені кожною з цих комбінацій аргументів, використовуватимуться знову і знову щоразу, коли функцію викликають із точно такою ж комбінацією аргументів.
Наприклад, якщо у вас є функція:
@lru_cache
def say_hi(name: str, salutation: str = "Ms."):
return f"Hello {salutation} {name}"
ваша програма може виконуватись так:
sequenceDiagram
participant code as Code
participant function as say_hi()
participant execute as Execute function
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Camila")
function ->> execute: execute function code
execute ->> code: return the result
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Camila")
function ->> code: return stored result
end
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Rick")
function ->> execute: execute function code
execute ->> code: return the result
end
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Rick", salutation="Mr.")
function ->> execute: execute function code
execute ->> code: return the result
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Rick")
function ->> code: return stored result
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Camila")
function ->> code: return stored result
end
У випадку з нашою залежністю get_settings() функція взагалі не приймає жодних аргументів, тож вона завжди повертає те саме значення.
Таким чином, вона поводиться майже так само, якби це була просто глобальна змінна. Але оскільки використовується функція залежності, ми можемо легко переписати її для тестування.
@lru_cache є частиною functools, що входить до стандартної бібліотеки Python, більше про це можна прочитати в документації Python для @lru_cache.
Підсумок
Ви можете використовувати Pydantic Settings для обробки налаштувань або конфігурацій вашого застосунку, з усією потужністю моделей Pydantic.
- Використовуючи залежність, ви можете спростити тестування.
- Ви можете використовувати з ним файли
.env. - Використання
@lru_cacheдає змогу уникнути повторного читання файла dotenv для кожного запиту, водночас дозволяючи переписувати його під час тестування.