Перейти к содержанию

Настройки

С увеличением сложности проекта и распределением настроек по всему коду, начинается беспорядок.

Отличный фреймворк 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]

Что произошло

  1. Создан AppSettings, унаследованный от EsmeraldAPISettings с общими свойствами для всех сред.
  2. Создан по одному файлу настроек для каждой среды, унаследованных от базового AppSettings.
  3. Импортированы специфические настройки базы данных для каждой среды и добавлены события 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)])

Что здесь происходит

В приведенном выше примере мы:

  1. Создали объект настроек, унаследованный от основного EsmeraldAPISettings и передали некоторые значения по умолчанию.
  2. Передали ChildEsmeraldSettings в экземпляр ChildEsmerald.
  3. Передали ChildEsmerald в приложение Esmerald.

Итак, как осуществляется приоритет с использованием settings_module?

  1. Если значение параметра (при создании экземпляра), например app_name, не указано, будет проверено это же значение внутри settings_module.
  2. Если settings_module не предоставляет значение app_name, оно будет искать значение в ESMERALD_SETTINGS_MODULE.
  3. Если переменная окружения ESMERALD_SETTINGS_MODULE вами не указана, то будет использовано значение по умолчанию Esmerald. Узнайте больше об этом здесь.

Таким образом, порядок приоритетов:

  1. Значение параметра экземпляра имеет приоритет над settings_module.
  2. settings_module имеет приоритет над ESMERALD_SETTINGS_MODULE.
  3. ESMERALD_SETTINGS_MODULE проверяется последним.

Конфигурация настроек и settings_module Esmerald

Красота этого модульного подхода заключается в том, что он позволяет использовать оба подхода одновременно (порядок приоритетов).

Рассмотрим пример, где:

  1. Мы создаем основной объект настроек Esmerald, который будет использоваться ESMERALD_SETTINGS_MODULE.
  2. Мы создаем settings_module, который будет использоваться экземпляром Esmerald.
  3. Мы запускаем приложение, используя оба варианта.

Также предположим, что у вас все настройки находятся в директории src/configs.

Создайте конфигурацию для использования ESMERALD_SETTINGS_MODULE

src/configs/main_settings.py
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

src/configs/app_settings.py
from esmerald import EsmeraldAPISettings


# Create a ChildEsmeraldSettings object
class InstanceSettings(EsmeraldAPISettings):
    app_name: str = "my instance"

Создайте экземпляр Esmerald

src/app.py
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)]

Приложение будет:

  1. Запущено с debug как False.
  2. Запущено с промежуточным ПО HTTPSRedirectMiddleware.

Запуск приложения с вышеуказанными настройками обеспечит наличие начального HTTPSRedirectMiddleware и значения debug, но что произойдет, если вы используете настройки вместе с параметрами при создании экземпляра?

from esmerald import Esmerald

app = Esmerald(debug=True, middleware=[])

Приложение будет:

  1. Запущено с debug как True.
  2. Запущено без пользовательских middlewares, если HTTPSRedirectMiddleware был переопределён на [].

Хотя в настройках было указано начать с HTTPSRedirectMiddleware и debug как False, как только вы передаете разные значения при создании объекта Esmerald, эти значения становятся приоритетными.

Объявление параметров в экземпляре всегда будет иметь приоритет над значениями из ваших настроек.

Причина, по которой вы должны использовать настройки, заключается в том, что это сделает ваш код более организованным и облегчит его поддержку.

Check

Когда вы передаете параметры при создании объекта Esmerald, а не через параметры, при доступе к значениям через request.app.settings эти значения не будут находиться в настройках, так как они были переданы через создание приложения, а не через объект настроек. Доступ к этим значениям можно получить, например, непосредственно через request.app.app_name.