DB-backed settings manager on SQLAlchemy, table-compatible with timurturdyev/simple-settings
Project description
Simple DB Settings
Менеджер настроек в БД для Python на SQLAlchemy 2: группы, TTL-кэш, аудит изменений, типизация через pydantic. Идея взята из Laravel-пакета timurturdyev/simple-settings и совместима с ним по таблице: приложения на Python и PHP могут работать с одними и теми же настройками.
Требования
- Python 3.11+
- SQLAlchemy 2.0+
- Любая СУБД с драйвером для SQLAlchemy: SQLite, PostgreSQL, MySQL/MariaDB
Установка
pip install simple-db-settings
Дополнительные возможности ставятся экстрами:
pip install "simple-db-settings[pydantic]" # типизированные группы
pip install "simple-db-settings[cli]" # консольная команда
Быстрый старт
from sqlalchemy import create_engine
from simple_db_settings import SettingsStore
engine = create_engine("sqlite:///app.db")
store = SettingsStore(engine)
store.create_tables() # или создайте таблицы своей миграцией
site = store.group("site")
site["name"] = "My App"
site["per_page"] = 15
site["name"] # 'My App'
site.get("missing", "default") # 'default'
Группы
Настройки разделены по группам. Группа по умолчанию - global.
email = store.group("email")
email["host"] = "smtp.example.com"
store.group("site").get("host") # None - другая группа
store.groups() # ['email', 'site']
GroupView ведет себя как обычный словарь, работают все привычные операции:
"host" in email # True
len(email) # 1
dict(email) # {'host': 'smtp.example.com'}
del email["host"] # удалить ключ
email.clear() # удалить все настройки группы
Типы данных
Значения сериализуются в JSON и восстанавливаются без ручного приведения:
site["count"] = 42 # int
site["price"] = 9.99 # float
site["enabled"] = True # bool
site["tags"] = ["a", "b"] # list
site["meta"] = {"k": "v"} # dict
site["empty"] = None # None
Массовая запись
site.update({
"name": "My App",
"url": "https://example.com",
"per_page": 15,
})
Вся пачка уходит в БД одним upsert-запросом в одной транзакции. Валидация идет до записи: невалиден хоть один ключ - не сохранится ни один. Кэш сбрасывается один раз после коммита.
Кэш
Чтение идет через TTL-кэш (по умолчанию 5 секунд), кэшируется карта группы целиком.
from simple_db_settings import NullCache
store = SettingsStore(engine, cache_ttl=30) # свой TTL
store = SettingsStore(engine, cache=NullCache()) # без кэша
site.fresh() # прочитать группу из БД мимо кэша
Кэш инвалидируется только после коммита транзакции, поэтому параллельный запрос не затянет в кэш еще не зафиксированные данные. При нескольких процессах (gunicorn, celery) устаревание ограничено TTL.
Свой бэкенд - любой объект с методами get / set / invalidate (протокол CacheBackend), например обертка над Redis.
Транзакции
По умолчанию каждая операция - отдельная короткая транзакция. Чтобы записать настройки атомарно вместе со своими данными, передайте соединение:
with engine.begin() as conn:
conn.execute(orders_table.insert().values(user_id=1))
store.with_connection(conn).group("site")["last_order"] = 1
with_connection() возвращает копию стора на внешнем соединении: она ничего не коммитит сама, коммит общий, кэш сбросится после него.
Аудит
Опциональная история изменений в таблице simple_setting_changes:
from simple_db_settings import SettingsStore, causer
store = SettingsStore(engine, audit=True)
with causer("user", 42):
store.group("site")["per_page"] = 20
Каждая запись и удаление кладет строку: event (created / updated / deleted), old_payload, new_payload, causer_type, causer_id, created_at. Строки аудита пишутся в той же транзакции, что и сами настройки: либо сохранилось все, либо ничего. Вне контекста causer() автор будет NULL.
Как и в PHP-пакете, clear() записей в истории не создает, а холостое удаление (ключа нет) не оставляет следов.
Чтение истории - обычный select:
from sqlalchemy import MetaData, select
from simple_db_settings.schema import make_changes_table
changes = make_changes_table(MetaData())
with engine.connect() as conn:
rows = conn.execute(
select(changes)
.where(changes.c.group == "site", changes.c.name == "per_page")
.order_by(changes.c.created_at.desc())
.limit(20)
).all()
Типизированные настройки
Экстра pydantic. Дефолты живут в коде, БД хранит только отличия от них:
from pydantic import BaseModel
class SiteSettings(BaseModel):
name: str = "My App"
per_page: int = 15
maintenance: bool = False
typed = store.typed(SiteSettings, group="site")
cfg = typed.load() # дефолты + оверрайды из БД
cfg.per_page = 50
typed.save(cfg) # в БД уйдет только per_page
typed.reset("per_page") # вернуть поле к дефолту
typed.reset() # вернуть все поля
save() удаляет из БД поля, значение которых совпало с дефолтом, поэтому смена дефолта в коде сразу видна везде, где поле не переопределяли.
Консольная команда
Экстра cli. URL базы передается флагом --url или переменной окружения SIMPLE_DB_SETTINGS_URL.
export SIMPLE_DB_SETTINGS_URL="sqlite:///app.db"
# Получить и установить (значение парсится как JSON, иначе строка)
simple-db-settings get site_name
simple-db-settings get host --group email
simple-db-settings set per_page 15
simple-db-settings set tags '["a","b"]'
# Список настроек и групп
simple-db-settings list
simple-db-settings list --group email
simple-db-settings groups
# Удаление
simple-db-settings delete site_name
simple-db-settings clear --group email
# Экспорт и импорт JSON
simple-db-settings export backup.json
simple-db-settings export --group email
simple-db-settings import backup.json
simple-db-settings import backup.json --replace
Экспорт и импорт
from simple_db_settings import export_json, import_json
dump = export_json(store) # все группы
dump = export_json(store, "email") # одна группа
import_json(store, dump) # merge: существующие ключи перезаписываются
import_json(store, dump, replace=True) # сначала очистить группы из файла
Файл несет сырые val и type, поэтому типы переносятся без потерь. Формат совместим с setting:export / setting:import из PHP-пакета в обе стороны. Валидация идет до записи: битая запись в файле - и не импортируется ничего.
Совместимость с Laravel-пакетом
Пакет работает с той же таблицей, что и timurturdyev/simple-settings v6:
- схема идентична:
(group, name, val, type, created_at, updated_at), составной первичный ключ(group, name) - Python читает все типы, которые пишет PHP:
string,integer,float,boolean,array,object,null(и legacydouble) - Python пишет
type = 'json'; PHP читает его начиная с v6.1 - таблица аудита и формат export/import тоже общие
Одна таблица настроек - разные приложения на разных языках.
Как это работает
Точка входа - SettingsStore: ему отдают engine и, по желанию, имя таблицы, кэш и флаг аудита. Стор сам ничего не хранит, он раздает GroupView - живые представления групп, через которые идет вся работа. CLI, типизированный слой и export/import - обертки над теми же GroupView, отдельных путей к БД у них нет.
Чтение. site["per_page"] сначала смотрит в кэш. Если карта группы там и не протухла - БД не трогается вообще. При промахе одним select-ом читается вся группа, каждая строка прогоняется через кодек (по колонке type) и готовая карта кладется в кэш. Следующие чтения любой настройки этой группы бесплатны до истечения TTL.
Запись. site["x"] = 1 и site.update({...}) собирают пачку, кодируют значения в JSON и отправляют одним upsert-запросом: insert с обработкой конфликта по (group, name) на диалекте вашей СУБД. Если включен аудит, в той же транзакции читаются старые значения и одним insert-ом пишутся строки истории. Кэш группы сбрасывается строго после коммита.
Внешняя транзакция. Стор, полученный через with_connection(), выполняет те же операции на вашем соединении и не коммитит: судьбу транзакции решает вызывающий код. Сброс кэша откладывается до реального коммита, а откат не оставляет в кэше мусора.
Вторая сторона таблицы. Laravel-приложение с пакетом simple-settings ходит в ту же таблицу со своим кэшем. Оба пакета переживают типы через колонку type, поэтому настройка, записанная одним, корректно читается другим.
Лимиты значений
Колонка val создается как TEXT - на MySQL/MariaDB это 65 535 байт (~64 KB), на PostgreSQL и SQLite ограничения нет. Для типовых настроек этого с большим запасом. Если уперлись в лимит - это сигнал, что в одну настройку положили что-то не то (каталог товаров, лог, контент). Таким данным нужна своя таблица.
Схема БД
simple_settings
group string
name string
val text
type char(20)
created_at
updated_at
PRIMARY KEY (group, name)
simple_setting_changes
id bigint PK
group string
name string
event string created / updated / deleted
old_payload text NULL
new_payload text NULL
causer_type string NULL
causer_id bigint NULL
created_at
Справочник API
| Метод | Описание |
|---|---|
SettingsStore(engine, table_name=..., cache=..., cache_ttl=..., audit=..., changes_table_name=...) |
Создать стор |
store.group(name) |
GroupView для группы |
store.groups() |
Список всех групп |
store.rows(group=None) |
Сырые строки с фильтром по группе |
store.typed(ModelCls, group=...) |
Типизированная группа (pydantic) |
store.with_connection(conn) |
Копия стора на внешнем соединении |
store.create_tables() |
Создать таблицы |
view[key] / view.get(key, default) |
Получить значение |
view[key] = value |
Записать значение |
del view[key] |
Удалить ключ |
view.update(mapping) |
Записать пачку атомарно |
view.fresh() |
Прочитать группу мимо кэша |
view.clear() |
Удалить все настройки группы |
causer(type, id) |
Контекст автора изменений для аудита |
export_json(store, group=None) |
Экспорт в JSON-строку |
import_json(store, text, group=None, replace=False) |
Импорт из JSON-строки |
Лицензия
MIT
English
DB-backed settings manager for Python on SQLAlchemy 2: group namespacing, TTL cache, change audit, typed groups via pydantic. Table-compatible with the Laravel package timurturdyev/simple-settings: Python and PHP apps can share the same settings table.
Requirements: Python 3.11+, SQLAlchemy 2.0+; SQLite, PostgreSQL or MySQL/MariaDB.
Install:
pip install simple-db-settings
pip install "simple-db-settings[pydantic,cli]" # optional extras
Basic usage:
from sqlalchemy import create_engine
from simple_db_settings import SettingsStore
store = SettingsStore(create_engine("sqlite:///app.db"))
store.create_tables()
site = store.group("site")
site["per_page"] = 15
site.get("missing", "default")
site.update({"a": 1, "b": 2}) # single upsert, all-or-nothing
site.fresh() # bypass cache
Values are stored as JSON and restored automatically (int, float, bool, str, list, dict, None). Reads go through a per-group TTL cache invalidated only after commit. Pass a connection via store.with_connection(conn) to join your own transaction.
Audit: SettingsStore(engine, audit=True) records every create/update/delete into simple_setting_changes within the same transaction; wrap calls in causer("user", 42) to attach the author.
Typed groups: defaults live in code, the DB keeps only overrides; typed.save() deletes rows that returned to their defaults.
CLI: simple-db-settings get/set/delete/list/groups/clear/export/import with --url or SIMPLE_DB_SETTINGS_URL.
Cross-language: same table schema as the Laravel package v6; Python reads every PHP type and writes type = 'json', which PHP reads since v6.1. Export files are interchangeable.
For full documentation see the Russian section above.
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 simple_db_settings-1.0.0.tar.gz.
File metadata
- Download URL: simple_db_settings-1.0.0.tar.gz
- Upload date:
- Size: 27.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b0c003ef1c7cd84559995a0780a71b3f40e81c98aeff3ae9efbbf5f43dbdef2
|
|
| MD5 |
78ef36a4eb2c1b55f353b82e1c7624d0
|
|
| BLAKE2b-256 |
95100227727e6750cae1925b5b649cad31613e8d74a79a7c5ad3d35de2241ff6
|
Provenance
The following attestation bundles were made for simple_db_settings-1.0.0.tar.gz:
Publisher:
publish.yml on TimurTurdyev/simple-settings-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
simple_db_settings-1.0.0.tar.gz -
Subject digest:
2b0c003ef1c7cd84559995a0780a71b3f40e81c98aeff3ae9efbbf5f43dbdef2 - Sigstore transparency entry: 2294884075
- Sigstore integration time:
-
Permalink:
TimurTurdyev/simple-settings-py@7db267a4cc0bb64f236a660d1a66a617d4462c37 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/TimurTurdyev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7db267a4cc0bb64f236a660d1a66a617d4462c37 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file simple_db_settings-1.0.0-py3-none-any.whl.
File metadata
- Download URL: simple_db_settings-1.0.0-py3-none-any.whl
- Upload date:
- Size: 20.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ae0c9e109085f1645b8b6f67df841b42b9afe7e20ff6b11aa87a30925c55b011
|
|
| MD5 |
1be7f356773214f33e74c1ad4d0f2e8e
|
|
| BLAKE2b-256 |
1a6d37301b5ae74923ff48d9361dc83fdda8a87564254aa26f02cd1173982dc3
|
Provenance
The following attestation bundles were made for simple_db_settings-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on TimurTurdyev/simple-settings-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
simple_db_settings-1.0.0-py3-none-any.whl -
Subject digest:
ae0c9e109085f1645b8b6f67df841b42b9afe7e20ff6b11aa87a30925c55b011 - Sigstore transparency entry: 2294884121
- Sigstore integration time:
-
Permalink:
TimurTurdyev/simple-settings-py@7db267a4cc0bb64f236a660d1a66a617d4462c37 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/TimurTurdyev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7db267a4cc0bb64f236a660d1a66a617d4462c37 -
Trigger Event:
workflow_dispatch
-
Statement type: