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

Настройки

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

Отличный фреймворк Django предоставляет свой способ управления настройками, но из-за наследия кода и сложности, накопившейся за почти 20 лет разработки, они стали громоздкими и трудными для поддержки.

Вдохновленный Django и опытом 99% разработанных приложений, Ravyn оснащен механизмом для работы с настройками на нативном уровне, используя Pydantic для их обработки.

Note

Начиная с версии 0.8.X, Ravyn позволяет использовать настройки на разных уровнях, делая их полностью модульными.

Способы использования настроек

В приложении Ravyn существует два способа использования объекта настроек:

  • Использование RAVYN_SETTINGS_MODULE
  • Использование settings_module

Каждый из этих методов имеет свои специфические случаи применения, но также они могут работать вместе.

RavynSettings и приложение

При запуске экземпляра Ravyn, если параметры не указаны, автоматически загружаются настройки по умолчанию из системного объекта настроек — RavynSettings.

from ravyn import Ravyn

# Loads application default values from RavynSettings
app = Ravyn()
from ravyn import Ravyn

# Creates the application instance with app_name and version set
# and loads the remaining parameters from the RavynSettings
app = Ravyn(app_name="my app example", version="0.1.0")

Пользовательские настройки

Использование настроек по умолчанию из RavynSettings возможно не будет достаточно для большинства приложений.

Поэтому требуются пользовательские настройки.

Все пользовательские настройки должны быть унаследованы от RavynSettings.

Предположим, у нас есть три среды для одного приложения: production, testing, development и файл базовых настроек, содержащий общие настройки для всех трех сред.

from __future__ import annotations

from ravyn import RavynSettings
from ravyn.conf.enums import EnvironmentType
from ravyn.middleware.https import HTTPSRedirectMiddleware
from ravyn.types import Middleware
from lilya.middleware import DefineMiddleware


class AppSettings(RavynSettings):
    # 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 ravyn.conf.enums import EnvironmentType
from ravyn.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 = ("palfrey.asgi", "palfrey.access", "ravyn")
        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 ravyn.conf.enums import EnvironmentType
from ravyn.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 ravyn.conf.enums import EnvironmentType
from ravyn.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, унаследованный от RavynSettings с общими свойствами для всех сред.
  2. Создан по одному файлу настроек для каждой среды, унаследованных от базового AppSettings.
  3. Импортированы специфические настройки базы данных для каждой среды и добавлены события on_startup и on_shutdown, уникальные для каждой среды.

Модуль настроек Ravyn

По умолчанию Ravyn ищет переменную окружения RAVYN_SETTINGS_MODULE для выполнения любых пользовательских настроек. Если переменная не указана, будут выполнены настройки приложения по умолчанию.

palfrey src:app --reload

INFO:     Palfrey 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.
RAVYN_SETTINGS_MODULE=src.configs.production.ProductionSettings palfrey src:app

INFO:     Palfrey 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:RAVYN_SETTINGS_MODULE="src.configs.production.ProductionSettings"; palfrey src:app

INFO:     Palfrey 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.

Это очень просто: RAVYN_SETTINGS_MODULE ищет класс пользовательских настроек, созданный для приложения и загружает его в ленивом режиме.

Модуль настроек (settings_module)

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

settings_module — это параметр, доступный в каждом экземпляре Ravyn и ChildRavyn.

Создание settings_module

settings_module имеет абсолютно тот же принцип, что и RavynSettings, это означает, что каждый settings_module должен быть унаследован от RavynSettings, иначе будет выброшено исключение ImproperlyConfigured.

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

from typing import TYPE_CHECKING

from ravyn import Ravyn, RavynSettings
from ravyn.contrib.schedulers.asyncz.config import AsynczConfig

if TYPE_CHECKING:
    from ravyn.types import SchedulerType


# Create a ChildRavynSettings object
class RavynSettings(RavynSettings):
    app_name: str = "my application"
    secret_key: str = "a child secret"

    @property
    def scheduler_config(self) -> AsynczConfig:
        return AsynczConfig()


# Create an Ravyn application
app = Ravyn(routes=..., settings_module=RavynSettings)

Ravyn упрощает управление настройками на каждом уровне, сохраняя при этом целостность.

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

Порядок приоритетов

