Design

MergedOptions is designed like a cursor looking at some storage where each MergedOptions instance is just a different position in the same storage.

Storage

When you add data to a MergedOptions instance the code will store a tuple of the prefix (path from the root of the options to this data), the data itself and the source of the data, which is specified when you add data to the MergedOptions.

We are able to use this information to convert the storage into a dictionary; to find nested values; and importantly, to memoize access to keys.

Everytime you access a dictionary in the MergedOptions you get back a new instances of MergedOptions that has a different prefix into the same storage.

Data access

There are two ways to access data via a MergedOptions instance. The first is via string access:

m = MergedOptions.using({"a": {"b": 3}})
assert m["a.b"] == 3

And the second is via array access:

m = MergedOptions.using({"a": {"b": 3}})
assert m[["a", "b"]] == 3

The string access will match on the longest match. This is a deliberate decision so that keys are not split by dots:

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

In that example, a.b will match the “a.b” key rather than going into the “a” key and accessing it’s “b” member.

The array access, however, will not do such matching and treats each item in the array as a full key.

Setting data

Just like accessing data, there are two ways of setting data in a MergedOptions: with strings and with arrays.

In both cases, data is added to the storage, rather than trying to modify any existing data.