from omegaconf import OmegaConf
conf = OmegaConf.create()
print(conf)

"""
Explanation: Installation
Just pip install:
pip install omegaconf
If you want to try this notebook after checking out the repository be sure to run 
python setup.py develop at the repository root before running this code.
Creating OmegaConf objects
Empty
End of explanation
"""


conf = OmegaConf.create(dict(k='v',list=[1,dict(a='1',b='2')]))
print(OmegaConf.to_yaml(conf))

"""
Explanation: From a dictionary
End of explanation
"""


conf = OmegaConf.create([1, dict(a=10, b=dict(a=10))])
print(OmegaConf.to_yaml(conf))

"""
Explanation: From a list
End of explanation
"""


conf = OmegaConf.load('../source/example.yaml')
print(OmegaConf.to_yaml(conf))

"""
Explanation: From a yaml file
End of explanation
"""


yaml = """
a: b
b: c
list:
- item1
- item2
"""
conf = OmegaConf.create(yaml)
print(OmegaConf.to_yaml(conf))

"""
Explanation: From a yaml string
End of explanation
"""


dot_list = ["a.aa.aaa=1", "a.aa.bbb=2", "a.bb.aaa=3", "a.bb.bbb=4"]
conf = OmegaConf.from_dotlist(dot_list)
print(OmegaConf.to_yaml(conf))

"""
Explanation: From a dot-list
End of explanation
"""


# Simulating command line arguments
import sys
sys.argv = ['your-program.py', 'server.port=82', 'log.file=log2.txt']
conf = OmegaConf.from_cli()
print(OmegaConf.to_yaml(conf))

"""
Explanation: From command line arguments
To parse the content of sys.arg:
End of explanation
"""


conf = OmegaConf.load('../source/example.yaml')
print(OmegaConf.to_yaml(conf))

"""
Explanation: Access and manipulation
Input yaml file:
End of explanation
"""


conf.server.port

"""
Explanation: Object style access:
End of explanation
"""


conf['log']['rotation']

"""
Explanation: dictionary style access
End of explanation
"""


conf.users[0]

"""
Explanation: items in list
End of explanation
"""


conf.server.port = 81

"""
Explanation: Changing existing keys
End of explanation
"""


conf.server.hostname = "localhost"

"""
Explanation: Adding new keys
End of explanation
"""


conf.database = {'hostname': 'database01', 'port': 3306}

"""
Explanation: Adding a new dictionary
End of explanation
"""


conf.get('missing_key', 'a default value')

"""
Explanation: providing default values
End of explanation
"""


from omegaconf import MissingMandatoryValue
try:
    conf.log.file
except MissingMandatoryValue as exc:
    print(exc)

"""
Explanation: Accessing mandatory values
Accessing fields with the value ??? will cause a MissingMandatoryValue exception.
Use this to indicate that the value must be set before accessing.
End of explanation
"""


conf = OmegaConf.load('../source/config_interpolation.yaml')
print(OmegaConf.to_yaml(conf))

# Primitive interpolation types are inherited from the referenced value
print("conf.client.server_port: ", conf.client.server_port, type(conf.client.server_port).__name__)
# Composite interpolation types are always string
print("conf.client.url: ", conf.client.url, type(conf.client.url).__name__)

"""
Explanation: Variable interpolation
OmegaConf support variable interpolation, Interpolations are evaluated lazily on access.
Config node interpolation
The interpolated variable can be the path to another node in the configuration, and in that case the value will be the value of that node.
This path may use either dot-notation (foo.1), brackets ([foo][1]) or a mix of both (foo[1], [foo].1).
Interpolations are absolute by default. Relative interpolation are prefixed by one or more dots: The first dot denotes the level of the node itself and additional dots are going up the parent hierarchy. e.g. ${..foo} points to the foo sibling of the parent of the current node.
End of explanation
"""


print(OmegaConf.to_yaml(conf, resolve=True))

"""
Explanation: to_yaml() will resolve interpolations if resolve=True is passed
End of explanation
"""


cfg = OmegaConf.create(
    {
        "plans": {"A": "plan A", "B": "plan B"},
        "selected_plan": "A",
        "plan": "${plans[${selected_plan}]}",
    }
)
print(f"Default: cfg.plan = {cfg.plan}")
cfg.selected_plan = "B"
print(f"After selecting plan B: cfg.plan = {cfg.plan}")

"""
Explanation: Interpolations may be nested, enabling more advanced behavior like dynamically selecting a sub-config:
End of explanation
"""


cfg = OmegaConf.create(
    {
        "john": {"height": 180, "weight": 75},
        "player": "${john}",
    }
)
(cfg.player.height, cfg.player.weight)

"""
Explanation: Interpolated nodes can be any node in the config, not just leaf nodes:
End of explanation
"""


# Let's set up the environment first (only needed for this demonstration)
import os
os.environ['USER'] = 'omry'

"""
Explanation: Environment variable interpolation
Access to environment variables is supported using oc.env.
End of explanation
"""


conf = OmegaConf.load('../source/env_interpolation.yaml')
print(OmegaConf.to_yaml(conf))

conf = OmegaConf.load('../source/env_interpolation.yaml')
print(OmegaConf.to_yaml(conf, resolve=True))

"""
Explanation: Here is an example config file interpolates with the USER environment variable:
End of explanation
"""


