MergedOptions

This is the main class and the entry point for the programmer.

It provides a mechanism to treat multiple dictionaries as if they were one dictionary.

Also provided is the ability to reference parts and still maintain the idea of it being one dictionary.

With the ability to delete from the dictionary and the ability to convert values on access.

Instantition

It is recommended you use one of the class methods on MergedOptions to create a MergedOptions object.

classmethod MergedOptions.using(*options, **kwargs)

Convenience for calling update multiple times

m = MergedOptions.using({"a": 1}, {"b": 2})

is equivalent to:

m = MergedOptions()
m.update({"a": 1})
m.update({"b": 2})

Any kwargs given to using is passed into update for each provided dictionary.

MergedOptions.Attributes(**kwargs)

This is way of extracting attributes from an object and creating a MergedOptions from that:

obj = type("obj", (object, ), {"one": "two", "two": "three", "four": "five"})
result = MergedOptions.Attributes(obj, ("one", "four"), lift="global")
assertEqual(as_dict(), {"global": {"one": "two", "four": "five"}})
MergedOptions.KeyValuePairs(**kwargs)

This allows us to create a MergedOptions from (key, value) pairs.

result = MergedOptions.KeyValuePairs([(["one"], "two"), (["three", "four"], "five")])
assertEqual(result.as_dict(), {"one": "two", "three": {"four": "five"}})

Instance Methods

class option_merge.MergedOptions(prefix=None, storage=None, dont_prefix=None, converters=None, ignore_converters=False)

Wrapper around multiple dictionaries to behave as one.

Usage:

options = MergedOptions.using(options1, options2, source="SomePlace")

Is equivalent to:

options = MergedOptions()
options.update(options1, source="SomePlace")
options.update(options2, source="SomePlace")

The later an option is added, the more influence it has. i.e. when a key is accessed, later options are looked at first.

When you delete a key, it removes it from the first dictionary it can find. This means a key can change value when deleted rather than disappearing altogether

It will also merge deeply.

So:

options1 = {'a':{'b':1, 'c':3}, 'b':5}
options2 = {'a':{'b':4'}, 'd':7}
merged = MergedOptions.using(options1, options2)
merged['a'] == MergedOptions(prefix='a', <same_options>)
merged['a']['b'] == 4
merged['a']['c'] == 3
merged['d'] == 7

You can also change deeply nested keys:

# You can get keys with "a.b" but setting them must separate the parts of the structure
merged[["a", "b"]] = 5
merged["a"].as_dict() == {"b": 5, "c": 3}

Note

MergedOptions uses a cache system to avoid having to repeatedly iterate through the underlying data structures.

A side effect of this caching is that changes in the underlying structures won’t cause a cache invalidation in the MergedOptions object.

If you wish for changes to be made, make them on the MergedOptions object. (Note that changing a merged options object is an additive operation and will not change the underlying data)

Note

When instantiating a MergedOptions directly, it’s recommended the only option you specify is dont_prefix which is a list of types that you want to be treated as concrete values instead of being converted into a MergedOptions dictionary.

This is necessary for subtypes of dictionaries or anything that returns True from is_dict.

__contains__(path)

Ask storage if it has a path

m = MergedOptions.using({"a": 1})

assert "a" in m
assert "b" not in m
__delitem__(path)

Delete a key from the storage

m = MergedOptions.using({"a": 1}, {"a": 2})
assert m['a'] == 2

del m['a']
assert m['a'] == 1
__eq__(other)

Equal to another merged options if has same storage and prefix

__iter__()

Iterate over the keys

__len__()

Get number of keys we have

__setitem__(path, value)

Set a key in the storage

This takes into account the prefix on this option as well as the provided path.

m = MergedOptions.using({"a": 1})
assertEqual(m.as_dict(), {"a": 1})

a = m["a"]
a['b'] = 2

assertEqual(m.as_dict(), {"a": 1, "b": 2})
add_converter(converter)

Add a converter to our collection

as_dict(key='', ignore_converters=True, seen=None, ignore=None)

Collapse the storage at this prefix into a single dictionary

get(path, default=None, ignore_converters=False)

Get some path or return default value

m = MergedOptions.using({"a": 1})

assert m.get("a") == 1
assert m.get("b") == None
assert m.get("b", 2) == 2

You may also specify ignore_converters and it won’t take the the converters into account.

m = MergedOptions.using({"a": 1})
m.add_converter(Converter(convert=add_one, convert_path=["a"]))
m.converters.activate()

assert m["a"] == 2
assert m.get("a", ignore_converters=True) == 1
install_converters(converters, make_converter)

For each specified converter, make a converter function and install a converter for that name.

def make_converter(name, transformer):
    def convert(path, val):
        return transformer(val)
    return convert

m = MergedOptions.using({"a": 1, "b": 2})
m.install_converters({"a": lambda v: v+1, "b": lambda v: v*2}, make_converter)
m.converters.activate()

assert m['a'] == 2
assert m['b'] == 4
items(ignore_converters=False)

Iterate over [(key, value), …] pairs

keys(ignore_converters=False)

Return a de-duplicated list of the keys we know about

source_for(path, chain=None)

Proxy self.storage.source_for

Source is specifying in calls to __init__ and update and this will find the entries for the specified path an return the first source it finds.

update(options, source=None, **kwargs)

Add new options to the storage under this prefix.

The later options are added, the more influence they have.

wrapped()

Return a MergedOptions with this inside

Equivalent to:

m = MergedOptions.using("a")
wrapped = MergedOptions.using(m, converters=m.converters, dont_prefix=m.dont_prefix)