Перейти к содержанию

SecretModel

SecretModel — это базовый класс, от которого наследуется ваш конфиг. Он добавляет поверх pydantic.BaseModel:

  • Распознавание полей-секретов, объявленных через Secret(...)
  • Ленивый фетч при первом обращении к атрибуту
  • Общий thread-safe TTL-кэш
  • Маскирование в repr / model_dump / JSON
  • Проверку шаблонов путей при конструировании

Всё остальное — типы полей, валидаторы, model_config, наследование — работает как в обычном Pydantic. Можно мешать поля vaultly с обычными полями, computed fields, валидаторами и так далее.

Жизненный цикл

class App(SecretModel):
    stage: str
    db_password: str = Secret("/db/{stage}/password", ttl=60)

При вызове App(stage="prod", backend=...):

  1. Pydantic валидирует входыstage должен быть str, и так далее.
  2. Запускается model_validator(mode='after') от vaultly:
    • Привязывает вложенные SecretModel к этому корню — они начинают использовать его кэш и бэкенд.
    • Обходит каждый путь Secret(...) и проверяет, что каждая {var} резолвится в реальное не-секретное поле корня.
    • Если на классе указано validate="fetch", вызывает prefetch() и сразу заполняет кэш.
  3. Конструктор возвращает модель. Ни одно секретное значение пока не прочитано (если не использовали validate="fetch").

При обращении к секретному полю (app.db_password):

  1. SecretModel.__getattribute__ замечает, что это секрет.
  2. Шаблон пути заполняется текущими скалярными полями модели.
  3. Кэш проверяется — при попадании сразу возвращаем значение.
  4. При промахе берётся per-key fetch lock; бэкенд вызывается ровно один раз, даже если 100 потоков одновременно обращаются к этому же полю.
  5. Сырая строка приводится к типу поля (str, int, bool, dict, list или то, что вернёт пользовательский transform=).
  6. Значение кэшируется и возвращается.

Конфигурация

Настройки уровня подкласса задаются через kwargs класса (предпочтительно) или через ClassVar с подчёркиванием (запасной вариант для старого кода):

class App(SecretModel, validate="fetch", stale_on_error=True):
    ...
class App(SecretModel):
    _vaultly_validate = "fetch"
    _vaultly_stale_on_error = True

validate

Что проверять при конструировании:

  • "none" — ничего. Ошибки всплывают только при первом фетче.
  • "paths" (по умолчанию) — проверить, что каждая {var} есть в полях корня. Дёшево, ловит опечатки.
  • "fetch" — дополнительно вызвать prefetch() и прочитать каждый секрет. Любая проблема с бэкендом или правами всплывёт сразу при старте, ценой одного дополнительного раунд-трипа.

stale_on_error

Если бэкенд кинул TransientError и в кэше есть просроченное значение, вернуть это значение с warning'ом в лог. По умолчанию выключено: для некоторых нагрузок отдать устаревшие учётные данные хуже, чем упасть. Включается через kwarg класса:

class App(SecretModel, stale_on_error=True):
    ...

Публичный 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:

from vaultly.backends.aws_ssm import AWSSSMBackend
from vaultly.backends.vault import VaultBackend