cfg = OmegaConf.create(
    {
        "database": {
            "password1": "${oc.env:DB_PASSWORD,password}",
            "password2": "${oc.env:DB_PASSWORD,12345}",
            "password3": "${oc.env:DB_PASSWORD,null}",
        },
    }
)
print(repr(cfg.database.password1))
print(repr(cfg.database.password2))
print(repr(cfg.database.password3))

"""
Explanation: You can specify a default value to use in case the environment variable is not set.
In such a case, the default value is converted to a string using str(default), unless it is null (representing Python None) - in which case None is returned. 
The following example falls back to default passwords when DB_PASSWORD is not defined:
End of explanation
"""


cfg = OmegaConf.create(
    {
        "database": {
            "port": "${oc.decode:${oc.env:DB_PORT}}",
            "nodes": "${oc.decode:${oc.env:DB_NODES}}",
            "timeout": "${oc.decode:${oc.env:DB_TIMEOUT,null}}",
        }
    }
)

os.environ["DB_PORT"] = "3308"  # integer
os.environ["DB_NODES"] = "[host1, host2, host3]"  # list
os.environ.pop("DB_TIMEOUT", None)  # unset variable

print("port (int):", repr(cfg.database.port))
print("nodes (list):", repr(cfg.database.nodes))
print("timeout (missing variable):", repr(cfg.database.timeout))

os.environ["DB_TIMEOUT"] = "${.port}"
print("timeout (interpolation):", repr(cfg.database.timeout))

"""
Explanation: Decoding strings with interpolations
With oc.decode, strings can be converted into their corresponding data types using the OmegaConf grammar.
This grammar recognizes typical data types like bool, int, float, dict and list,
e.g. "true", "1", "1e-3", "{a: b}", "[a, b, c]".
It will also resolve interpolations like "${foo}", returning the corresponding value of the node.
Note that:

When providing as input to oc.decode a string that is meant to be decoded into another string, in general
  the input string should be quoted (since only a subset of characters are allowed by the grammar in unquoted
  strings). For instance, a proper string interpolation could be: "'Hi! My name is: ${name}'" (with extra quotes).
None (written as null in the grammar) is the only valid non-string input to oc.decode (returning None in that case)

This resolver can be useful for instance to parse environment variables:
End of explanation
"""


OmegaConf.register_new_resolver("plus_10", lambda x: x + 10)
conf = OmegaConf.create({'key': '${plus_10:990}'})
conf.key

"""
Explanation: Custom interpolations
You can add additional interpolation types using custom resolvers.
The example below creates a resolver that adds 10 to the given value.
End of explanation
"""


OmegaConf.register_new_resolver("plus", lambda x, y: x + y)
conf = OmegaConf.create({"a": 1, "b": 2, "a_plus_b": "${plus:${a},${b}}"})
conf.a_plus_b

"""
Explanation: You can take advantage of nested interpolations to perform custom operations over variables:
End of explanation
"""


import random
random.seed(1234)

OmegaConf.register_new_resolver("cached", random.randint, use_cache=True)
OmegaConf.register_new_resolver("uncached", random.randint)

cfg = OmegaConf.create(
    {
        "uncached": "${uncached:0,10000}",
        "cached_1": "${cached:0,10000}",
        "cached_2": "${cached:0, 10000}",
        "cached_3": "${cached:0,${uncached}}",
    }
)

# not the same since the cache is disabled by default
print("Without cache:", cfg.uncached, "!=", cfg.uncached)

# same value on repeated access thanks to the cache
print("With cache:", cfg.cached_1, "==", cfg.cached_1)

# same value as `cached_1` since the input is the same
print("With cache (same input):", cfg.cached_2, "==", cfg.cached_1)

# same value even if `uncached` changes, because the cache is based
# on the string literal "${uncached}" that remains the same
print("With cache (interpolation):", cfg.cached_3, "==", cfg.cached_3)



"""
Explanation: By default a custom resolver is called on every access, but it is possible to cache its output
by registering it with use_cache=True.
This may be useful either for performance reasons or to ensure the same value is always returned.
Note that the cache is based on the string literals representing the resolver's inputs, and not
the inputs themselves:
End of explanation
"""


base_conf = OmegaConf.load('../source/example2.yaml')
print(OmegaConf.to_yaml(base_conf))

second_conf = OmegaConf.load('../source/example3.yaml')
print(OmegaConf.to_yaml(second_conf))

from omegaconf import OmegaConf
import sys

# Merge configs:
conf = OmegaConf.merge(base_conf, second_conf)

# Simulate command line arguments
sys.argv = ['program.py', 'server.port=82']
# Merge with cli arguments
conf.merge_with_cli()
print(OmegaConf.to_yaml(conf))

"""
Explanation: Merging configurations
Merging configurations enables the creation of reusable configuration files for each logical component instead of a single config file for each variation of your task.
Machine learning experiment example:
python
conf = OmegaConf.merge(base_cfg, model_cfg, optimizer_cfg, dataset_cfg)
Web server configuration example:
python
conf = OmegaConf.merge(server_cfg, plugin1_cfg, site1_cfg, site2_cfg)
The following example creates two configs from files, and one from the cli. It then combines them into a single object. Note how the port changes to 82, and how the users lists are combined.
End of explanation
"""


