mardi 5 mai 2015

avoiding 'exec' while updating dict with complex structure

I have a JSON file with a complex structure. This file stores categories of some sort. Each category and sub-category has its 'ID' like

2 drinks
2.1 drinks coffee
2.1.1 drinks coffee instant 
2.1.2 drinks coffee real 
2.2 drinks tea 
2.3 drinks water  

For example, suppose I want to add a new category, 'alcohol'. The proper place for it us as a subcategory of 'drinks'. So I type 2 press Enter, type alcohol press Enter, and the program creates a new dictionary with a key 'drinks' like this: nd={"drinks":{"alcohol":""}} Then it updates the existing large dictionary with that new one.

As I am new in Python and in coding at all, I've done it in a very ugly way by making that new dictionary using exec because I lack skills. Also the code doesn't work properly, for example if I want to add not to existing dictionary like drinks but to just a value of key like adding 'soda' to 'water' it will have an error. I might make a workaround for it but in the same messy way. So I just ask for help and explanation how to do it in pythonic way.

To be concise: I have a set of keys that form the 'path' for new value like:

communication | mobile | verizon | calls | out | roaming | Other country

Using this 'path' I must upgrade a dict with new value, if last step in 'path' is that value - convert it to dict and then add new value.

here goes sample of JSON file:

{
"food": {
    "dairy": {
        "cheese": "Gauda",
        "milk": {
            "origin": "place",
            "brand": "name"
        }
    }
},
"communication": {
    "mobile": {
        "life": {
            "txt": "",
            "calls": ""
        },
        "vodafone": {
            "txt": "",
            "subscr": "",
            "mms": "",
            "calls": {
                "in": {
                    "home": "",
                    "roaming": ""
                },
                "out": {
                    "home": "",
                    "roaming": ""
                }
            },
            "internet": ""
        },
        "verizon": {
            "txt": "",
            "subscr": "",
            "mms": "",
            "calls": {
                "in": {
                    "home": "",
                    "roaming": ""
                },
                "out": {
                    "home": "500 min",
                    "roaming": "Other country",
                    "reached": ""
                }
            },
            "internet": "1Gb"
        }
    },
    "internet": "SomeProviderName"
},
"taxes": "",
"drinks": {
    "water": "",
    "tea": "",
    "coffee": {
        "real": "",
        "instant": ""
    }}}

Here's the code by itself:

#!/usr/bin/env python -tt
# -*- coding: utf-8 -*-
import json
import collections

def walk_dict(d, key=None, parent=None):
    res = {}
    for i, e in enumerate(sorted(d), 1):
        k = (key    + "." + str(i)) if key    else str(i)
        p = (parent + " | " +     e ) if parent else     e
        if isinstance(d[e], dict):
            res[k] = p
            res.update(walk_dict(d[e], k, p))
        else:
            res[k] = p + " | " + str(d[e])
    return res

with open('new_cat.json') as f:
    cat_data = json.load(f)
    new_data = walk_dict(cat_data)
    for v in sorted(new_data):
        print v, new_data[v]

def find_cat(d,new_cat):
    ex_str="{"
    for i in d:
        print i
        ex_str+="\""+i+"\":{"
    ex_str+="\""+new_cat+"\":\"\""+"}"*(len(d)+1)
    print ex_str
    return ex_str

inp=raw_input("pick one (numbers) or \'ENTER\' for new in root\n")
if inp=='':
    new_cat=raw_input("input new category\n")
    upd=find_cat([],new_cat)
else:
    print new_data[inp]
    new_cat=raw_input("input new category\n")
    keys=new_data[inp].split(" | ")
    upd=find_cat(keys,new_cat)

exec "nd="+str(upd)

def update(d, u):
    for k, v in u.iteritems():
        if isinstance(v, collections.Mapping):
            r = update(d.get(k, {}), v)
            d[k] = r
        else:
            d[k] = u[k]
    return d
update(cat_data,nd)   
for i in cat_data:
    print i, cat_data[i] 
f=open("new_cat.json",'w')
json.dump(cat_data,f,indent=4,encoding='utf-8',ensure_ascii=False,sort_keys=False,separators=(',', ': '))
f.close()

Aucun commentaire:

Enregistrer un commentaire