Skip to main content

Multi-Layer Configuration Architecture with priority order: in-code defaults, config file, env vars, and CLI.

Project description

ConfStack

Summary: This document describes a multi-layer configuration system with priority order (lowest to highest): (1) in-code defaults, (2) configuration file, (3) lowercase dotted environment variables, (4) uppercase underscored environment variables, (5) command line arguments.

Layer Priority Name Quick Example
1 Lowest In-code Defaults key_00: str = "value"
2 Configuration File {"key_00": "value"}
3 Lowercase Dotted Env. Vars app_name.key_00=value
4 Uppercase Underscored Env. Vars APP_NAME_KEY_00=value
5 Highest Command Line Arguments --key_00 value

Quickie: How to use confstack

Config Model:

# source: src/confstack/example00.py
import pydantic as pdt
import typing as tp
import confstack


class ConfStackExample00(confstack.ConfStack):
    app_name: tp.ClassVar[str] = "app_name"
    key_00: str = "layer_01_value_00"
    key_01: str = "layer_01_value_01"

    class Key02(pdt.BaseModel):
        subkey_01: str = "layer_01_value_02_01"
        subkey_02: str = "layer_01_value_02_02"

    key_02: Key02 = pdt.Field(default_factory=Key02)


if __name__ == "__main__":
    parser = ConfStackExample00.get_argparser()
    parser.add_argument("--extra_flag_01", action="store_true")
    parser.add_argument("--extra_args_01", type=str, default="default_value_01")
    parser.add_argument("extra_pos_args_01", nargs="?")
    config = ConfStackExample00.load_config(cli_args=parser.parse_args())
    config.print_json()

Scenario A: One key overriden by many

cat ~/.config/app_name/config.json
# {"key_00": "L2"}  # default config file

env "app_name.key_00=L3" \
APP_NAME_KEY_00="L4" \
  python -m confstack.example00 \
    --key_00 "L5"
# {
#   "key_00": "L5", # ⭐ cli arg wins (highest priority)
#   "key_01": "layer_01_value_01",
#   "key_02": {
#     "subkey_01": "layer_01_value_02_01",
#     "subkey_02": "layer_01_value_02_02"
#   },
#   "extra_flag_01": false,
#   "extra_args_01": "default_value_01",
#   "extra_pos_args_01": null
# }

Scenario B: Many keys overriden by many

cat ~/.config/app_name/config.json
# {"key_02": {"subkey_02": "L2"}}  # default config file

env "app_name.key_02.subkey_01=L3" \
APP_NAME_KEY_00="L4" \
  python -m confstack.example00 \
    --key_01 "L5" \
    --extra_args_01 "custom_value"
# {
#   "key_00": "L4", # ⭐ uppercase underscored env
#   "key_01": "L5", # ⭐ cli arg
#   "key_02": {
#     "subkey_01": "L3", # ⭐ lowercase dotted env
#     "subkey_02": "L2" # ⭐ default config file
#   },
#   "extra_args_01": "custom_value",
#   "extra_pos_args_01": null
# }

Multi-Layer Configuration Architecture

Layer 1 : In-code Defaults

import pydantic as pdt
import typing as tp


class Config(pdt.BaseModel):
    key_00: str = "layer_01_value_00"
    key_01: str = "layer_01_value_01"

    class Key02(pdt.BaseModel):
        subkey_01: str = "layer_01_value_02_01"
        subkey_02: str = "layer_01_value_02_02"
        subkey_03: str = "layer_01_value_02_03"

    key_02: Key02 = pdt.Field(default_factory=Key02)

    class Key03(pdt.BaseModel):
        class Subkey00(pdt.BaseModel):
            subsubkey_00: str = "layer_01_value_03_00_00"

        subkey_00: Subkey00 = pdt.Field(default_factory=Subkey00)

        class Subkey01(pdt.BaseModel):
            subsubkey_00: str = "layer_01_value_03_01_00"
            subsubkey_01: str = "layer_01_value_03_01_01"

        subkey_01: Subkey01 = pdt.Field(default_factory=Subkey01)

    key_03: Key03 = pdt.Field(default_factory=Key03)

Layer 2 : Configuration File

{
  "key_00": "layer_02_value_00",
  "key_01": "layer_02_value_01",
  "key_02": {
    "subkey_01": "layer_02_value_02_01",
    "subkey_02": "layer_02_value_02_02",
    "subkey_03": "layer_02_value_02_03"
  },
  "key_03": {
    "subkey_00": {
      "subsubkey_00": "layer_02_value_03_00_00"
    },
    "subkey_01": {
      "subsubkey_00": "layer_02_value_03_01_00",
      "subsubkey_01": "layer_02_value_03_01_01"
    }
  }
}

Layer 3 : Lowercase Dotted Environment Variables

env \
  app_name.key_00="layer_03_value_00" \
  app_name.key_01="layer_03_value_01" \
  \
  app_name.key_02.subkey_01="layer_03_value_02_01" \
  app_name.key_02.subkey_02="layer_03_value_02_02" \
  app_name.key_02.subkey_03="layer_03_value_02_03" \
  \
  app_name.key_03.subkey_00.subsubkey_00="layer_03_value_03_00_00" \
  app_name.key_03.subkey_01.subsubkey_00="layer_03_value_03_01_00" \
  app_name.key_03.subkey_01.subsubkey_01="layer_03_value_03_01_01" \
  \
  path/to/app_name.exe
