Python: Accessing YAML values using "dot notation"
<p>I'm using a YAML configuration file. So this is the code to load my config in Python:</p>

<pre><code>import os
import yaml
with open('./config.yml') as file:
    config = yaml.safe_load(file)
</code></pre>

<p>This code actually creates a dictionary. Now the problem is that in order to access the values I need to use tons of brackets.</p>

<p>YAML:</p>

<pre><code>mysql:
    user:
        pass: secret
</code></pre>

<p>Python:</p>

<pre><code>import os
import yaml
with open('./config.yml') as file:
    config = yaml.safe_load(file)
print(config['mysql']['user']['pass']) # &lt;--
</code></pre>

<p>I'd prefer something like that (dot notation):</p>

<pre><code>config('mysql.user.pass')
</code></pre>

<p>So, my idea is to utilize the PyStache render() interface.</p>

<pre><code>import os
import yaml
with open('./config.yml') as file:
    config = yaml.safe_load(file)

import pystache
def get_config_value( yml_path, config ):
    return pystache.render('{{' + yml_path + '}}', config)

get_config_value('mysql.user.pass', config)
</code></pre>

<p>Would that be a "good" solution? If not, what would be a better alternative?</p>

<p><strong>Additional question [Solved]</strong></p>

<p>I've decided to use Ilja EverilÃ¤'s solution. But now I've got an additional question: How would you create a wrapper Config class around DotConf?</p>

<p>The following code doesn't work but I hope you get the idea what I'm trying to do:</p>

<pre><code>class Config( DotDict ):
    def __init__( self ):
        with open('./config.yml') as file:
            DotDict.__init__(yaml.safe_load(file))

config = Config()
print(config.django.admin.user)
</code></pre>

<p>Error: </p>

<pre><code>AttributeError: 'super' object has no attribute '__getattr__'
</code></pre>

<p><strong>Solution</strong></p>

<p>You just need to pass <code>self</code> to the constructor of the super class.</p>

<pre><code>DotDict.__init__(self, yaml.safe_load(file))
</code></pre>

<p><strong>Even better soltution (Ilja EverilÃ¤)</strong></p>

<pre><code>super().__init__(yaml.safe_load(file))
</code></pre>

<p>On the one hand your example takes the right approach by using <code>get_config_value('mysql.user.pass', config)</code> instead of solving the dotted access with attributes. I am not sure
if you realised that on purpose you were not trying to do the more intuitive:</p>

<p>print(config.mysql.user.pass)
which you can't get to work, even when overloading <strong>getattr</strong> as <code>pass</code> is a python language element.</p>

<p>However your example describes only a very restricted subset of YAML files as it doens't involve any sequence collections, nor any complex keys for</p>

<p>If you want to cover more than the tiny subset you can e.g. extend the powerful round-trip capable objects of <code>ruamel.yaml</code>:Â¹</p>

<pre><code>def mapping_string_access(self, s, delimiter=None, key_delim=None):
    def p(v):
        try:
            v = int(v)
        except:
            pass
        return v
       # possible extend for primitives like float, datetime, booleans, etc.

    if delimiter is None:
        delimiter = '.'
    if key_delim is None:
        key_delim = ','
    try:
        key, rest = s.split(delimiter, 1)
    except ValueError:
        key, rest = s, None
    if key_delim in key:
        key = tuple((p(key) for key in key.split(key_delim)))
    else:
        key = p(key)
    if rest is None:
        return self[key]
    return self[key].string_access(rest, delimiter, key_delim)

ruamel.yaml.comments.CommentedMap.string_access = mapping_string_access


def sequence_string_access(self, s, delimiter=None, key_delim=None):
    if delimiter is None:
        delimiter = '.'
    try:
        key, rest = s.split(delimiter, 1)
    except ValueError:
        key, rest = s, None
    key = int(key)
    if rest is None:
        return self[key]
    return self[key].string_access(rest, delimiter, key_delim)

ruamel.yaml.comments.CommentedSeq.string_access = sequence_string_access
</code></pre>

<p>Once that is set up you are can run the following:</p>

<pre><code>yaml_str = """\
mysql:
    user:
        pass: secret
    list: [a: 1, b: 2, c: 3]
    [2016, 9, 14]: some date
    42: some answer
"""

config = ruamel.yaml.round_trip_load(yaml_str)

def get_config_value(path, data, **kw):
    return data.string_access(path, **kw)

print(get_config_value('mysql.user.pass', config))
print(get_config_value('mysql:user:pass', config, delimiter=":"))
print(get_config_value('mysql.list.1.b', config))
print(get_config_value('mysql.2016,9,14', config))
print(config.string_access('mysql.42'))
</code></pre>

<p>giving:</p>

<pre><code>secret
secret
2
some date
some answer
</code></pre>

<p>showing that with a bit more forethought and very little extra work you can have flexible dotted access to many to a vast range of YAML files, and not just those consisting of recursive mappings with string scalars as keys.</p>

<ol>
<li>As shown you can directly call <code>config.string_access(</code>mysql.user.pass<code>) instead of defining and using</code>get_config_value()`</li>
<li>this works with strings and integers as mapping keys, but can be easily extended to support other key types (boolean, date, date-time).</li>
</ol>

<hr>

<p>Â¹ <sub>This was done using <a href="https://pypi.python.org/pypi/ruamel.yaml" rel="nofollow">ruamel.yaml</a> a YAML 1.2 parser, of which I am the author.</sub></p>

python python-3.x yaml