Skip to main content

Parse configuration files that include paths to other config files into a single configuration object

Project description

nested-config

PyPI package   GitLab repository   GitHub mirror   pipeline status

If you've ever wanted to have the option of replacing part of a configuration file with a path to another configuration file that contains those sub-parameters, then nested-config might be for you.

nested-config allows you to parse configuration files that contain references to other configuration files using a series of models. If a model includes a nested model as one of its attributes and nested-config finds a string value for that parameter in the configuration file instead of an associative array[^assoc-array], then it assumes that this string is a path to another configuration file that should be parsed and whose contents should replace the string in the main configuration file. If the string appears to be a relative path, it is assumed to be relative to the path of its parent configuration file.

Contents

Basic Usage

Given the following configuration files /tmp/house.toml and /tmp/tmp2/dimensions.toml:

Figure 1: /tmp/house.toml
name = "my house"
dimensions = "tmp2/dimensions.toml"
Figure 2: /tmp/tmp2/dimensions.toml
length = 10
width = 20

You can expand these into a single dict with the following:

Figure 3: Expand /tmp/house.toml
import nested_config

class Dimensions:
    length: int
    width: int


class House:
    name: str
    dimensions: Dimensions


house_dict = nested_config.expand_config("/tmp/house.toml", House)
print(house_dict)
# {'name': 'my house', 'dimensions': {'length': 10, 'width': 20}}

Note that in /tmp/house.toml, dimensions is not a mapping but is a path to another toml file at a path relative to house.toml.

See tests for more detailed use-cases, such as where the root model contains lists or dicts of other models and when those may be included in the root config file or specified as paths to sub-config files.

Nomenclature

loader

A loader is a function that reads a config file and returns a dict containing the key-value pairs from the file. nested-config includes loaders for JSON, TOML, and (if PyYAML is installed) YAML. For example, the JSON loader looks like this:

import json

def json_load(path):
    with open(path, "rb") as fobj:
        return json.load(fobj)

model

nested-config uses the term model to refer to a class definition that includes annotated attributes. For example, this model, Dimensions, includes three attributes, each of float type, x, y, and z:

class Dimensions:
    x: float
    y: float
    z: float

A model can be decorated as a dataclass or using attrs.define or can subclass pydantic.BaseModel to provide some method for instantiating an object instance of the model but they aren't necessary to use nested-config.

The only criterion for a type to be a model is that is has a __dict__ attribute that includes an __annotations__ member or an __annote_func__ member (Python 3.14+). Note: This does not mean that instances of the model must have a __dict__ attribute. For example, instances of classes with __slots__ and NamedTuple instances may not have a __dict__ attribute.

nested model

A nested model is a model that is included within another model as one of its class attributes. For example, the below model House includes an name of string type, and an attribute dimensions of Dimensions type (defined above). Since Dimensions is a model type, this is an example of a nested model.

class House:
    name: str
    dimensions: Dimensions

config dict

A config dict is simply a dict with string keys such as may be obtained by reading in configuration text. For example reading in a string of TOML text with tomllib.loads returns a config dict.

import tomllib

config = "x = 2\ny = 3"
print(tomllib.loads(config))
# {'x': 2, 'y': 3}

API

nested_config.expand_config(config_path, model, *, default_suffix = None)

This function first loads the config file at config_path into a config dict using the appropriate loader. It then uses the attribute annotations of model and/or any nested models within model to see if any of the string values in the configuration file correspond to a nested model. For each such case, the string is assumed to be a path and is loaded into another config dict which replaces the string value in the parent config dict. This continues until all paths are converted and then the fully-expanded config dict is returned.

Note that all non-absolute string paths are assumed to be relative to the path of their parent config file.

The loader for a given config file is determined by file extension (AKA suffix). If default_suffix is specified, any config file with an unknown suffix or no suffix will be assumed to be of that type, e.g. ".toml". (Otherwise this is an error.) It is possible for one config file to include a path to a config file of a different format, so long as each file has the appropriate suffix and there is a loader for that suffix.

nested_config.config_dict_loaders