Существует порядок приоритетов, в котором Ravyn считывает ваши настройки.

Если в экземпляр Ravyn передан settings_module, этот объект имеет приоритет над всем остальным.

Предположим следующее:

  • Приложение Ravyn с обычными настройками.
  • ChildRavyn со специфическим набором конфигураций, уникальных для него.
from ravyn import ChildRavyn, Ravyn, RavynSettings, Include


# Create a ChildRavynSettings object
class ChildRavynSettings(RavynSettings):
    app_name: str = "child app"
    secret_key: str = "a child secret"


# Create a ChildRavyn application
child_app = ChildRavyn(routes=[...], settings_module=ChildRavynSettings)

# Create an Ravyn application
app = Ravyn(routes=[Include("/child", app=child_app)])

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

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

  1. Создали объект настроек, унаследованный от основного `RavynSettings передали некоторые значения по умолчанию.
  2. Передали ChildRavynSettings в экземпляр ChildRavyn.
  3. Передали ChildRavyn в приложение Ravyn.

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

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

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

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

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

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

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

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

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

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

src/configs/main_settings.py
from typing import TYPE_CHECKING, List

from ravyn import RavynSettings
from ravyn.middleware import RequestSettingsMiddleware

if TYPE_CHECKING:
    from ravyn.types import Middleware


# Create a ChildRavynSettings object
class AppSettings(RavynSettings):
    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 ravyn import RavynSettings


# Create a ChildRavynSettings object
class InstanceSettings(RavynSettings):
    app_name: str = "my instance"

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

src/app.py
from ravyn import Ravyn, Gateway, JSONResponse, Request, get

from .configs.app_settings import InstanceSettings


@get()
async def home(request: Request) -> JSONResponse: ...


app = Ravyn(routes=[Gateway(handler=home)], settings_module=InstanceSettings)

Теперь мы можем запустить сервер, используя AppSettings в качестве глобальных настроек, а InstanceSettings, передавая их при создании экземпляра. AppSettings из файла main_settings.py используется для вызова из командной строки.

RAVYN_SETTINGS_MODULE=src.configs.main_settings.AppSettings palfrey src:app --reload

INFO:     Palfrey 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:RAVYN_SETTINGS_MODULE="src.configs.main_settings.AppSettings"; palfrey src:app --reload

INFO:     Palfrey 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 и RAVYN_SETTINGS_MODULE одновременно!

Посмотрите на порядок приоритетов, чтобы понять, какое значение имеет приоритет и как Ravyn их считывает.

Параметры

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

Check

Все конфигурации являются объектами Pydantic. Ознакомьтесь с CORS, CSRF, Session, JWT, StaticFiles, Template и OpenAPI, чтобы узнать, как их использовать.

Примечание: Чтобы понять, какие параметры существуют, а также соответствующие значения, обратитесь к справочнику по настройкам.

Доступ к настройкам

Существует несколько способов доступа к настройкам приложения:

from ravyn import Ravyn, Gateway, Request, get


@get()
async def app_name(request: Request) -> dict:
    settings = request.app.settings
    return {"app_name": settings.app_name}


app = Ravyn(routes=[Gateway(handler=app_name)])
from ravyn import Ravyn, Gateway, get, settings


@get()
async def app_name() -> dict:
    return {"app_name": settings.app_name}


app = Ravyn(routes=[Gateway(handler=app_name)])
from ravyn import Ravyn, Gateway, get
from ravyn.conf import settings


@get()
async def app_name() -> dict:
    return {"app_name": settings.app_name}


app = Ravyn(routes=[Gateway(handler=app_name)])

Info

Некоторая информация могла быть упомянута в других частях документации, но мы предполагаем, что читатели могли её пропустить.

Порядок важности

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

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

Передача параметров в объект всегда будет переопределять значения из настроек по умолчанию.

from ravyn import RavynSettings
from ravyn.middleware.https import HTTPSRedirectMiddleware
from ravyn.types import Middleware
from lilya.middleware import DefineMiddleware


class AppSettings(RavynSettings
debug: bool = False

              @ property


def middleware(self) -> List[Middleware]:
    return [DefineMiddleware(HTTPSRedirectMiddleware)]

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

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

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

from ravyn import Ravyn

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

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

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

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

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

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

Check

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