deep_chainmap


Namedeep_chainmap JSON
Version 0.1.2 PyPI version JSON
download
home_pageNone
SummaryA recursive subclass of ChainMap
upload_time2025-01-21 09:32:22
maintainerNone
docs_urlNone
authorC.M.T. Robert
requires_python>=3.9
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # DeepChainMap
[![PyPI](https://img.shields.io/pypi/v/deep-chainmap?logo=pypi&logoColor=white&label=PyPI)](https://pypi.org/project/deep-chainmap/)
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/neutrinoceros/deep_chainmap/main.svg)](https://results.pre-commit.ci/latest/github/neutrinoceros/deep_chainmap/main)
[![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)

A recursive subclass of [`collections.ChainMap`](https://docs.python.org/3/library/collections.html#collections.ChainMap).

## Installation

```shell
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).

```python
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`.
```python
>>> 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
```python
>>> 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`

```python
>>> 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

```python
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](https://pypi.org/project/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
```python
>>> cm["mesh"]["resolution"]["x"]["spacing"] = "exp"
```
The affected layer is `config_file_layer` rather than `runtime_layer`, as one
can see
```python
>>> 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.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "deep_chainmap",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": null,
    "author": "C.M.T. Robert",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/d8/0d/cd208e8defb4864bbf741d0135e52680b2681e46fa783af3508ae23eff22/deep_chainmap-0.1.2.tar.gz",
    "platform": null,
    "description": "# DeepChainMap\n[![PyPI](https://img.shields.io/pypi/v/deep-chainmap?logo=pypi&logoColor=white&label=PyPI)](https://pypi.org/project/deep-chainmap/)\n[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/neutrinoceros/deep_chainmap/main.svg)](https://results.pre-commit.ci/latest/github/neutrinoceros/deep_chainmap/main)\n[![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)\n\nA recursive subclass of [`collections.ChainMap`](https://docs.python.org/3/library/collections.html#collections.ChainMap).\n\n## Installation\n\n```shell\npython -m pip install deep-chainmap\n```\n\n## Usage\n\nThe canonical use case for `collections.ChainMap` is to aggregate configuration\ndata from layered mapping (basically dictionaries) sources. However, it is not\nsuited for non-flat (nested) mappings, since the lookup mechanism only works for\nthe top level of a mapping.\n\n`deep_chainmap.DeepChainMap` provides a simple solution to this problem by making\nrecurive lookups in arbitrarily deeply nested mappings. Let's illustrate this\nwith a simple example. We will simulate 3 layers of mapping, and pretend they\nwere obtained from different sources (a default configuration, a configuration\nfile and parameters configured at runtime).\n\n```python\nfrom deep_chainmap import DeepChainMap\n\ndefault_layer = {\n    \"architecture\": \"gpu\",\n    \"logging_level\": \"warning\",\n    \"solver\": \"RK4\",\n    \"database\": {\n        \"url\": \"unset\",\n        \"keep_in_sync\": False,\n    },\n    \"mesh\": {\n        \"type\": \"rectangular\",\n        \"resolution\": {\n            \"x\": {\n                \"npoints\": 100,\n                \"spacing\": \"linear\",\n            },\n            \"y\": {\n                \"npoints\": 100,\n                \"spacing\": \"linear\",\n            },\n            \"z\": {\n                \"npoints\": 100,\n                \"spacing\": \"linear\",\n            },\n        },\n    },\n}\n\nconfig_file_layer = {\n    \"architecture\": \"cpu\",\n    \"mesh\": {\n        \"resolution\": {\n            \"x\": {\n                \"spacing\": \"log\",\n            },\n            \"z\": {\n                \"npoints\": 1,\n            },\n        },\n    },\n}\n\nruntime_layer = {\n    \"logging_level\": \"debug\",\n    \"database\": {\n        \"url\": \"https://my.database.api\",\n        \"keep_in_sync\": True\n    },\n}\n\n# now building a DeepChainMap\ncm = DeepChainMap(runtime_layer, config_file_layer, default_layer)\n```\n\nNow when a single parameter is requested, it is looked up in each layer until a\nvalue is found, by order of insertion. Here the `runtime_layer` takes priority\nover the `config_file_layer`, which in turns takes priority over the\n`default_layer`.\n```python\n>>> cm[\"logging_level\"]\n'debug'\n>>> cm[\"mesh\"][\"resolution\"][\"x\"][\"spacing\"]\n'log'\n>>> cm[\"mesh\"][\"resolution\"][\"x\"][\"npoints\"]\n100\n```\n\nNote that submappings at any level can be retrieved as new\n`DeepChainMap` instances\n```python\n>>> cm[\"mesh\"]\nDeepChainMap({'resolution': {'x': {'spacing': 'log'}, 'z': {'npoints': 1}}},\n             {'resolution': {'x': {'npoints': 100, 'spacing': 'linear'},\n                             'y': {'npoints': 100, 'spacing': 'linear'},\n                             'z': {'npoints': 100, 'spacing': 'linear'}},\n              'type': 'rectangular'})\n```\n\nThe other important feature is the `to_dict` method, which constructs a builtin\n`dict` from a `DeepChainMap`\n\n```python\n>>> cm.to_dict()\n{\n    'architecture': 'cpu',\n    'logging_level': 'debug',\n    'solver': 'RK4',\n    'database': {\n        'url': 'https://my.database.api',\n        'keep_in_sync': True\n    },\n    'mesh': {\n        'type': 'rectangular',\n        'resolution': {\n            'x': {'npoints': 100, 'spacing': 'log'},\n            'y': {'npoints': 100, 'spacing': 'linear'},\n            'z': {'npoints': 1, 'spacing': 'linear'}\n        }\n    }\n}\n```\nAn important implication is that the `DeepChainMap` class enables a very simple,\nfunctional implementation of a depth-first dict-merge algorithm as\n\n```python\nfrom deep_chainmap import DeepChainMap\n\ndef depth_first_merge(*mappings) -> dict:\n    return DeepChainMap(*mappings).to_dict()\n```\n\n\n\n## Limitations\n\nAs the standard `collections.ChainMap` class, `DeepChainMap` does not, by\ndesign, perform any kind of data validation. Rather, it is _assumed_ that the\ninput mappings are similar in structure, meaning that a key which maps to a dict\nin one of the input mappings is assumed to map to dict instances as well in\nevery other input mapping. Use the excellent\n[schema](https://pypi.org/project/schema/) library or similar projects for this\ntask.\n\n:warning: An important difference with `collections.ChainMap` is that, when\nsetting a (key, value) pair in a `DeepChainMap` instance, the new value is\nstored in the first mapping _which already contains the parent map_. For example\nif we run\n```python\n>>> cm[\"mesh\"][\"resolution\"][\"x\"][\"spacing\"] = \"exp\"\n```\nThe affected layer is `config_file_layer` rather than `runtime_layer`, as one\ncan see\n```python\n>>> config_file_layer\n{\n    'architecture': 'cpu',\n    'mesh': {\n        'resolution': {\n            'x': {'spacing': 'exp'},\n            'z': {'npoints': 1}\n        }\n    }\n}\n>>> runtime_layer\n{\n    'logging_level': 'debug',\n    'database': {\n        'url': 'https://my.database.api',\n        'keep_in_sync': True\n    }\n}\n```\nThis behaviour is a side effect on an implementation detail and subject to\nchange in a future version. Please do not rely on it.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A recursive subclass of ChainMap",
    "version": "0.1.2",
    "project_urls": {
        "Homepage": "https://github.com/neutrinoceros/deep_chainmap"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "bff818069912b20833ccd5a1e65f61e447e0ac76f158441940ad8a866ebd599e",
                "md5": "109e30cef6071e8f353fb4552ef278df",
                "sha256": "c2402cc87d40245d6b280a305a02cae183b66e46210336d3bffc94ebc1fd38e2"
            },
            "downloads": -1,
            "filename": "deep_chainmap-0.1.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "109e30cef6071e8f353fb4552ef278df",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 4340,
            "upload_time": "2025-01-21T09:32:20",
            "upload_time_iso_8601": "2025-01-21T09:32:20.995885Z",
            "url": "https://files.pythonhosted.org/packages/bf/f8/18069912b20833ccd5a1e65f61e447e0ac76f158441940ad8a866ebd599e/deep_chainmap-0.1.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "d80dcd208e8defb4864bbf741d0135e52680b2681e46fa783af3508ae23eff22",
                "md5": "54fa5ecc9d057063c39cd83b25ba6330",
                "sha256": "47b3df87ed5b609ecb094fb44b2662d9718eb202f5cd688c929d73f470f5235c"
            },
            "downloads": -1,
            "filename": "deep_chainmap-0.1.2.tar.gz",
            "has_sig": false,
            "md5_digest": "54fa5ecc9d057063c39cd83b25ba6330",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 5199,
            "upload_time": "2025-01-21T09:32:22",
            "upload_time_iso_8601": "2025-01-21T09:32:22.236616Z",
            "url": "https://files.pythonhosted.org/packages/d8/0d/cd208e8defb4864bbf741d0135e52680b2681e46fa783af3508ae23eff22/deep_chainmap-0.1.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-01-21 09:32:22",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "neutrinoceros",
    "github_project": "deep_chainmap",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "deep_chainmap"
}
        
Elapsed time: 0.38355s