Налаштування та змінні оточення¶
🌐 Переклад ШІ та людьми
Цей переклад виконано ШІ під керівництвом людей. 🤝
Можливі помилки через неправильне розуміння початкового змісту або неприродні формулювання тощо. 🤖
Ви можете покращити цей переклад, допомігши нам краще спрямовувати AI LLM.
У багатьох випадках вашому застосунку можуть знадобитися зовнішні налаштування або конфігурації, наприклад секретні ключі, облікові дані бази даних, облікові дані для email-сервісів тощо.
Більшість із цих налаштувань змінні (можуть змінюватися), як-от URL-адреси баз даних. І багато з них можуть бути чутливими, як-от секрети.
З цієї причини поширено надавати їх у змінних оточення, які зчитуються застосунком.
Порада
Щоб зрозуміти змінні оточення, ви можете прочитати Змінні оточення.
Типи та перевірка¶
Ці змінні оточення можуть містити лише текстові строки, оскільки вони зовнішні до 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().
from fastapi import FastAPI
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
settings = Settings()
app = FastAPI()
@app.get("/info")
async def info():
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}
Порада
Якщо вам потрібно щось швидко скопіювати й вставити, не використовуйте цей приклад, скористайтеся останнім нижче.
Потім, коли ви створите екземпляр цього класу Settings (у цьому випадку в об'єкті settings), Pydantic зчитуватиме змінні оточення без урахування регістру, тож верхньорегістрова змінна APP_NAME все одно буде прочитана для атрибута app_name.
Далі він перетворить і перевірить дані. Тож коли ви використовуватимете об'єкт settings, у вас будуть дані тих типів, які ви оголосили (наприклад, items_per_user буде int).
Використовуйте settings¶
Потім ви можете використати новий об'єкт settings у вашому застосунку:
from fastapi import FastAPI
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
settings = Settings()
app = FastAPI()
@app.get("/info")
async def info():
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}
Запустіть сервер¶
Далі ви б запустили сервер, передаючи конфігурації як змінні оточення, наприклад, ви можете встановити ADMIN_EMAIL і APP_NAME так:
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Порада
Щоб встановити кілька змінних оточення для однієї команди, просто розділіть їх пробілами і розмістіть усі перед командою.
Після цього налаштування admin_email буде встановлено в "deadpool@example.com".
app_name буде "ChimichangApp".
А items_per_user збереже своє значення за замовчуванням 50.
Налаштування в іншому модулі¶
Ви можете розмістити ці налаштування в іншому модулі, як ви бачили в Більші застосунки - кілька файлів.
Наприклад, у вас може бути файл config.py з:
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
settings = Settings()
А потім використати його у файлі main.py:
from fastapi import FastAPI
from .config import settings
app = FastAPI()
@app.get("/info")
async def info():
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}
Порада
Вам також знадобиться файл __init__.py, як ви бачили в Більші застосунки - кілька файлів.
Налаштування як залежність¶
Іноді може бути корисно надавати налаштування через залежність, замість того, щоб мати глобальний об'єкт settings, який використовується всюди.
Це може бути особливо корисно під час тестування, оскільки дуже легко переписати залежність власними налаштуваннями.
Файл конфігурації¶
Продовжуючи попередній приклад, ваш файл config.py може виглядати так:
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
Зверніть увагу, що тепер ми не створюємо екземпляр за замовчуванням settings = Settings().
Основний файл застосунку¶
Тепер ми створюємо залежність, яка повертає новий config.Settings().
from functools import lru_cache
from typing import Annotated
from fastapi import Depends, FastAPI
from .config import Settings
app = FastAPI()
@lru_cache
def get_settings():
return Settings()
@app.get("/info")
async def info(settings: Annotated[Settings, Depends(get_settings)]):
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from functools import lru_cache
from fastapi import Depends, FastAPI
from .config import Settings
app = FastAPI()
@lru_cache
def get_settings():
return Settings()
@app.get("/info")
async def info(settings: Settings = Depends(get_settings)):
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}
Порада
Ми обговоримо @lru_cache трохи згодом.
Поки що можете вважати, що get_settings() - це звичайна функція.
А далі ми можемо вимагати її у функції операції шляху як залежність і використовувати будь-де, де це потрібно.
from functools import lru_cache
from typing import Annotated
from fastapi import Depends, FastAPI
from .config import Settings
app = FastAPI()
@lru_cache
def get_settings():
return Settings()
@app.get("/info")
async def info(settings: Annotated[Settings, Depends(get_settings)]):
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from functools import lru_cache
from fastapi import Depends, FastAPI
from .config import Settings
app = FastAPI()
@lru_cache
def get_settings():
return Settings()
@app.get("/info")
async def info(settings: Settings = Depends(get_settings)):
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}
Налаштування і тестування¶
Потім буде дуже просто надати інший об'єкт налаштувань під час тестування, створивши переписування залежності для get_settings:
from fastapi.testclient import TestClient
from .config import Settings
from .main import app, get_settings
client = TestClient(app)
def get_settings_override():
return Settings(admin_email="testing_admin@example.com")
app.dependency_overrides[get_settings] = get_settings_override
def test_app():
response = client.get("/info")
data = response.json()
assert data == {
"app_name": "Awesome API",
"admin_email": "testing_admin@example.com",
"items_per_user": 50,
}
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from fastapi.testclient import TestClient
from .config import Settings
from .main import app, get_settings
client = TestClient(app)
def get_settings_override():
return Settings(admin_email="testing_admin@example.com")
app.dependency_overrides[get_settings] = get_settings_override
def test_app():
response = client.get("/info")
data = response.json()
assert data == {
"app_name": "Awesome API",
"admin_email": "testing_admin@example.com",
"items_per_user": 50,
}
У переписуванні залежності ми встановлюємо нове значення admin_email під час створення нового об'єкта Settings, а потім повертаємо цей новий об'єкт.
Після цього ми можемо перевірити, що саме він використовується.
Читання файлу .env¶
Якщо у вас багато налаштувань, які можуть часто змінюватися, можливо в різних оточеннях, може бути корисно розмістити їх у файлі, а потім зчитувати їх із нього так, ніби це змінні оточення.
Ця практика достатньо поширена, тож має назву - ці змінні оточення зазвичай розміщуються у файлі .env, а сам файл називається «dotenv».
Порада
Файл, що починається з крапки (.), є прихованим у системах, подібних до Unix, як-от Linux і macOS.
Але файл dotenv не обов'язково має мати саме таку назву.
Pydantic має підтримку читання з таких типів файлів за допомогою зовнішньої бібліотеки. Ви можете дізнатися більше тут: Pydantic Settings: Dotenv (.env) support.
Порада
Щоб це працювало, потрібно виконати pip install python-dotenv.
Файл .env¶
У вас може бути файл .env із вмістом:
ADMIN_EMAIL="deadpool@example.com"
APP_NAME="ChimichangApp"
Зчитування налаштувань із .env¶
Потім оновіть ваш config.py так:
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
model_config = SettingsConfigDict(env_file=".env")
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
model_config = SettingsConfigDict(env_file=".env")
Порада
Атрибут 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 буде створено лише один раз, під час першого виклику. ✔️
from functools import lru_cache
from typing import Annotated
from fastapi import Depends, FastAPI
from . import config
app = FastAPI()
@lru_cache
def get_settings():
return config.Settings()
@app.get("/info")
async def info(settings: Annotated[config.Settings, Depends(get_settings)]):
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from functools import lru_cache
from fastapi import Depends, FastAPI
from . import config
app = FastAPI()
@lru_cache
def get_settings():
return config.Settings()
@app.get("/info")
async def info(settings: config.Settings = Depends(get_settings)):
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}
Потім для будь-якого подальшого виклику 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 для кожного запиту, водночас дозволяючи переписувати його під час тестування.