SecretModel¶
SecretModel — это базовый класс, от которого наследуется ваш конфиг.
Он добавляет поверх pydantic.BaseModel:
- Распознавание полей-секретов, объявленных через
Secret(...) - Ленивый фетч при первом обращении к атрибуту
- Общий thread-safe TTL-кэш
- Маскирование в
repr/model_dump/ JSON - Проверку шаблонов путей при конструировании
Всё остальное — типы полей, валидаторы, model_config, наследование —
работает как в обычном Pydantic. Можно мешать поля vaultly с обычными
полями, computed fields, валидаторами и так далее.
Жизненный цикл¶
При вызове App(stage="prod", backend=...):
- Pydantic валидирует входы —
stageдолжен бытьstr, и так далее. - Запускается
model_validator(mode='after')от vaultly:- Привязывает вложенные
SecretModelк этому корню — они начинают использовать его кэш и бэкенд. - Обходит каждый путь
Secret(...)и проверяет, что каждая{var}резолвится в реальное не-секретное поле корня. - Если на классе указано
validate="fetch", вызываетprefetch()и сразу заполняет кэш.
- Привязывает вложенные
- Конструктор возвращает модель. Ни одно секретное значение пока
не прочитано (если не использовали
validate="fetch").
При обращении к секретному полю (app.db_password):
SecretModel.__getattribute__замечает, что это секрет.- Шаблон пути заполняется текущими скалярными полями модели.
- Кэш проверяется — при попадании сразу возвращаем значение.
- При промахе берётся per-key fetch lock; бэкенд вызывается ровно один раз, даже если 100 потоков одновременно обращаются к этому же полю.
- Сырая строка приводится к типу поля (
str,int,bool,dict,listили то, что вернёт пользовательскийtransform=). - Значение кэшируется и возвращается.
Конфигурация¶
Настройки уровня подкласса задаются через kwargs класса (предпочтительно) или через ClassVar с подчёркиванием (запасной вариант для старого кода):
validate¶
Что проверять при конструировании:
"none"— ничего. Ошибки всплывают только при первом фетче."paths"(по умолчанию) — проверить, что каждая{var}есть в полях корня. Дёшево, ловит опечатки."fetch"— дополнительно вызватьprefetch()и прочитать каждый секрет. Любая проблема с бэкендом или правами всплывёт сразу при старте, ценой одного дополнительного раунд-трипа.
stale_on_error¶
Если бэкенд кинул TransientError и в кэше есть просроченное значение,
вернуть это значение с warning'ом в лог. По умолчанию выключено: для
некоторых нагрузок отдать устаревшие учётные данные хуже, чем упасть.
Включается через kwarg класса:
Публичный API¶
| Метод | Что делает |
|---|---|
prefetch() |
Заранее загрузить все секреты в дереве. |
refresh(name) |
Очистить кэш одного поля и перечитать. |
refresh_all() |
Очистить весь кэш. |
Что SecretModel НЕ поддерживает¶
Эти операции явно поднимают NotImplementedError:
model.model_copy()copy.copy(model)copy.deepcopy(model)pickle.dumps(model)
Каждая из них либо разделила бы in-memory кэш с открытым текстом
секретов между двумя моделями, либо склонировала бы его и поломала
связи _root во вложенных деревьях. Создавайте новый экземпляр — это
дешевле и понятнее. Подробнее — в гайде по модели
безопасности.
model.model_construct(...) разрешён, но обходит валидацию
Pydantic — а вместе с ней и нашу. Path-валидация и prefetch не
запускаются, ошибки всплывают лениво при первом фетче. Используйте
только если знаете, что делаете.
Публичный API на верхнем уровне¶
from vaultly import (
SecretModel, # базовый класс
Secret, # маркер поля
Backend, # ABC для своих бэкендов
EnvBackend, # встроенный: переменные окружения
MockBackend, # встроенный: in-memory, для тестов
RetryingBackend, # обёртка с ретраями на TransientError
# ошибки
VaultlyError,
ConfigError,
MissingContextVariableError,
SecretNotFoundError,
AuthError,
TransientError,
)
Облачные бэкенды живут в отдельных подмодулях, чтобы import vaultly
работал без их SDK: