Name | deep_chainmap JSON |
Version |
0.1.2
JSON |
| download |
home_page | None |
Summary | A recursive subclass of ChainMap |
upload_time | 2025-01-21 09:32:22 |
maintainer | None |
docs_url | None |
author | C.M.T. Robert |
requires_python | >=3.9 |
license | MIT |
keywords |
|
VCS |
 |
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# DeepChainMap
[](https://pypi.org/project/deep-chainmap/)
[](https://results.pre-commit.ci/latest/github/neutrinoceros/deep_chainmap/main)
[](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[](https://pypi.org/project/deep-chainmap/)\n[](https://results.pre-commit.ci/latest/github/neutrinoceros/deep_chainmap/main)\n[](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"
}