Расскажу про свою минималистичную библиотеку, которая решает задачу удобства переопределения и переиспользования элементов конфигурации в микросервисной среде.

Пока подбирал библиотеку управления конфигурацией, просмотрел популярные решения, которые по отдельности реализуют те или иные потребности, но идеальный вариант найти не удалось. Ключевые особенности библиотеки, которых (суммарно) мне не хватило в изученных решениях:

  • Можно указать множество источников конфигурации;

  • добавляемые в конце источники перезаписывают добавленные ранее;

  • можно сослаться на определенный ранее элемент конфигурации;

  • можно выстроить цепочку вычислений одних свойств на основе других;

  • можно переопределить один элемент в цепочке зависимых свойств не нарушая формулы расчета остальных элементов цепочки

  • можно использовать популярный и простой формат для конфигурации, YAML;

  • можно переопределять элементы через переменные окружени;

  • можно расширять набор источников данных своими реализациями.

Библиотека называется Toya и находится здесь: GitHub и PyPI.

Особенности реализации

Для воплощения в жизнь указанных возможностей используется:

  • pyyaml для работы с форматом YAML;

  • jinja для шаблонизации значений;

  • кастомный YAML-тег (по умолчанию !t, но можно указать свой).

Пример использования

Есть базовый файл конфигурации со значениями по умолчанию, поставляемый в дистрибутиве сервиса, base.yml, содержащий такой фрагмент:

db:
  user: ""
  password: ""
  host: ""
  port: "5432"
  dbname: ""
  schema: "public"
  options: !t "-c search_path={{ schema }}"
  connection_string_base: !t "{{ user }}:{{ password }}@{{ host }}:{{ port }}/{{ dbname }}?options={{ options|urlencode }}"
  sync_connecting_string: !t "postgresql+psycopg://{{ connection_string_base }}"
  async_connecting_string: !t "{{ sync_connecting_string }}"

У нас может появиться потребность перезаписать db.host или db.connection_string_base. При этом мы, естественно, не хотели бы потом руками указывать десяток полей, которые зависят от перезаписываемого поля. А именно так будет при использовании большинства популярных библиотек управления конфигурацией.

Также у нас есть отдельные файлы для каждого из стендов, со специфичными настройками (IFT, STAGE, PROD). Например:

Настройки, общие, для всех стендов:

# all-stands.yml
db:
  user: user
  password: password  
  dbname: app_db

Настройки только для прода:

# prod.yml
db:
  host: prod-db.host

тогда можно прочитать конфигурацияю так:

from pathlib import Path

from toya.config import load_and_eval_config
from toya.supplier.env_supplier import EnvSupplier
from toya.supplier.mapping_supplier import MappingSupplier
from toya.supplier.yaml_supplier import YamlSupplier

cfg = load_and_eval_config([
    YamlSupplier(Path("base.yml")),
    YamlSupplier(Path("all-stands.yml")),
    YamlSupplier(Path("prod.yml"))
])

assert cfg["db"]["async_connecting_string"] == "postgresql+psycopg://user:password@prod-db.host:5432/app_db?options=-c%20search_path%3Dpublic"

Далее можно совместить сырую конфигурацию с Pydantic для работы с типизированными значениями:

from pydantic import BaseModel

class Config(BaseModel):
    class DB(BaseModel):
          sync_connecting_string: str
          async_connecting_string: str

    db: DB
    
cfg = load_and_eval_config([...])
typed_cfg = Config.model_validate(cfg)

Поддерживаемые источники данных

  • YamlSupplier - для чтения из YAML-файла

  • EnvSupplier - для чтения переменных окружения

  • MappingSupplier - для чтения конфигурации из словаря и всего совместимого с ним

Название

Описание

Поддерживает шаблонизацию?

YamlSupplier

для чтения из YAML-файла

+

EnvSupplier

для чтения переменных окружения

-

MappingSupplier

для чтения конфигурации из словаря и всего совместимого с ним

+

Комментарии (0)