invoke dictionary fields dynamically by name - python

I have a function like this that allows to me to get the dictionary key like get_key(dictionary, 'path.to.key')
#staticmethod
def get_key(cont, key, default=None):
def get_key_inner(parent, keys, default):
if not keys:
return default
cur = keys.pop(0)
if cur not in parent:
return default
if not keys:
return parent[cur]
if isinstance(parent[cur], dict):
return get_key_inner(parent[cur], keys, default)
return default
return get_key_inner(cont, key.split('.'), default)
I want to implement some kind of object wrapper that would accept dictionary as a parameter and then would delegate the calls by name using the dictionary keys. Something like this
class Wrapper:
def __init__(self, dict_source):
self.source = dict_source
leon_dict = {
'name': 'Leon',
'job': {
'title': 'engineer'
}
}
leon = Wrapper(leon_dict)
assert 'engineer' == leon.job.title # the same as leon['job']['title']
Can I achieve that?

class Wrapper:
def __init__(self, dict_source):
self.source = dict_source
def __getattr__(self, key):
try:
data = self.source[key]
if isinstance(data, dict):
return Wrapper(data)
return data
except KeyError as e:
print(e)
raise AttributeError()
leon_dict = {
'name': 'Leon',
'job': {
'title': 'engineer'
}
}
leon = Wrapper(leon_dict)
print(leon.job.title)

Related

Recursively creates dataclasses based in nested dictionary

I have a dataclass called Config that is created through the properties and values of a dictionary. Since this dictionary can have nested dictionaries, i would like to make nested dictionaries as Config objects. Here is an example:
## Dummy example of a config dict
data = {
'a' : 1,
'b' : [2,2,2],
'c': {
'c_1' : 3.1
}
}
final_config = create_config(data)
# Expected result
Config(a=1, b=[2,2,2], c=Config(c_1=3.1) )
Here is what i've came up, using dataclasses.make_dataclass:
def _Config(params_dict):
config = make_dataclass('Config', params_dict.keys())
return config(**params_dict)
def get_inner_dict(d):
for _, v in d.items():
if isinstance(v, dict):
return get_inner_dict(v)
else:
return _Config(**d)
Unfortunately, this doesn't work because the recursion will try to create a dataclass object when it finds a single value. I feel like i'm in the right way, but couldn't figure out what needs to change.
It looks like you (technically) don't need to use dataclasses or make_dataclass in this scenario.
You can implement a custom class with a __dict__ update approach as mentioned by #Stef. Check out the following example:
from __future__ import annotations
## Dummy example of a config dict
data = {
'a': 1,
'b': [2, 2, 2],
'c': {
'c_1': 3.1
},
'd': [
1,
'2',
{'k1': 'v1'}
]
}
_CONTAINER_TYPES = (dict, list)
class Config:
def __init__(self, **kwargs):
self.__dict__ = kwargs
#classmethod
def create(cls, data: dict | list) -> Config | list:
if isinstance(data, list):
return [cls.create(e) if isinstance(e, _CONTAINER_TYPES) else e
for e in data]
new_data = {
k: cls.create(v) if isinstance(v, _CONTAINER_TYPES) else v
for k, v in data.items()
}
return cls(**new_data)
def __repr__(self):
return f"Config({', '.join([f'{name}={val!r}' for name, val in self.__dict__.items()])})"
final_config = Config.create(data)
print(final_config)
# Prints:
# Config(a=1, b=[2, 2, 2], c=Config(c_1=3.1), d=[1, '2', Config(k1='v1')])

json serialization of a list of objects of a custom class

