Настройки¶
С увеличением сложности проекта и распределением настроек по всему коду, начинается беспорядок.
Отличный фреймворк Django предоставляет свой способ управления настройками, но из-за наследия кода и сложности, накопившейся за почти 20 лет разработки, они стали громоздкими и трудными для поддержки.
Вдохновленный Django и опытом 99% разработанных приложений, Esmerald оснащен механизмом для работы с настройками на нативном уровне, используя Pydantic для их обработки.
Note
Начиная с версии 0.8.X, Esmerald позволяет использовать настройки на разных уровнях, делая их полностью модульными.
Способы использования настроек¶
В приложении Esmerald существует два способа использования объекта настроек:
- Использование ESMERALD_SETTINGS_MODULE
- Использование settings_module
Каждый из этих методов имеет свои специфические случаи применения, но также они могут работать вместе.
EsmeraldAPISettings и приложение¶
При запуске экземпляра Esmerald, если параметры не указаны, автоматически загружаются настройки по умолчанию
из системного объекта настроек — EsmeraldAPISettings
.
from esmerald import Esmerald
# Loads application default values from EsmeraldAPISettings
app = Esmerald()
from esmerald import Esmerald
# Creates the application instance with app_name and version set
# and loads the remaining parameters from the EsmeraldAPISettings
app = Esmerald(app_name="my app example", version="0.1.0")
Пользовательские настройки¶
Использование настроек по умолчанию из EsmeraldAPISettings
возможно не будет достаточно
для большинства приложений.
Поэтому требуются пользовательские настройки.
Все пользовательские настройки должны быть унаследованы от EsmeraldAPISettings
.
Предположим, у нас есть три среды для одного приложения: production
, testing
, development
и файл
базовых настроек, содержащий общие настройки для всех трех сред.
from __future__ import annotations
from esmerald import EsmeraldAPISettings
from esmerald.conf.enums import EnvironmentType
from esmerald.middleware.https import HTTPSRedirectMiddleware
from esmerald.types import Middleware
from lilya.middleware import DefineMiddleware
class AppSettings(EsmeraldAPISettings):
# The default is already production but for this example
# we set again the variable
environment: str = EnvironmentType.PRODUCTION
debug: bool = False
reload: bool = False
@property
def middleware(self) -> list[Middleware]:
return [DefineMiddleware(HTTPSRedirectMiddleware)]
from __future__ import annotations
import logging
import sys
from typing import Any
from loguru import logger
from esmerald.conf.enums import EnvironmentType
from esmerald.types import LifeSpanHandler
from ..configs.settings import AppSettings
async def start_database(): ...
async def close_database(): ...
class InterceptHandler(logging.Handler): # pragma: no cover
def emit(self, record: logging.LogRecord) -> None:
level: str
try:
level = logger.level(record.levelname).name
except ValueError:
level = str(record.levelno)
frame, depth = logging.currentframe(), 2
while frame.f_code.co_filename == logging.__file__:
frame = frame.f_back
depth += 1
logger.opt(depth=depth, exception=record.exc_info).log(
level,
record.getMessage(),
)
class DevelopmentSettings(AppSettings):
# the environment can be names to whatever you want.
environment: str = EnvironmentType.DEVELOPMENT
debug: bool = True
reload: bool = True
def __init__(self, *args: Any, **kwds: Any):
super().__init__(*args, **kwds)
logging_level = logging.DEBUG if self.debug else logging.INFO
loggers = ("uvicorn.asgi", "uvicorn.access", "esmerald")
logging.getLogger().handlers = [InterceptHandler()]
for logger_name in loggers:
logging_logger = logging.getLogger(logger_name)
logging_logger.handlers = [InterceptHandler(level=logging_level)]
logger.configure(handlers=[{"sink": sys.stderr, "level": logging_level}])
@property
def on_startup(self) -> list[LifeSpanHandler]:
"""
List of events/actions to be done on_startup.
"""
return [start_database]
@property
def on_shutdown(self) -> list[LifeSpanHandler]:
"""
List of events/actions to be done on_shutdown.
"""
return [close_database]
from __future__ import annotations
from esmerald.conf.enums import EnvironmentType
from esmerald.types import LifeSpanHandler
from ..configs.settings import AppSettings
async def start_database(): ...
async def close_database(): ...
class TestingSettings(AppSettings):
# the environment can be names to whatever you want.
environment: str = EnvironmentType.TESTING
debug: bool = True
reload: bool = False
@property
def on_startup(self) -> list[LifeSpanHandler]:
"""
List of events/actions to be done on_startup.
"""
return [start_database]
@property
def on_shutdown(self) -> list[LifeSpanHandler]:
"""
List of events/actions to be done on_shutdown.
"""
return [close_database]
from __future__ import annotations
from esmerald.conf.enums import EnvironmentType
from esmerald.types import LifeSpanHandler
from ..configs.settings import AppSettings
async def start_database(): ...
async def close_database(): ...
class ProductionSettings(AppSettings):
# the environment can be names to whatever you want.
environment: str = EnvironmentType.PRODUCTION
debug: bool = True
reload: bool = False
@property
def on_startup(self) -> list[LifeSpanHandler]:
"""
List of events/actions to be done on_startup.
"""
return [start_database]
@property
def on_shutdown(self) -> list[LifeSpanHandler]:
"""
List of events/actions to be done on_shutdown.
"""
return [close_database]
Что произошло
- Создан
AppSettings
, унаследованный отEsmeraldAPISettings
с общими свойствами для всех сред. - Создан по одному файлу настроек для каждой среды, унаследованных от базового
AppSettings
. - Импортированы специфические настройки базы данных для каждой среды и добавлены события
on_startup
иon_shutdown
, уникальные для каждой среды.
Модуль настроек Esmerald¶
По умолчанию Esmerald ищет переменную окружения ESMERALD_SETTINGS_MODULE
для выполнения любых
пользовательских настроек. Если переменная не указана, будут выполнены настройки приложения по умолчанию.
uvicorn src:app --reload
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [28720]
INFO: Started server process [28722]
INFO: Waiting for application startup.
INFO: Application startup complete.
ESMERALD_SETTINGS_MODULE=src.configs.production.ProductionSettings uvicorn src:app
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [28720]
INFO: Started server process [28722]
INFO: Waiting for application startup.
INFO: Application startup complete.
$env:ESMERALD_SETTINGS_MODULE="src.configs.production.ProductionSettings"; uvicorn src:app
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [28720]
INFO: Started server process [28722]
INFO: Waiting for application startup.
INFO: Application startup complete.
Это очень просто: ESMERALD_SETTINGS_MODULE
ищет класс пользовательских настроек, созданный для приложения
и загружает его в ленивом режиме.
Модуль настроек (settings_module)¶
Это отличный инструмент для того, чтобы сделать ваши приложения Esmerald полностью независимыми и модульными. Бывают случаи, когда вам просто нужно подключить существующее приложение Esmerald к другому, а это приложение уже имеет уникальные настройки и значения по умолчанию.
settings_module
— это параметр, доступный в каждом экземпляре Esmerald
и ChildEsmerald
.
Создание settings_module¶
settings_module
имеет абсолютно тот же принцип, что и
EsmeraldAPISettings, это означает, что каждый settings_module
должен быть унаследован от EsmeraldAPISettings, иначе будет выброшено исключение ImproperlyConfigured
.
Причина этого заключается в том, чтобы сохранить целостность приложения и настроек.
from typing import TYPE_CHECKING
from esmerald import Esmerald, EsmeraldAPISettings
from esmerald.contrib.schedulers.asyncz.config import AsynczConfig
if TYPE_CHECKING:
from esmerald.types import SchedulerType
# Create a ChildEsmeraldSettings object
class EsmeraldSettings(EsmeraldAPISettings):
app_name: str = "my application"
secret_key: str = "a child secret"
@property
def scheduler_config(self) -> AsynczConfig:
return AsynczConfig()
# Create an Esmerald application
app = Esmerald(routes=..., settings_module=EsmeraldSettings)
Esmerald упрощает управление настройками на каждом уровне, сохраняя при этом целостность.
Посмотрите порядок приоритетов, чтобы понять это немного лучше.
Порядок приоритетов¶
Существует порядок приоритетов, в котором Esmerald считывает ваши настройки.
Если в экземпляр Esmerald передан settings_module
, этот объект имеет приоритет над всем остальным.
Предположим следующее:
- Приложение Esmerald с обычными настройками.
- ChildEsmerald со специфическим набором конфигураций, уникальных для него.
from esmerald import ChildEsmerald, Esmerald, EsmeraldAPISettings, Include
# Create a ChildEsmeraldSettings object
class ChildEsmeraldSettings(EsmeraldAPISettings):
app_name: str = "child app"
secret_key: str = "a child secret"
# Create a ChildEsmerald application
child_app = ChildEsmerald(routes=[...], settings_module=ChildEsmeraldSettings)
# Create an Esmerald application
app = Esmerald(routes=[Include("/child", app=child_app)])
Что здесь происходит
В приведенном выше примере мы:
- Создали объект настроек, унаследованный от основного
EsmeraldAPISettings
и передали некоторые значения по умолчанию. - Передали
ChildEsmeraldSettings
в экземплярChildEsmerald
. - Передали
ChildEsmerald
в приложениеEsmerald
.
Итак, как осуществляется приоритет с использованием settings_module
?
- Если значение параметра (при создании экземпляра), например
app_name
, не указано, будет проверено это же значение внутриsettings_module
. - Если
settings_module
не предоставляет значениеapp_name
, оно будет искать значение вESMERALD_SETTINGS_MODULE
. - Если переменная окружения
ESMERALD_SETTINGS_MODULE
вами не указана, то будет использовано значение по умолчанию Esmerald. Узнайте больше об этом здесь.
Таким образом, порядок приоритетов:
- Значение параметра экземпляра имеет приоритет над
settings_module
. settings_module
имеет приоритет надESMERALD_SETTINGS_MODULE
.ESMERALD_SETTINGS_MODULE
проверяется последним.
Конфигурация настроек и settings_module Esmerald¶
Красота этого модульного подхода заключается в том, что он позволяет использовать оба подхода одновременно (порядок приоритетов).
Рассмотрим пример, где:
- Мы создаем основной объект настроек Esmerald, который будет использоваться
ESMERALD_SETTINGS_MODULE
. - Мы создаем
settings_module
, который будет использоваться экземпляром Esmerald. - Мы запускаем приложение, используя оба варианта.
Также предположим, что у вас все настройки находятся в директории src/configs
.
Создайте конфигурацию для использования ESMERALD_SETTINGS_MODULE
from typing import TYPE_CHECKING, List
from esmerald import EsmeraldAPISettings
from esmerald.middleware import RequestSettingsMiddleware
if TYPE_CHECKING:
from esmerald.types import Middleware
# Create a ChildEsmeraldSettings object
class AppSettings(EsmeraldAPISettings):
app_name: str = "my application"
secret_key: str = "main secret key"
@property
def middleware(self) -> List["Middleware"]:
return [RequestSettingsMiddleware]
Создайте конфигурацию для использования в setting_config
from esmerald import EsmeraldAPISettings
# Create a ChildEsmeraldSettings object
class InstanceSettings(EsmeraldAPISettings):
app_name: str = "my instance"
Создайте экземпляр Esmerald
from esmerald import Esmerald, Gateway, JSONResponse, Request, get
from .configs.app_settings import InstanceSettings
@get()
async def home(request: Request) -> JSONResponse: ...
app = Esmerald(routes=[Gateway(handler=home)], settings_module=InstanceSettings)
Теперь мы можем запустить сервер, используя AppSettings
в качестве глобальных настроек, а InstanceSettings
,
передавая их при создании экземпляра. AppSettings
из файла main_settings.py используется для вызова из
командной строки.
ESMERALD_SETTINGS_MODULE=src.configs.main_settings.AppSettings uvicorn src:app --reload
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [28720]
INFO: Started server process [28722]
INFO: Waiting for application startup.
INFO: Application startup complete.
$env:ESMERALD_SETTINGS_MODULE="src.configs.main_settings.AppSettings"; uvicorn src:app --reload
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [28720]
INFO: Started server process [28722]
INFO: Waiting for application startup.
INFO: Application startup complete.
Отлично! Теперь мы использовали settings_module
и ESMERALD_SETTINGS_MODULE
одновременно!
Посмотрите на порядок приоритетов, чтобы понять, какое значение имеет приоритет и как Esmerald их считывает.
Параметры¶
Параметры, доступные внутри EsmeraldAPISettings
, могут быть переопределены любыми пользовательскими настройками.
Подробнее в справочнике по настройкам.
Check
Все конфигурации являются объектами Pydantic. Ознакомьтесь с CORS, CSRF, Session, JWT, StaticFiles, Template и OpenAPI, чтобы узнать, как их использовать.
Примечание: Чтобы понять, какие параметры существуют, а также соответствующие значения, обратитесь к справочнику по настройкам.
Доступ к настройкам¶
Существует несколько способов доступа к настройкам приложения:
from esmerald import Esmerald, Gateway, Request, get
@get()
async def app_name(request: Request) -> dict:
settings = request.app.settings
return {"app_name": settings.app_name}
app = Esmerald(routes=[Gateway(handler=app_name)])
from esmerald import Esmerald, Gateway, get, settings
@get()
async def app_name() -> dict:
return {"app_name": settings.app_name}
app = Esmerald(routes=[Gateway(handler=app_name)])
from esmerald import Esmerald, Gateway, get
from esmerald.conf import settings
@get()
async def app_name() -> dict:
return {"app_name": settings.app_name}
app = Esmerald(routes=[Gateway(handler=app_name)])
Info
Некоторая информация могла быть упомянута в других частях документации, но мы предполагаем, что читатели могли её пропустить.
Порядок важности¶
Использование настроек для запуска приложения вместо предоставления параметров напрямую в момент создания экземпляра не означает, что одно будет работать с другим.
При создании экземпляра приложения либо вы передаете параметры напрямую, либо используете настройки, либо смешиваете оба подхода.
Передача параметров в объект всегда будет переопределять значения из настроек по умолчанию.
from esmerald import EsmeraldAPISettings
from esmerald.middleware.https import HTTPSRedirectMiddleware
from esmerald.types import Middleware
from lilya.middleware import DefineMiddleware
class AppSettings(EsmeraldAPISettings):
debug: bool = False
@property
def middleware(self) -> List[Middleware]:
return [DefineMiddleware(HTTPSRedirectMiddleware)]
Приложение будет:
- Запущено с
debug
какFalse
. - Запущено с промежуточным ПО
HTTPSRedirectMiddleware
.
Запуск приложения с вышеуказанными настройками обеспечит наличие начального
HTTPSRedirectMiddleware
и значения debug
, но что произойдет, если вы используете настройки вместе
с параметрами при создании экземпляра?
from esmerald import Esmerald
app = Esmerald(debug=True, middleware=[])
Приложение будет:
- Запущено с
debug
какTrue
. - Запущено без пользовательских middlewares, если
HTTPSRedirectMiddleware
был переопределён на[]
.
Хотя в настройках было указано начать с HTTPSRedirectMiddleware
и debug
как False
,
как только вы передаете разные значения при создании объекта Esmerald
,
эти значения становятся приоритетными.
Объявление параметров в экземпляре всегда будет иметь приоритет над значениями из ваших настроек.
Причина, по которой вы должны использовать настройки, заключается в том, что это сделает ваш код более организованным и облегчит его поддержку.
Check
Когда вы передаете параметры при создании объекта Esmerald, а не через параметры, при доступе к
значениям через request.app.settings
эти значения не будут находиться в настройках,
так как они были переданы через создание приложения, а не через объект настроек.
Доступ к этим значениям можно получить, например, непосредственно через request.app.app_name
.