Skip to main content

nested dictionaries with paths

Project description

Pure python. Path keys. ChainedMap on steroids.


pip install texas

Quick Start

import texas

context = texas.Context()

environment = context.include("environment")
cli = context.include("cli")

config = context.include("environment", "cli")

environment["src.root"] = "~/pics"
cli["src.type"] = "jpg"

config["src.root"]  # ~/pics
config["src.type"]  # jpg

# Change cli's root
cli["src.root"] = "~/other"

# Doesn't change the underlying environment root
environment["src.root"]  # ~/pics

# Modifies cli, which is the top context in config
del config["src.root"]
config["src.root"]  # ~/pics

# Snapshot the contexts into a single dict for use in modules that
# typecheck against dict (instead of
import pprint
# {
#     "src": {
#         "root": "~/pics",
#         "type": "jpg"
#     }
# }


Contexts are namespaced python dictionaries with (configurable) path lookups:

import texas

context = texas.Context()
# Single context
root = context.include("root")

# normal dictionary operations
root["foo"] = "bar"
assert "bar" == root["foo"]
del root["foo"]

# paths
root[""] = "baz"
assert "baz" == root[""]
del root[""]


Include takes a variable number of context names to load into a view:

bottom = context.include("bottom")
top = context.include("top")

both = context.include("bottom", "top")

This can be used to create a priority when looking up values. The top of the context stack will be checked for a key first, then the next, until a context with the given key is found:

bottom["key"] = "bottom"
assert both["key"] == "bottom"

top["key"] = "top"
assert both["key"] == "top"

Combined with paths, this can be very powerful for configuration management:

context = texas.Context()
env = context.include("env")
cli = context.include("cli")
config = context.include("env", "cli")

env["src.root"] = "~/pics"
cli["src.type"] = "jpg"

assert config["src.root"] == "~/pics"
assert config["src.type"] == "jpg"

This even works with individual path segments, since ContextView returns proxies against the underlying mapping objects:

config["src"]  # <texas.context.ContextView at ... >
config["src"]["type"]  # "jpg"

Setting values only applies to the top context in the view, so the value in bottom is still the same:

assert bottom["key"] == "bottom"

This breaks down with mutable values - for instance, this will modify the list in the bottom context:

context = texas.Context() bottom = context.include(“bottom”) top = context.include(“top”) both = context.include(“bottom”, “top”)

bottom[“list”] = [] top[“list”].append(“modified!”)

assert bottom[“list”] == [“modified!”]


Context does some heavy lifting to make paths and multiple dicts work together comfortably. Unfortunately, some libraries make isinstance checks against dict, and not

This is also useful when passing a ContextView to code that will perform many lookups in a tight loop. Because an intermediate lookup on a deeply nested set of dicts creates one proxy per level (ie. something["foo"]["bar"]["baz"] creates two proxies for the value something[""] = "blah") it can be a significant speedup to “snapshot” or bake the ContextView for much faster reading.

Merging dicts in general is a complex problem at best, with many ambiguities. To simplify things, the following rules are used:

(1) For every key in each context, the top-most[0] context that contains
    that key will determine if the value will be used directly, or merged
    with other contexts.
(2) If that value is a, the value of that key in
    each context that contains that key will be merged.
    (A) If there is a context with that key whose value is NOT a mapping,
        its value will be ignored.
    (B) If that value is NOT a, the value will be
        used directly and no merging occurs[1].
3) These rules are applied recursively[2] for any nested mappings.

The “top-most context that contains that key” is not always the top context. In the following, the bottom context is the only one that contains the key “bottom”:

    "bottom": "bottom-value"
    "top": "top-value"


    "bottom": "bottom-value",
    "top": "top-value"

When there is a conflict in type (mapping, non-mapping) the top-most context determines the type. For example, this will take the mapping values from bottom and top, but not middle (whose value is not a mapping):

    "key": {
        "bottom": "bottom-value"
    "key": ["middle", "non", "mapping"]
    "key": {
        "top": "top-value"


    "key": {
        "bottom": "bottom-value",
        "top": "top-value"

While snapshot applies its rules recursively to mappings, the implementation is not recursive. A sample file that merges arbitrary iterables of mappings using the same rules as texas is available here.

Context Factory

By default, texas uses simple dict``s for storage.  However, this can be customized with the ``context_factory function, such as using a collections.OrderedDict or pre-loading values into the node.

This function is used when creating snapshots, the context root, new contexts, and intermediate segments when setting values by paths.

created = 0

def factory():
    global created
    created += 1
    return dict()

# Root context container
context = texas.Context(context_factory=factory)
assert created == 1

# Including contexts
ctx = context.include("some-context")
assert created == 2

# Segments along a path when setting values
ctx[""] = "value"
assert created == 3


Internally, all data is stored in python dicts. You can inspect the global state of a context through its contexts attribute:

import texas
context = texas.Context()

context.include("bar", "", "finally")


Path traversal is performed by the traverse function, which only handles traversal of Therefore, when a non-mapping value is expected at the end of a path, the path should be split like so:

full_path = ""
path, last = full_path.rsplit(".", 1)

assert path == ""
assert last = "baz"

This allows us to travers a root and create the intermediate foo and bar dicts without modifying or inspecting baz:

from texas.traversal import traverse, create_on_missing

root = dict()
full_path = ""
path, key = full_path.rsplit(".", 1)

node = traverse(root, path, ".", create_on_missing(dict))
node[key] = "value"

assert root["foo"]["bar"]["baz"] == "value"

Project details

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Files for texas, version 0.5.2
Filename, size File type Python version Upload date Hashes
Filename, size texas-0.5.2.tar.gz (7.3 kB) File type Source Python version None Upload date Hashes View

Supported by

Pingdom Pingdom Monitoring Google Google Object Storage and Download Analytics Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN DigiCert DigiCert EV certificate StatusPage StatusPage Status page