Настройки¶
С увеличением сложности проекта и распределением настроек по всему коду, начинается беспорядок.
Отличный фреймворк 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]
Что произошло
- Создан
AppSettings, унаследованный отRavynSettingsс общими свойствами для всех сред. - Создан по одному файлу настроек для каждой среды, унаследованных от базового
AppSettings. - Импортированы специфические настройки базы данных для каждой среды и добавлены события
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)])
Что здесь происходит
В приведенном выше примере мы:
- Создали объект настроек, унаследованный от основного `RavynSettings передали некоторые значения по умолчанию.
- Передали
ChildRavynSettingsв экземплярChildRavyn. - Передали
ChildRavynв приложениеRavyn.
Итак, как осуществляется приоритет с использованием settings_module?
- Если значение параметра (при создании экземпляра), например
app_name, не указано, будет проверено это же значение внутриsettings_module. - Если
settings_moduleне предоставляет значениеapp_name, оно будет искать значение вRAVYN_SETTINGS_MODULE. - Если переменная окружения
RAVYN_SETTINGS_MODULEвами не указана, то будет использовано значение по умолчанию Ravyn. Узнайте больше об этом здесь.
Таким образом, порядок приоритетов:
- Значение параметра экземпляра имеет приоритет над
settings_module. settings_moduleимеет приоритет надRAVYN_SETTINGS_MODULE.RAVYN_SETTINGS_MODULEпроверяется последним.
Конфигурация настроек и settings_module Ravyn¶
Красота этого модульного подхода заключается в том, что он позволяет использовать оба подхода одновременно (порядок приоритетов).
Рассмотрим пример, где:
- Мы создаем основной объект настроек Ravyn, который будет использоваться
RAVYN_SETTINGS_MODULE. - Мы создаем
settings_module, который будет использоваться экземпляром Ravyn. - Мы запускаем приложение, используя оба варианта.
Также предположим, что у вас все настройки находятся в директории src/configs.
Создайте конфигурацию для использования RAVYN_SETTINGS_MODULE
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
from ravyn import RavynSettings
# Create a ChildRavynSettings object
class InstanceSettings(RavynSettings):
app_name: str = "my instance"
Создайте экземпляр Ravyn
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)]
Приложение будет:
- Запущено с
debugкакFalse. - Запущено с промежуточным ПО
HTTPSRedirectMiddleware.
Запуск приложения с вышеуказанными настройками обеспечит наличие начального
HTTPSRedirectMiddleware и значения debug, но что произойдет, если вы используете настройки вместе
с параметрами при создании экземпляра?
from ravyn import Ravyn
app = Ravyn(debug=True, middleware=[])
Приложение будет:
- Запущено с
debugкакTrue. - Запущено без пользовательских middlewares, если
HTTPSRedirectMiddlewareбыл переопределён на[].
Хотя в настройках было указано начать с HTTPSRedirectMiddleware и debug как False,
как только вы передаете разные значения при создании объекта Ravyn,
эти значения становятся приоритетными.
Объявление параметров в экземпляре всегда будет иметь приоритет над значениями из ваших настроек.
Причина, по которой вы должны использовать настройки, заключается в том, что это сделает ваш код более организованным и облегчит его поддержку.
Check
Когда вы передаете параметры при создании объекта Ravyn, а не через параметры, при доступе к
значениям через request.app.settings эти значения не будут находиться в настройках,
так как они были переданы через создание приложения, а не через объект настроек.
Доступ к этим значениям можно получить, например, непосредственно через request.app.app_name.