Simple settings manager
Project description
dgsettings
Универсальный менеджер конфигураций Python-приложений с поддержкой версионирования
- ✔️ Чтение и авто-перезагрузка YAML/JSON-файлов с поддержкой переменных среды и .env
- ✔️ Хранение и обновление конфигов из различных источников: файлов, PostgreSQL, Redis
- ✔️ Версионирование конфигураций — сохранение истории изменений и возможность загрузки конкретных версий
- ✔️ Поддержка RedisJSON и dgredis — автоматическое определение и использование оптимального формата хранения
- ✔️ Плагинная архитектура — легко расширяется под ваши нужды
- ✔️ Работа с несколькими сервисами (service_name)
- ✔️ Легкая интеграция c любой средой (FastAPI, Celery, классические сервисы, микросервисы)
Возможности
- Settings: Основной класс для работы с деревом конфигурации.
- FileConfigStorage: Файловое хранение с автоматическим обновлением при изменении.
- DBConfigStorage: Встроенная поддержка PostgreSQL и других СУБД через SQLAlchemy с версионированием, автоматическое создание таблицы с JSON-полями.
- RedisConfigStorage: Быстрый конфиг через Redis с поддержкой RedisJSON и dgredis.
- check_reload(): Позволяет легко реализовать "hot reload" настроек сервиса, если источник изменился.
Установка
pip install dgsettings # базовая версия
pip install dgsettings[redis] # для Redis-хранилища
pip install dgsettings[db] # для работы с БД (PostgreSQL)
pip install dgsettings[dgredis] # для dgredis клиента
pip install dgsettings[all] # полная установка
Пример использования
Самый простой вариант: использование файлового слоя
from dgsettings import Settings
settings = Settings(file_="config.yaml", env_file=".env")
db = settings.get("db")
print(db["user"]) # поддерживаются переменные окружения и .env-файлов: db_user: "%PGUSER%"
# Авто-перезагрузка (например, в цикле сервиса)
if settings.check_reload():
print("Конфиг-файл изменился! Нужно обновить состояние приложения")
Использование с PostgreSQL (DBConfigStorage) с версионированием
from dgsettings import Settings, DBConfigStorage
from sqlalchemy import create_engine
engine = create_engine("postgresql://user:pass@localhost:5432/mydb")
# Простой вариант с автосозданием таблицы
storage = DBConfigStorage(
engine=engine,
service_name='my_service',
auto_reload=True,
create_table=True # автоматически создаст таблицу app_configs если её нет
)
settings = Settings(storage=storage)
# Сохранение создаёт новую версию
version = storage.save({'db_host': 'localhost', 'db_port': 5432})
print(f"Saved as version {version}")
# Загрузка конкретной версии
config_v1 = storage.load(version=1)
# Получение всех версий
versions = storage.get_versions()
print(f"Available versions: {versions}")
# Удаление старых версий
storage.delete_version(1)
Расширенный пример с кастомной таблицей
from sqlalchemy import Table, MetaData
# Если нужна кастомная таблица
metadata = MetaData()
table = Table("custom_configs", metadata, autoload_with=engine)
storage = DBConfigStorage(
engine=engine,
table=table,
key_column='app_name',
value_column='json_data',
version_column='version_num',
service_name='my_service',
auto_reload=True
)
Использование с Redis (RedisConfigStorage)
from dgsettings import Settings, RedisConfigStorage
import redis
# Стандартный redis клиент
client = redis.Redis(host="localhost", decode_responses=True)
storage = RedisConfigStorage(
redis_client=client,
redis_key="config:myservice",
ts_key="config:myservice:ts", # для hot reload
auto_reload=True,
use_redis_json=True # попытаться использовать RedisJSON если доступен
)
settings = Settings(storage=storage)
# Проверка используемого формата хранения
print(storage.get_storage_info())
# {'use_redis_json': True, 'storage_format': 'RedisJSON', 'is_dgredis': False}
Использование с dgredis (с автоматическим JSON)
from dgsettings import Settings, RedisConfigStorage
import dgredis
# dgredis синхронный
dgredis_client = dgredis.RedisClient(host='localhost', port=6379, db=0)
storage = RedisConfigStorage(
redis_client=dgredis_client,
redis_key='config:myservice',
use_redis_json=True # dgredis поддерживает JSON нативно
)
# dgredis асинхронный
async_client = dgredis.AsyncRedisClient(host='localhost')
async_storage = RedisConfigStorage(
redis_client=async_client,
redis_key='config:myservice',
use_redis_json=True
)
# Асинхронное использование
async def load_config():
config = await async_storage.load()
await async_storage.save({'new_setting': 'value'})
changed = await async_storage.has_changed()
API кратко
Основные методы Settings
Settings(file_="...", env_file="...")— создание и авто-парсинг конфига.parse(file_)— парсинг файла (JSON/YAML).save(file_)— сохранить текущий конфиг в файл.get(key, default=None)— получить значение по ключу.set(obj)— установить словарь настроек.check_reload()— если источник изменился, перезагружает, возвращает True.convert("config.yaml", "config.json", use_yaml=False)— переводит между форматами JSON/YAML.
Методы DBConfigStorage с версионированием
load(version=None)— загружает конкретную версию или последнюю.save(data, increment_version=True)— сохраняет с созданием новой версии или обновлением текущей.get_versions()— возвращает список всех доступных версий.delete_version(version)— удаляет конкретную версию.get_current_version()— возвращает номер текущей версии.
Методы RedisConfigStorage
get_client_info()— информация о типе клиента и формате хранения.force_string_mode()— принудительное переключение на строковое хранение.retry_json_mode()— попытка включить JSON режим.clear_config()— удаление конфигурации.exists()— проверка существования конфигурации.
Переменные среды и .env
Любая строка вида $VARNAME$ или ${VARNAME} в конфиге подменяется на значение переменной среды или из файла .env
.env:
PGUSER=postgres
PGPASSWORD=secret
config.yaml:
db_user: "$PGUSER$"
db_pass: "${PGPASSWORD}"
Автосоздание таблицы PostgreSQL с JSON и версионированием
Таблица создаётся автоматически с оптимальной структурой:
CREATE TABLE app_configs (
id SERIAL PRIMARY KEY,
service_name VARCHAR(128) NOT NULL,
config_data JSON NOT NULL, -- JSON поле для эффективного хранения
version INTEGER NOT NULL DEFAULT 1,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX ix_app_configs_service_version ON app_configs(service_name, version);
# Автосоздание таблицы
storage = DBConfigStorage(
engine=engine,
service_name='my_service',
create_table=True # создаст таблицу если не существует
)
Поддержка различных Redis клиентов
Автоматическое определение типа клиента:
- redis-py: Стандартный клиент с поддержкой RedisJSON модуля
- dgredis: Продвинутый клиент с нативной поддержкой JSON
- Асинхронные клиенты: aioredis, dgredis.AsyncRedisClient
Автоматический выбор формата хранения:
- RedisJSON: Если установлен модуль RedisJSON на сервере
- dgredis JSON: Нативная поддержка JSON в dgredis
- String fallback: Автоматический откат к строковому формату
# Проверка поддерживаемых возможностей
info = storage.get_client_info()
print(f"Client: {info['client_type']}")
print(f"Storage format: {info['storage_format']}")
print(f"Async support: {info['is_async']}")
Необязательные зависимости
- Базовая функциональность: только PyYAML
- БД (PostgreSQL):
sqlalchemy,psycopg2-binaryилиasyncpg - Redis:
redis-pyдля стандартного клиента - dgredis:
dgredisдля расширенного клиента - RedisJSON: модуль RedisJSON на сервере для оптимального хранения
Если зависимости не установлены, соответствующий storage будет недоступен, но базовая функциональность остается рабочей.
Пример сервисной интеграции с версионированием
from dgsettings import Settings, DBConfigStorage
from sqlalchemy import create_engine
# Инициализация
engine = create_engine("postgresql://user:pass@localhost/db")
storage = DBConfigStorage(engine=engine, service_name='web_service', create_table=True)
settings = Settings(storage=storage)
# В основном коде приложения
class MyService:
def __init__(self):
self.settings = settings
self.current_version = storage.get_current_version()
def check_and_reload(self):
"""Проверка и перезагрузка конфигурации"""
if self.settings.check_reload():
new_version = storage.get_current_version()
print(f"Config reloaded! Version: {self.current_version} -> {new_version}")
self.current_version = new_version
self.apply_new_settings()
return True
return False
def rollback_to_version(self, version: int):
"""Откат к предыдущей версии"""
config = storage.load(version=version)
if config:
self.settings.set(config)
print(f"Rolled back to version {version}")
# В потоке/таймере/worker-e проверяем:
service = MyService()
if service.check_and_reload():
print("Settings were updated!")
Расширяемость
Вы можете реализовать свой storage (например, через S3, Consul, etcd).
Просто реализуйте абстрактный класс ConfigStorageBase:
from dgsettings.storage import ConfigStorageBase
class MyCustomStorage(ConfigStorageBase):
def load(self) -> dict:
# Загрузка конфигурации из вашего источника
pass
def save(self, data: dict):
# Сохранение конфигурации
pass
def has_changed(self) -> bool:
# Проверка изменений для auto_reload
pass
Миграция конфигураций между версиями
# Получение всех версий для анализа
versions = storage.get_versions()
for version in versions:
config = storage.load(version=version)
print(f"Version {version}: {list(config.keys())}")
# Создание новой версии на основе старой с изменениями
old_config = storage.load(version=1)
new_config = {**old_config, 'new_feature_enabled': True}
new_version = storage.save(new_config)
Производительность и рекомендации
Для PostgreSQL:
- Используйте JSON поля для эффективных запросов
- Регулярно очищайте старые версии конфигураций
- Настройте индексы для быстрого поиска по service_name
Для Redis:
- Предпочитайте RedisJSON для сложных конфигураций
- Используйте dgredis для максимальной производительности
- Настройте TTL для временных конфигураций
Общие рекомендации:
- Включайте
auto_reload=Trueтолько при необходимости - Используйте версионирование для критичных сервисов
- Настройте мониторинг изменений конфигураций
Contributing
Пулреквесты, багрепорты и обсуждения приветствуются!
Запуск тестов
# Установка dev зависимостей
pip install -e .[dev]
# Запуск тестов
pytest tests/
# Запуск с покрытием
pytest --cov=dgsettings tests/
Лицензия
MIT
Авторы:
Roman Rasputin
Примеры, полная документация по API и архитектуре — см. исходный код и примеры в репозитории!
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dgsettings-1.0.0a10.tar.gz.
File metadata
- Download URL: dgsettings-1.0.0a10.tar.gz
- Upload date:
- Size: 21.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ba2b68eebc36b64f4178f3cb758453a82628745380aebf2540efbd966f2de86
|
|
| MD5 |
98d8f2198d6b43c56035c80d4cabd4fe
|
|
| BLAKE2b-256 |
fb4fce4232ed541dd98cdde83c38442861a6afecd994fce1f47303650e2fdb8e
|
File details
Details for the file dgsettings-1.0.0a10-py3-none-any.whl.
File metadata
- Download URL: dgsettings-1.0.0a10-py3-none-any.whl
- Upload date:
- Size: 19.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7c3891594dc865b0b409370093949317ecfdee8d313afcf5268e9bcecf6cde48
|
|
| MD5 |
5610700955ce99d4a06c956924e93d0b
|
|
| BLAKE2b-256 |
f2086fade1e504cd87157edcb5c5c77cf4b65c9eb558e2911f69d748e65101eb
|