Skip to main content

DB-backed settings manager on SQLAlchemy, table-compatible with timurturdyev/simple-settings

Project description

Simple DB Settings

Simple DB Settings

Менеджер настроек в БД для Python на SQLAlchemy 2: группы, TTL-кэш, аудит изменений, типизация через pydantic. Идея взята из Laravel-пакета timurturdyev/simple-settings и совместима с ним по таблице: приложения на Python и PHP могут работать с одними и теми же настройками.

English


Требования

  • 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 (и legacy double)
  • 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

simple_db_settings-1.0.0.tar.gz (27.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

simple_db_settings-1.0.0-py3-none-any.whl (20.9 kB view details)

Uploaded Python 3

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

Hashes for simple_db_settings-1.0.0.tar.gz
Algorithm Hash digest
SHA256 2b0c003ef1c7cd84559995a0780a71b3f40e81c98aeff3ae9efbbf5f43dbdef2
MD5 78ef36a4eb2c1b55f353b82e1c7624d0
BLAKE2b-256 95100227727e6750cae1925b5b649cad31613e8d74a79a7c5ad3d35de2241ff6

See more details on using hashes here.

Provenance

The following attestation bundles were made for simple_db_settings-1.0.0.tar.gz:

Publisher: publish.yml on TimurTurdyev/simple-settings-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file simple_db_settings-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for simple_db_settings-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ae0c9e109085f1645b8b6f67df841b42b9afe7e20ff6b11aa87a30925c55b011
MD5 1be7f356773214f33e74c1ad4d0f2e8e
BLAKE2b-256 1a6d37301b5ae74923ff48d9361dc83fdda8a87564254aa26f02cd1173982dc3

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page