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

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

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

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

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

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

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

  • можно использовать популярный и простой формат для конфигурации, 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

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

+