I have a song class, which holds the attributes to a song, and it is a custom class. I also have a list of songs in a list called track list. When I try to json.dump the list, I get an error that says :
TypeError: Object of type 'Song' is not JSON serializable
How would I go about converting this list of songs to json?
Here is the additional relevant code that returns the error:
class Song:
def __init__(self, sname, sartist, coverart, albname, albartist, spotid):
self.sname = sname
self.sartist = sartist
self.coverart = coverart
self.albname = albname
self.albartist = albartist
self.spotid = spotid
tracklist = createDict(tracks) ##creates the list of songs, works fine
jsontracks = json.dumps(tracklist)
pp.pprint(jsontracks)
Thanks
I've solved this by adding an encode() method to the class:
def encode(self):
return self.__dict__
and adding some arguments to json.dumps:
jsontracks = json.dumps(tracklist, default=lambda o: o.encode(), indent=4)
This will "crawl" down your class tree (if you have any child classes) and encode every object as a json list/object automatically. This should work with just about any class and is fast to type. You may also want to control which class parameters get encoded with something like:
def encode(self):
return {'name': self.name,
'code': self.code,
'amount': self.amount,
'minimum': self.minimum,
'maximum': self.maximum}
or a little bit faster to edit (if you're lazy like me):
def encode(self):
encoded_items = ['name', 'code', 'batch_size', 'cost',
'unit', 'ingredients', 'nutrients']
return {k: v for k, v in self.__dict__.items() if k in encoded_items}
full code:
import json
class Song:
def __init__(self, sname, sartist, coverart, albname, albartist, spotid):
self.sname = sname
self.sartist = sartist
self.coverart = coverart
self.albname = albname
self.albartist = albartist
self.spotid = spotid
def encode(self):
return self.__dict__
tracklist = [
Song('Imagine', 'John Lennon', None, None, None, None),
Song('Hey Jude', 'The Beatles', None, None, None, None),
Song('(I Can\'t Get No) Satisfaction', 'The Rolling Stones', None, None, None, None),
]
jsontracks = json.dumps(tracklist, default=lambda o: o.encode(), indent=4)
print(jsontracks)
output:
[
{
"sname": "Imagine",
"sartist": "John Lennon",
"coverart": null,
"albname": null,
"albartist": null,
"spotid": null
},
{
"sname": "Hey Jude",
"sartist": "The Beatles",
"coverart": null,
"albname": null,
"albartist": null,
"spotid": null
},
{
"sname": "(I Can't Get No) Satisfaction",
"sartist": "The Rolling Stones",
"coverart": null,
"albname": null,
"albartist": null,
"spotid": null
}
]

How can I deal with recursion problem in such nested dictionary?

You've recieved a serialized JSON object from an API and have deserialized it using the standard library's json library. The object represents your geneology from a given ancestor downward. Assuming your name is Sally and your given ancestor is Janet, your geneology object would be as follows:
geneology_object = {
'husband': 'Craig',
'wife': 'Janet',
'children': {
'Chris': {
'husband': 'Chris',
'wife': 'Jesse',
'children': {
'Rebecca': {
'husband': 'Doug',
'wife': 'Rebecca',
}
}
},
'Wonda': {
'husband': 'Kevin',
'wife': 'Wonda',
'children': {
'Sally': {}
}
}
}
}
Write a function with the signature get_generations_down(geneology_object, search_name, generations=0) to recursively search for the number of generations between search_name and the eldest ancestor. If the name is not found, a NameNotFoundError should be raised by the recursive function.
Assuming the geneology object above, your function should behave as so:
get_generations_down(geneology_object, 'Chris') 1
get_generations_down(geneology_object, 'Sally') 2
My code is here,it doesn't work For 'Sally'
class NameNotFoundError(Exception):
pass
count=0
def get_generations_down(geneology_object, search_name, generations=0):
global count
for i in geneology_object:
if isinstance(geneology_object[i],dict):
if i=='children':
generations+=1
if search_name in geneology_object[i]:
count+=1
break
return
get_generations_down(geneology_object[i],search_name,generations)
elif i == search_name:
count+=1
break
elif geneology_object[i]== search_name:
count+=1
break
print(geneology_object)
if count==0:
raise NameNotFoundError
return generations
#raise NotImplementedError()
get_generations_down(geneology_object, 'Sally')
Your code doesn't seem to be a recursive solution.
Here's how I worked it out,
class NameNotFoundError (Exception):
pass
def get_generations_down(geneology_object, search_name, generations=0):
found=False
if ('husband' in geneology_object) and ('wife' in geneology_object) and (search_name == geneology_object['husband'] or search_name == geneology_object['wife']):
return True,generations
if 'children' in geneology_object:
children=(geneology_object['children'])
generations+=1
if search_name in children:
return True,generations
else:
for child in children:
if found:
break
found,generations=get_generations_down(geneology_object['children'][child],search_name,generations)
else:
generations-=1
return found,generations
result = get_generations_down(geneology_object, 'Sally')
if(result[0]):
print (result[1])
else:
raise NameNotFoundError()

Recursively create same class during instantiation

I have a json config that I want to create a dict from. Because json configs are recursive, any time I see a json value that is an array I want to recursively iterate on it. However this is not doing what I want it to do.
class FieldHandler():
formfields = {}
def __init__(self, fields):
for field in fields:
options = self.get_options(field)
f = getattr(self, "create_field_for_" +
field['type'])(field, options)
self.formfields[field['name']] = f
def get_options(self, field):
options = {}
options['label'] = field['name']
options['help_text'] = field.get("help_text", None)
options['required'] = bool(field.get("required", 0))
return options
def create_field_for_string(self, field, options):
options['max_length'] = int(field.get("max_length", "20"))
return django.forms.CharField(**options)
def create_field_for_int(self, field, options):
options['max_value'] = int(field.get("max_value", "999999999"))
options['min_value'] = int(field.get("min_value", "-999999999"))
return django.forms.IntegerField(**options)
def create_field_for_array(self, field, options):
fh = FieldHandler(field['elements'])
return fh
and instantiating:
fh = FieldHandler([
{'type': 'string', 'name': 'position'},
{'type': 'array', 'name': 'calendar', 'elements': [
{'type': 'string', 'name': 'country'},
{'type': 'string', 'name': 'url'},
]},
{'type': 'int', 'name': 'maxSize'}
])
I expect to get a dict like so:
{
'position': <django.forms.fields.CharField object at 0x10b57af50>,
'calendar': <__main__.FieldHandler instance at 0x10b57c680>,
'maxSize': <django.forms.fields.IntegerField object at 0x10b58e050>,
}
Where calendar itself is expected to be:
{
'url': <django.forms.fields.CharField object at 0x10b58e150>,
'country': <django.forms.fields.CharField object at 0x10b58e0d0>
}
Instead I get:
{
'url': <django.forms.fields.CharField object at 0x10b58e150>,
'position': <django.forms.fields.CharField object at 0x10b57af50>,
'calendar': <__main__.FieldHandler instance at 0x10b57c680>,
'maxSize': <django.forms.fields.IntegerField object at 0x10b58e050>,
'country': <django.forms.fields.CharField object at 0x10b58e0d0>
}
What am I doing wrong? Why are the position and country parameters being set on my global FieldHandler?
formfields is a class attribute that is shared among all instances. Make it an instance attribute instead:
class FieldHandler():
def __init__(self, fields):
self.formfields = {}
# ...
Now, all FieldHandler instances have their own formfields, with only the "inner" calendar handler having the country and url (not position assuming that was a typo) fields.

How do I return a dict from a function?

I have a small piece of code :
def extract_nodes():
for i in json.load(open('/home/ubuntu/slcakbot_openNMS/CLEAR/out.txt'))["node"]:
try:
socket.inet_aton(i["label"])
print(i["label"])
print(i["id"])
#return { 'ip': i["label"], 'id': i["id"]} # i need to return these values
except Exception as e:
pass
I need to create a dict and return it to the calling function, i am not sure how do i create a dict and return from here. Also once returned how do i use the dictionary value
There may be more than one values for the key "id" and "label",so you should consider use list .
Here is my code
def extract_nodes():
labels = []
ids = []
results = {}
for i in json.load(open('/home/ubuntu/slcakbot_openNMS/CLEAR/out.txt'))["node"]:
try:
socket.inet_aton(i["label"])
print(i["label"])
labels.append(i["label"])
print(i["id"])
ids.append(i["id"])
#return { 'ip': i["label"], 'id': i["id"]} # i need to return these values
except Exception as e:
pass
results['ip']=labels
results['id']=ids
return results
I hope it can work :)
You could use a generator, but I'm guessing you are new to python and this will be simpler:
def extract_nodes():
return_data = dict()
for node_datum in json.load(open('/home/ubuntu/slcakbot_openNMS/CLEAR/out.txt'))["node"]:
try:
socket.inet_aton(node_datum["label"])
return_data[node_datum["id"]] = { 'ip': node_datum["label"], 'id': node_datum["id"]}
print(node_datum["label"])
print(node_datum["id"])
#return { 'ip': node_datum["label"], 'id': node_datum["id"]} # i need to return these values
except Exception as err:
print err
pass
return return_data
As for using it,
node_data = extract_nodes()
for key, node_details in node_data.items():
print node_details['ip'], node_details['id']
def extract_nodes():
to_return_dict = dict()
for i in json.load(open('/home/ubuntu/slcakbot_openNMS/CLEAR/out.txt'))["node"]:
try:
socket.inet_aton(i["label"])
to_return_dict[i['id']] = i['label']
print(i["label"])
print(i["id"])
#return { 'ip': i["label"], 'id': i["id"]} # i need to return these values
except Exception as e:
pass
return to_return_dict
This should do it.... Let me know if it works!
Edit:
As for how to use it:
id_label_dict = extract_nodes()
print(id_label_dict['ip']) # should print the label associated with 'ip'

Categories

Resources