Skip to main content

A recursive subclass of ChainMap

Project description

DeepChainMap

PyPI pre-commit.ci status uv

A recursive subclass of collections.ChainMap.

Installation

python -m pip install deep-chainmap

Usage

The canonical use case for collections.ChainMap is to aggregate configuration data from layered mapping (basically dictionaries) sources. However, it is not suited for non-flat (nested) mappings, since the lookup mechanism only works for the top level of a mapping.

deep_chainmap.DeepChainMap provides a simple solution to this problem by making recurive lookups in arbitrarily deeply nested mappings. Let's illustrate this with a simple example. We will simulate 3 layers of mapping, and pretend they were obtained from different sources (a default configuration, a configuration file and parameters configured at runtime).

from deep_chainmap import DeepChainMap

default_layer = {
    "architecture": "gpu",
    "logging_level": "warning",
    "solver": "RK4",
    "database": {
        "url": "unset",
        "keep_in_sync": False,
    },
    "mesh": {
        "type": "rectangular",
        "resolution": {
            "x": {
                "npoints": 100,
                "spacing": "linear",
            },
            "y": {
                "npoints": 100,
                "spacing": "linear",
            },
            "z": {
                "npoints": 100,
                "spacing": "linear",
            },
        },
    },
}

config_file_layer = {
    "architecture": "cpu",
    "mesh": {
        "resolution": {
            "x": {
                "spacing": "log",
            },
            "z": {
                "npoints": 1,
            },
        },
    },
}

runtime_layer = {
    "logging_level": "debug",
    "database": {
        "url": "https://my.database.api",
        "keep_in_sync": True
    },
}

# now building a DeepChainMap
cm = DeepChainMap(runtime_layer, config_file_layer, default_layer)

Now when a single parameter is requested, it is looked up in each layer until a value is found, by order of insertion. Here the runtime_layer takes priority over the config_file_layer, which in turns takes priority over the default_layer.

>>> cm["logging_level"]
'debug'
>>> cm["mesh"]["resolution"]["x"]["spacing"]
'log'
>>> cm["mesh"]["resolution"]["x"]["npoints"]
100

Note that submappings at any level can be retrieved as new DeepChainMap instances

>>> cm["mesh"]
DeepChainMap({'resolution': {'x': {'spacing': 'log'}, 'z': {'npoints': 1}}},
             {'resolution': {'x': {'npoints': 100, 'spacing': 'linear'},
                             'y': {'npoints': 100, 'spacing': 'linear'},
                             'z': {'npoints': 100, 'spacing': 'linear'}},
              'type': 'rectangular'})

The other important feature is the to_dict method, which constructs a builtin dict from a DeepChainMap

>>> cm.to_dict()
{
    'architecture': 'cpu',
    'logging_level': 'debug',
    'solver': 'RK4',
    'database': {
        'url': 'https://my.database.api',
        'keep_in_sync': True
    },
    'mesh': {
        'type': 'rectangular',
        'resolution': {
            'x': {'npoints': 100, 'spacing': 'log'},
            'y': {'npoints': 100, 'spacing': 'linear'},
            'z': {'npoints': 1, 'spacing': 'linear'}
        }
    }
}

An important implication is that the DeepChainMap class enables a very simple, functional implementation of a depth-first dict-merge algorithm as

from deep_chainmap import DeepChainMap

def depth_first_merge(*mappings) -> dict:
    return DeepChainMap(*mappings).to_dict()

Limitations

As the standard collections.ChainMap class, DeepChainMap does not, by design, perform any kind of data validation. Rather, it is assumed that the input mappings are similar in structure, meaning that a key which maps to a dict in one of the input mappings is assumed to map to dict instances as well in every other input mapping. Use the excellent schema library or similar projects for this task.

:warning: An important difference with collections.ChainMap is that, when setting a (key, value) pair in a DeepChainMap instance, the new value is stored in the first mapping which already contains the parent map. For example if we run

>>> cm["mesh"]["resolution"]["x"]["spacing"] = "exp"

The affected layer is config_file_layer rather than runtime_layer, as one can see

>>> config_file_layer
{
    'architecture': 'cpu',
    'mesh': {
        'resolution': {
            'x': {'spacing': 'exp'},
            'z': {'npoints': 1}
        }
    }
}
>>> runtime_layer
{
    'logging_level': 'debug',
    'database': {
        'url': 'https://my.database.api',
        'keep_in_sync': True
    }
}

This behaviour is a side effect on an implementation detail and subject to change in a future version. Please do not rely on it.

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

deep_chainmap-0.1.2.tar.gz (5.2 kB view details)

Uploaded Source

Built Distribution

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

deep_chainmap-0.1.2-py3-none-any.whl (4.3 kB view details)

Uploaded Python 3

File details

Details for the file deep_chainmap-0.1.2.tar.gz.

File metadata

  • Download URL: deep_chainmap-0.1.2.tar.gz
  • Upload date:
  • Size: 5.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.5.21

File hashes

Hashes for deep_chainmap-0.1.2.tar.gz
Algorithm Hash digest
SHA256 47b3df87ed5b609ecb094fb44b2662d9718eb202f5cd688c929d73f470f5235c
MD5 54fa5ecc9d057063c39cd83b25ba6330
BLAKE2b-256 d80dcd208e8defb4864bbf741d0135e52680b2681e46fa783af3508ae23eff22

See more details on using hashes here.

File details

Details for the file deep_chainmap-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for deep_chainmap-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 c2402cc87d40245d6b280a305a02cae183b66e46210336d3bffc94ebc1fd38e2
MD5 109e30cef6071e8f353fb4552ef278df
BLAKE2b-256 bff818069912b20833ccd5a1e65f61e447e0ac76f158441940ad8a866ebd599e

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