config_dict_loaders is a dict that maps file suffixes to loaders.

Included loaders

nested-config automatically loads the following files based on extension:

Format Extensions(s) Library
JSON .json json (stdlib)
TOML .toml tomllib (Python 3.11+ stdlib) or tomli
YAML .yaml, .yml pyyaml (extra dependency[^yaml-extra])

Adding loaders

To add a loader for another file extension, simply update config_dict_loaders:

import nested_config
from nested_config import ConfigDict  # alias for dict[str, Any]

def dummy_loader(config_path: Path | str) -> ConfigDict:
    return {"a": 1, "b": 2}

nested_config.config_dict_loaders[".dmy"] = dummy_loader

# or add another extension for an existing loader
nested_config.config_dict_loaders[".jsn"] = nested_config.config_dict_loaders[".json"]

# or use a different library to replace an existing loader
import rtoml

def rtoml_load(path) -> ConfigDict:
    with open(path, "rb") as fobj:
        return rtoml.load(fobj)

nested_config.config_dict_loaders[".toml"] = rtoml_load

Deprecated features in v2.1.0, to be removed in v3.0.0

The following functionality is available only if Pydantic is installed:

  • nested_config.validate_config() expands a configuration file according to a Pydantic model and then validates the config dictionary into an instance of the Pydantic model.
  • nested_config.BaseModel can be used as a replacement for pydantic.BaseModel to include a from_config() classmethod on all models that uses nested_config.validate_config() to create an instance of the model.
  • By importing nested_config, PurePath validators and JSON encoders are added to pydantic in Pydantic 1.8-1.10 (they are included in Pydantic 2.0+)

Pydantic 1.0/2.0 Compatibility

The pydantic functionality in nested-config is runtime compatible with Pydantic 1.8+ and Pydantic 2.0.

The follow table gives info on how to configure the mypy and Pyright type checkers to properly work, depending on the version of Pydantic you are using.

Pydantic Version mypy config mypy cli Pyright config
2.0+ always_false = PYDANTIC_1 --always-false PYDANTIC_1 defineConstant = { "PYDANTIC_1" = false }
1.8-1.10 always_true = PYDANTIC_1 --always-true PYDANTIC_1 defineConstant = { "PYDANTIC_1" = true }

Footnotes

[^yaml-extra]: Install pyyaml separately with pip or install nested-config with pip install nested-config[yaml].

[^assoc-array]: Each language uses one or more names for an associative arrays. JSON calls it an object, YAML calls is a mapping, and TOML calls is a table. Any of course in Python it's a dictionary, or dict.

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

nested_config-2.1.5.tar.gz (83.9 kB view details)

Uploaded Source

Built Distribution

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

nested_config-2.1.5-py3-none-any.whl (13.2 kB view details)

Uploaded Python 3

File details

Details for the file nested_config-2.1.5.tar.gz.

File metadata

  • Download URL: nested_config-2.1.5.tar.gz
  • Upload date:
  • Size: 83.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"LMDE","version":"6","id":"faye","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for nested_config-2.1.5.tar.gz
Algorithm Hash digest
SHA256 d067d536046e2f96feb4cbf91c9d84da0a3de832ed3fc0aeb4eedb01ae5b384d
MD5 d94952a9b9f90bf64dee0b728a7b96a6
BLAKE2b-256 d2bbfc84ac7be25a156e1a4a2f08e93d4d7ddfcfc4a8d66f3ce110850c94a87d

See more details on using hashes here.

File details

Details for the file nested_config-2.1.5-py3-none-any.whl.

File metadata

  • Download URL: nested_config-2.1.5-py3-none-any.whl
  • Upload date:
  • Size: 13.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"LMDE","version":"6","id":"faye","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for nested_config-2.1.5-py3-none-any.whl
Algorithm Hash digest
SHA256 02275e2f39408d39d851b2b490ada8c124477199fee1e06eb35158d81fad23fd
MD5 6d279faa0c5e171dfd9b42f22345eb6d
BLAKE2b-256 82444c59d63cff8e974d29dc4b3479eb621fe3927bc27a3c5819c70957c6c43a

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