===========================================
Sparc YAML Based Application Configurations
===========================================
Many Sparc applications require some amount of run time configuration.  An
example is an application that logs into an external API service, therefore
requires a set of authentication parameters.

In addition, Sparc leverages the Zope Component Architecture as its core
framework to allow for the creation of small, re-usable components that
can be looked up at run time within the component registry.  Many times, these
components will require runtime configuration information to operate 
effectively.

Sparc.config.yaml provides utilities that allow easy access to 
run time information stored in YAML configuration files.  The expected
content of these YAML files will be dependent on the application that is
leveraging them.

Here are some common use case implementations.

Let's start with a basic key/value YAML configuration
>>> yaml_config = """\
... My first entry: my first value
... My second entry: my second value
... """

sparc.config.yaml provides a utility that can easily return a Sparc Config
Container based on the above key/value pairs.
>>> from zope import component
>>> from sparc.config import yaml
>>> config = component.getUtility(yaml.ISparcYamlConfigContainers).first(yaml_config)
>>> config.mapping()['My first entry'] == 'my first value'
True

The returned config container will also provide 
sparc.config.IConfigContainer
>>> from sparc.config import IConfigContainer
>>> IConfigContainer.providedBy(config)
True

We can also iterate on multi-document yaml configs
>>> yaml_config = """\
... ---
... My first entry: my first value
... My second entry: my second value
... ---
... 3: a
... 4: b
... ---
... """
>>> configs = list(component.getUtility(yaml.ISparcYamlConfigContainers).containers(yaml_config))
>>> configs[0].mapping()['My first entry'] == 'my first value'
True
>>> configs[1].mapping()[3] == 'a'
True

Here's a slightly more complex config that leverages references
>>> yaml_config = """\
... - EntryType1: &type1
...    dict1: value 1
...    dict2: value 2
... - EntryType1: &type2
...    dict3: value 3
...    dict4: value 4
... - EntryType2:
...    EntryType1: *type2
...    EntryType3:
...     field1: !!python/tuple [data1, data2] # creating python tuples isn't a great idea, but is possible
... """
>>> config = component.getUtility(yaml.ISparcYamlConfigContainers).first(yaml_config)
>>> config.mapping().get_value('EntryType2', 'EntryType1', 'dict3') == 'value 3'
True

We can also leverage the Jinja2 template system for the yaml configs.
>>> yaml_config = """\
... - EntryType1:
...    dict1: {{var1}}
... """
>>> vars = {'var1': 'value A'}
>>> config = component.getUtility(yaml.ISparcYamlConfigContainers).first(yaml_config, render_context=vars)
>>> config.mapping().get_value('EntryType1', 'dict1') == 'value A'
True

Complex/long yaml files can be broken up into smaller files
>>> import os, os.path
>>> import sparc.config.yaml.tests
>>> yaml_config = os.path.dirname(sparc.config.yaml.tests.__file__) + '/config/test.yaml'
>>> config = component.getUtility(yaml.ISparcYamlConfigContainers).first(yaml_config)
>>> mapping = config.mapping()
>>> config.mapping().get_value('test1', 'key1') == 'value1'
True

Invalid yaml inputs raise ValueError
>>> component.getUtility(yaml.ISparcYamlConfigContainers).first(u"bad_string")
Traceback (most recent call last):
 ...
ValueError: expected yaml_config to contain valid yaml file path or string: bad_string
>>> try:
...     component.getUtility(yaml.ISparcYamlConfigContainers).first(
...         os.path.dirname(sparc.config.__file__) + '/configure.zcml')
... except ValueError:
...     print("invalid file!")
invalid file!
