Настройки¶
С увеличением сложности проекта и распределением настроек по всему коду, начинается беспорядок.
Отличный фреймворк 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.logging import InterceptHandler
from esmerald.types import LifeSpanHandler
from ..configs.settings import AppSettings
async def start_database(): ...
async def close_database(): ...
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
.