red888
red888

Reputation: 31570

how can I create nested dictionary keys and assign them values from a list of namespaced key value pairs?

I have env vars that looks like this:

CONFIG-SOMEKEY-SOMEOTHERKEY = val345
CONFIG-SOMEKEY-SOMEOTHEROTHERKEY = val678
CONFIG-ANOTHERKEY = val222

I want to create a dictionary out of them that would look like:

{
  'SOMEKEY': {
    'SOMEOTHERKEY': 'val3242',
    'SOMEOTHEROTHERKEY': 'val678'
  }
  'ANOTHERKEY': 'val222'
}

"CONFIG-" is a prefix to denote which vars this should be done with- so I can filter them easily like this:

config_fields = [i for i in os.environ if i.startswith("CONFIG-")]

But I'm unsure of how to loop over the string, split on "-" and build a dict.

While looping I was thinking I could check if its the last item and assign the value but how would it know the full path of keys it's on?

I suspect this is a job for recursion I'm just now sure exactly how to implement it

Upvotes: 2

Views: 1098

Answers (3)

Dani Mesejo
Dani Mesejo

Reputation: 61910

You could do:

data = ['CONFIG-SOMEKEY-SOMEOTHERKEY = val345',
        'CONFIG-SOMEKEY-SOMEOTHEROTHERKEY = val678',
        'CONFIG-ANOTHERKEY = val222']

result = {}
for e in data:
    key, value = e.split(" = ")  # split into key and value
    path = key.split("-")  # split the key into parts
    ref = result
    for part in path[1:-1]:
        ref[part] = part in ref and ref[part] or {}
        ref = ref[part]
    ref[path[-1]] = value  # take the last part of key and set the value

print(result)

Output

{'SOMEKEY': {'SOMEOTHERKEY': 'val345', 'SOMEOTHEROTHERKEY': 'val678'}, 'ANOTHERKEY': 'val222'}

This part:

ref = result
for part in path[1:-1]:
    ref[part] = part in ref and ref[part] or {}
    ref = ref[part]
ref[path[-1]] = value

will create the nested dictionaries, is equivalent to:

for part in path[1:-1]:
    if part not in ref:
        ref[part] = {}
    ref = ref[part]

So if the part is in the dictionary you set ref as the value corresponding to part otherwise you create a new dictionary.

Upvotes: 2

gilch
gilch

Reputation: 11681

You can use the assoc_in function from toolz. Split the name on - and slice off the prefix.

import os

from toolz.dictoolz import assoc_in

CONFIG={}

for k, v in os.environ.items():
    if k.startswith("CONFIG-"):
        assoc_in(CONFIG, k.split('-')[1:], v)

If you don't want to add a dependency, you can see the implementation of assoc_in here. A simpler substitute might be something like

def assoc_in(d, ks, v):
    for k in ks[:-1]:
        d = d.setdefault(k, {})
    d[ks[-1]] = v

This uses the .setdefault() method to get the nested dicts, which will add a new one if it doesn't exist yet.

Upvotes: 2

Patrick Artner
Patrick Artner

Reputation: 51673

You can get your environment variables like so:

import os

text = [f"{k} = {v}" for k,v in os.environ.items() if k.startswith("CONFIG-")]
print(env)

(inspired by How to access environment variable values? - especially this answer)

Then you can use dicts to iterativly splitting your values:

text = """CONFIG-SOMEKEY-SOMEOTHERKEY = val345
CONFIG-SOMEKEY-SOMEOTHEROTHERKEY = val678
CONFIG-ANOTHERKEY = val222"""

text = text.split("\n")

d = {}

curr_d = d
for part in text:
    while "-" in part:
        a, b = part.split("-",1)
        if '-' in b:
            curr_d [a] = curr_d.get(a,{})
            curr_d = curr_d[a]
        part = b
    a, b = part.split("=",1)
    curr_d[a] = b

    curr_d = d

print(d)

Output:

{'CONFIG': {'SOMEOTHERKEY ': ' val345', 
            'SOMEOTHEROTHERKEY ': ' val678'}, 
 'ANOTHERKEY ': ' val222'}

Upvotes: 1

Related Questions