Registry

Suppose you have a file called config.ini like so…

!cat /tmp/config.ini

[META]
version = 0.0.1
name = mnist
root = /home/me/projects/${META.name}
description = This is a sample
    config file with a multiline
    description. These are useful for
    project descriptions/changelog/devnotes etc...

[Data]
source = https://files.fast.ai/data/examples/mnist_tiny.tgz
root = ${META.root}/data/

[misc]
x = 1
y = 20
z = float(${x}*${y}**2)
a = ['hello','hi','how','are','you', ${x}*${z}*${y}]
b = {"hi": 1, "hello": 2}

[load]
    [load.test]
    @load = print_root_location
    root = ${Data.root}
    
    [load.csv]
    @load = load_csv_function
    root = ${Data.root}
    
    [load.json]
    @load = load_json_class
    root = ${Data.root}
    
    

You can load it up as an AttrDict

config = parse("/tmp/config.ini")
assert config.META.version == "0.0.1"
assert config.META.root == "/home/me/projects/mnist"
assert isinstance(config.misc.b, AttrDict), type(config.project.data.b)
assert isinstance(config.misc.a, L)

Notice, how the ${} variables got resolved.
Not just that, the varaible z got computed on the fly.
Not just that, some of the variables like list and dict got resolved into their respective python data structures.

config.pretty()
{
    "Data": {
        "root": "/home/me/projects/mnist/data/",
        "source": "https://files.fast.ai/data/examples/mnist_tiny.tgz"
    },
    "META": {
        "description": "This is a sample\nconfig file with a multiline\ndescription. These are useful for\nproject 
descriptions/changelog/devnotes etc...",
        "name": "mnist",
        "root": "/home/me/projects/mnist",
        "version": "0.0.1"
    },
    "load": {
        "csv": {
            "@load": null,
            "root": "/home/me/projects/mnist/data/"
        },
        "json": {
            "@load": "load_json_class",
            "root": "/home/me/projects/mnist/data/"
        },
        "test": {
            "@load": "print_root_location",
            "root": "/home/me/projects/mnist/data/"
        }
    },
    "misc": {
        "a": [
            "hello",
            "hi",
            "how",
            "are",
            "you",
            8000.0
        ],
        "b": {
            "hello": 2,
            "hi": 1
        },
        "x": 1,
        "y": 20,
        "z": 400.0
    }
}
print(config.META.description)
This is a sample
config file with a multiline
description. These are useful for
project descriptions/changelog/devnotes etc...

You can also register/call python functions/callables/classes/objects to strings by running

registry.create("load")


@registry.load.register("print_root_location")
def printer(root):
    return root


@registry.load.register("load_csv_function")
def _load_csv_function(root):
    def load_csv_function(file):
        return f"Loading file from {root}/{file}"

    return load_csv_function


@registry.load.register("load_json_class")
class JsonLoader:
    def __init__(self, root):
        self.root = root

    def __call__(self, file):
        assert file.endswith("json")
        return f"Loading file from {self.root}/{file}"

… and resolve them on parse

config = parse_and_resolve("/tmp/config.ini")
config.load.test
'/home/me/projects/mnist/data/'
config.load.csv(file="file.csv")
'Loading file from /home/me/projects/mnist/data//file.csv'
config.load.json(file="file.json")
'Loading file from /home/me/projects/mnist/data//file.json'