# file_name: docker-compose.yaml
services:
  app:
    image: your_app_image
    environment:

      app_name.key_00: layer_03_value_00
      app_name.key_01: layer_03_value_01

      app_name.key_02.subkey_01: layer_03_value_02_01
      app_name.key_02.subkey_02: layer_03_value_02_02
      app_name.key_02.subkey_03: layer_03_value_02_03

      app_name.key_03.subkey_00.subsubkey_00: layer_03_value_03_00_00
      app_name.key_03.subkey_01.subsubkey_00: layer_03_value_03_01_00
      app_name.key_03.subkey_01.subsubkey_01: layer_03_value_03_01_01

    command: path/to/app_name.exe

Layer 4 : Uppercase Underscored Environment Variables

APP_NAME_KEY_00="layer_04_value_00" \
APP_NAME_KEY_01="layer_04_value_01" \
\
APP_NAME_KEY_02_SUBKEY_01="layer_04_value_02_01" \
APP_NAME_KEY_02_SUBKEY_02="layer_04_value_02_02" \
APP_NAME_KEY_02_SUBKEY_03="layer_04_value_02_03" \
\
APP_NAME_KEY_03_SUBKEY_00_SUBSUBKEY_00="layer_04_value_03_00_00" \
APP_NAME_KEY_03_SUBKEY_01_SUBSUBKEY_00="layer_04_value_03_01_00" \
APP_NAME_KEY_03_SUBKEY_01_SUBSUBKEY_01="layer_04_value_03_01_01" \
\
  path/to/app_name.exe
# file_name: docker-compose.yaml
version: '3.8'
services:
  app:
    image: your_app_image
    environment:

      APP_NAME_KEY_00: layer_04_value_00
      APP_NAME_KEY_01: layer_04_value_01

      APP_NAME_KEY_02_SUBKEY_01: layer_04_value_02_01
      APP_NAME_KEY_02_SUBKEY_02: layer_04_value_02_02
      APP_NAME_KEY_02_SUBKEY_03: layer_04_value_02_03

      APP_NAME_KEY_03_SUBKEY_00_SUBSUBKEY_00: layer_04_value_03_00_00
      APP_NAME_KEY_03_SUBKEY_01_SUBSUBKEY_00: layer_04_value_03_01_00
      APP_NAME_KEY_03_SUBKEY_01_SUBSUBKEY_01: layer_04_value_03_01_01

Layer 5 : Command Line Arguments

import argparse
parser = argparse.ArgumentParser()

parser.add_argument("--key_00", dest="key_00", default=None)
parser.add_argument("--key_01", dest="key_01", default=None)

parser.add_argument("--key_02.subkey_01", dest="key_02__subkey_01", default=None)
parser.add_argument("--key_02.subkey_02", dest="key_02__subkey_02", default=None)
parser.add_argument("--key_02.subkey_03", dest="key_02__subkey_03", default=None)

parser.add_argument("--key_03.subkey_00.subsubkey_00", dest="key_03__subkey_00__subsubkey_00", default=None)
parser.add_argument("--key_03.subkey_01.subsubkey_00", dest="key_03__subkey_01__subsubkey_00", default=None)
parser.add_argument("--key_03.subkey_01.subsubkey_01", dest="key_03__subkey_01__subsubkey_01", default=None)

args = parser.parse_args()
path/to/app_name.py \
  --key_00 layer_05_value_00 \
  --key_01 layer_05_value_01 \
  \
  --key_02.subkey_01 layer_05_value_02_01 \
  --key_02.subkey_02 layer_05_value_02_02 \
  --key_02.subkey_03 layer_05_value_02_03 \
  \
  --key_03.subkey_00.subsubkey_00 layer_05_value_03_00_00 \
  --key_03.subkey_01.subsubkey_00 layer_05_value_03_01_00 \
  --key_03.subkey_01.subsubkey_01 layer_05_value_03_01_01

Appendix: Dotted Environment Variables in Shell

# Set with `env`
env "my.var=value" ./script.sh

# Access
printenv "my.var"
python3 -c "import os; print(os.environ['my.var'])"

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

confstack-0.1.0.1.tar.gz (19.8 kB view details)

Uploaded Source

Built Distribution

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

confstack-0.1.0.1-py3-none-any.whl (20.8 kB view details)

Uploaded Python 3

File details

Details for the file confstack-0.1.0.1.tar.gz.

File metadata

  • Download URL: confstack-0.1.0.1.tar.gz
  • Upload date:
  • Size: 19.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.9

File hashes

Hashes for confstack-0.1.0.1.tar.gz
Algorithm Hash digest
SHA256 4e65afbf5cef63054635a727bef5d85d2890fca8acff2195278eae0119636fa9
MD5 c3ad7936cbe16a42bea21797e50a6b28
BLAKE2b-256 d9420159236df28de65a8b74ed21dd33f99014c5df5000e7560d85a37305ab15

See more details on using hashes here.

File details

Details for the file confstack-0.1.0.1-py3-none-any.whl.

File metadata

  • Download URL: confstack-0.1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 20.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.9

File hashes

Hashes for confstack-0.1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 12f990ea658222f28367c2e05fde306f18898ade6147a75ed5dcceb75f21a899
MD5 d864ccb7341225ccda2cc641d92af85f
BLAKE2b-256 74deba32939bec207d6a838495fb13b49b054e7476ef01099ff6d1a1d00e4ec6

See more details on using hashes here.

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