Access dictionary fields by a conditional key - python

I have a class in python with plenty of methods that access multiple fields of a dictionary like this:
{
"a": {
"element_in_a": {
"element_in_a_id": "some_id"
}
},
"b": {
"element_in_b": {
"element_in_b_id": "some_id"
}
}
}
This dictionary was eventually changed to:
{
"a": {
"element_in_a": {
"element_in_a_id": "some_id"
},
"optional_element_in_a": {
"optional_element_in_a_id": "some_id"
}
},
"b": {
"element_in_b": {
"element_in_b_id": "some_id"
},
"optional_element_in_b": {
"optional_element_in_b_id": "some_id"
}
}
}
I have to modify the methods that access element_in_a or element_in_b of this dictionary such that, whenever optional_element_in_a exists in a, the methods must read only the optional elements everywhere (as a business rule, if the optional element exists in a, it also must exist in b), otherwise keep the old behaviour.
So far, as a quick solution, I've been adding these lines of code at the top of said methods
if "optional_element_in_a" in the_dict["a"]:
element_key_a = "optional_element_in_a"
element_key_b = "optional_element_in_b"
id_key_a = "optional_element_in_a_id"
id_key_b = "optional_element_in_b_id"
else:
element_key_a = "element_in_a"
element_key_b = "element_in_b"
id_key_a = "element_in_a_id"
id_key_b = "element_in_b_id"
and then read the elements using those keys I just defined.
This is obviously not a good solution, as I keep finding more and more methods needing this change.
I need to find the best pythonic way of having this logic in 1 place, so that all methods who need it can easily access it.
Btw, the_dict is not a class property/attribute. It is passed as argument to each method that operates on it, so I don't have access to it in __init__.
Thanks in advance.

if "optional_element_in_a" in the_dict["a"]:
element_key_a = "optional_element_in_a"
element_key_b = "optional_element_in_b"
id_key_a = "optional_element_in_a_id"
id_key_b = "optional_element_in_b_id"
else:
element_key_a = "element_in_a"
element_key_b = "element_in_b"
id_key_a = "element_in_a_id"
id_key_b = "element_in_b_id"
The main problem you have here is code repetition.
We can compress it by doing if on prefix, like such:
prefix = "optional_" if "optional_element_in_a" in the_dict["a"] else ""
element_key_a = prefix+"element_in_a"
element_key_b = prefix+"element_in_b"
id_key_a = prefix+"element_in_a_id"
id_key_b = prefix+"element_in_b_id"
Moreover, if you say "I've been adding these lines of code at the top of said methods", then it means even more repetition. And it's a sign you might need to define a function - either with this code above, some variation of it, or even something else.
Decide what you need: What is the structure of those keys you need? Do you need the key or the value? Do you need to extract always the same number of keys/values or different? Do you need to operate on the dict you have, or can you do a new dict with values you extracted (so that you don't care about whether the keys have "optional_" or not)?

I eventually went with a function that returns a namedtuple, with the keys as members of that tuple.
Like this:
def get_keys(a_elem):
has_optional_elem = "optional_element_in_a" in a_elem
Keys = namedtuple("Keys", ["element_key_a", "element_key_b", ...])
return Keys(
element_key_a = "optional_element_in_a" if has_optional_elem else "element_in_a",
element_key_b = "optional_element_in_b" if has_optional_elem else "element_in_b",
...
)
so that then in each method that needs these keys I can do simply
keys = get_keys(the_dict[a])
the_needed_a_elem = the_dict[a][keys.element_key_a]
the_needed_b_elem = the_dict[b][keys.element_key_b]
...
which I believe is simple enough for good readability when using the keys.

Related

How to get the rows of a database in the order in which they're displayed?

I have some Python code that retrieves the rows in a Notion database using the notion-client library. The code does manage to retrieve all the rows, but the order is wrong. I looked at the Sort object from the API reference, but was unable to figure out how to use it to return the rows in the exact order in which they're displayed on notion.so. Here's the snippet in question:
from notion_client import Client
notion = Client(auth=NOTION_API_TOKEN)
result = notion.databases.query(database_id='...')
for row in result['results']:
title = row['properties']['NAME_OF_PROPERTY']['title']
if len(title) == 0:
print('')
else:
print(title[0]['plain_text'])
What am I missing?
The Notion API does not support views in the current version, so it is not necessarily going to match the order you have it in unless you have applied a sort or filter that you can also apply via the API.
This is working as well as their documentation
const response = await notion.databases.query({
database_id: databaseId,
filter: {
or: [
{
property: 'In stock',
checkbox: {
equals: true,
},
},
{
property: 'Cost of next trip',
number: {
greater_than_or_equal_to: 2,
},
},
],
},
sorts: [
{
property: 'Last ordered',
direction: 'ascending',
},
],
});
Use the order argument to notion.databases.query(). This argument is a list of sort specifications, which are dictionaries.
result = notion.databases.query(
database_id = 'df4dfb3f-f36f-462d-ad6e-1ef29f1867eb',
sort = [{"property": "NAME_OF_PROPERTY", "direction": "ascending"}]
)
You can put multiple sort specifications in the list, the later ones will be used for rows that are equal in the preceding properties.

The best way to transform a response to a json format in the example

Appreciate if you could help me for the best way to transform a result into json as below.
We have a result like below, where we are getting an information on the employees and the companies. In the result, somehow, we are getting a enum like T, but not for all the properties.
[ {
"T.id":"Employee_11",
"T.category":"Employee",
"node_id":["11"]
},
{
"T.id":"Company_12",
"T.category":"Company",
"node_id":["12"],
"employeecount":800
},
{
"T.id":"id~Employee_11_to_Company_12",
"T.category":"WorksIn",
},
{
"T.id":"Employee_13",
"T.category":"Employee",
"node_id":["13"]
},
{
"T.id":"Parent_Company_14",
"T.category":"ParentCompany",
"node_id":["14"],
"employeecount":900,
"childcompany":"Company_12"
},
{
"T.id":"id~Employee_13_to_Parent_Company_14",
"T.category":"Contractorin",
}]
We need to transform this result into a different structure and grouping based on the category, if category in Employee, Company and ParentCompany, then it should be under the node_properties object, else, should be in the edge_properties. And also, apart from the common properties(property_id, property_category and node), different properties to be added if the category is company and parent company. There are few more logic also where we have to get the from and to properties of the edge object based on the 'to' . the expected response is,
"node_properties":[
{
"property_id":"Employee_11",
"property_category":"Employee",
"node":{node_id: "11"}
},
{
"property_id":"Company_12",
"property_category":"Company",
"node":{node_id: "12"},
"employeecount":800
},
{
"property_id":"Employee_13",
"property_category":"Employee",
"node":{node_id: "13"}
},
{
"property_id":"Company_14",
"property_category":"ParentCompany",
"node":{node_id: "14"},
"employeecount":900,
"childcompany":"Company_12"
}
],
"edge_properties":[
{
"from":"Employee_11",
"to":"Company_12",
"property_id":"Employee_11_to_Company_12",
},
{
"from":"Employee_13",
"to":"Parent_Company_14",
"property_id":"Employee_13_to_Parent_Company_14",
}
]
In java, we have used the enhanced for loop, switch etc. How we can write the code in the python to get the structure as above from the initial result structure. ( I am new to python), thank you in advance.
Regards
Here is a method that I quickly made, you can adjust it to your requirements. You can use regex or your own function to get the IDs of the edge_properties then assign it to an object like the way I did. I am not so sure of your full requirements but if that list that you gave is all the categories then this will be sufficient.
def transform(input_list):
node_properties = []
edge_properties = []
for input_obj in input_list:
# print(obj)
new_obj = {}
if input_obj['T.category'] == 'Employee' or input_obj['T.category'] == 'Company' or input_obj['T.category'] == 'ParentCompany':
new_obj['property_id'] = input_obj['T.id']
new_obj['property_category'] = input_obj['T.category']
new_obj['node'] = {input_obj['node_id'][0]}
if "employeecount" in input_obj:
new_obj['employeecount'] = input_obj['employeecount']
if "childcompany" in input_obj:
new_obj['childcompany'] = input_obj['childcompany']
node_properties.append(new_obj)
else: # You can do elif == to as well based on your requirements if there are other outliers
# You can use regex or whichever method here to split the string and add the values like above
edge_properties.append(new_obj)
return [node_properties, edge_properties]

Python: how to loop through all unknown depth of a tree?

I have a strategic issue of writing a program doing a job.
I have CSV files like:
Column1 Column 2
------- ----------
parent1 [child1, child2, child3]
parent2 [child4, child5, child6]
child1 [child7, child8]
child5 [child10, child33]
... ...
It is unknown how deep each element of those lists will be extended and I want to loop through them.
Code:
def make_parentClass(self):
for i in self.csv_rows_list:
self.parentClassList.append(parentClass(i))
# after first Parent
for i in self.parentClassList:
if i.children !=[]:
for child in i.children:
for z in self.parentClassList:
if str(child) == str(z.node_parent):
i.node_children.append(z)
self.parentClassList.remove(z)
class parentClass():
node_children = []
def __init__(self, the_list):
self.node_parent = the_list[0]
self.children = the_list[1]
The above code might be a solution if I will find a way to iterate. Let me see if you like the question and makes sense now.
Output:
My aim is to build up a treeview through another language but first I need to make this output in JSON format. So the output expected to be something like:
{
paren1:{'child1':{'child7':{}, 'child8':{}},
'child2': {},
'child3': {},
},
parent2: {
'child4':{},
'child5': {
'child10':{},
'child33':{}
},
'child6':{}
}
}
I would recommend a solution using two dictionaries. One nested one with the actually data structure you plan to convert to JSON, and one flat one that will let you actually find the keys. Since everything is a reference in Python, you can make sure that both dictionaries have the exact same values. Carefully modifying the flat dictionary will build your structure for you.
The following code assumes that you have already managed to split each line into a string parent and list children, containing values form the two columns.
json_dict = {}
flat_dict = {}
for parent, children in file_iterator():
if parent in flat_dict:
value = flat_dict[parent]
else:
value = {}
flat_dict[parent] = json_dict[parent] = value
for child in children:
flat_dict[child] = value[child] = {}
Running this produces json_dict like this:
{
'parent1': {
'child1': {
'child7': {},
'child8': {}
},
'child2': {},
'child3': {}
},
'parent2': {
'child4': {},
'child5': {
'child10': {},
'child33': {}
},
'child6': {}
}
}
Here is an IDEOne link to play with.

Use Python and JSON to recursively get all keys associated with a value

Giving data organized in JSON format (code example bellow) how can we get the path of keys and sub-keys associated with a given value?
i.e.
Giving an input "23314" we need to return a list with:
Fanerozoico, Cenozoico, Quaternario, Pleistocenico, Superior.
Since data is a json file, using python and json lib we had decoded it:
import json
def decode_crono(crono_file):
with open(crono_file) as json_file:
data = json.load(json_file)
Now on we do not know how to treat it in a way to get what we need.
We can access keys like this:
k = data["Fanerozoico"]["Cenozoico"]["Quaternario "]["Pleistocenico "].keys()
or values like this:
v= data["Fanerozoico"]["Cenozoico"]["Quaternario "]["Pleistocenico "]["Superior"].values()
but this is still far from what we need.
{
"Fanerozoico": {
"id": "20000",
"Cenozoico": {
"id": "23000",
"Quaternario": {
"id": "23300",
"Pleistocenico": {
"id": "23310",
"Superior": {
"id": "23314"
},
"Medio": {
"id": "23313"
},
"Calabriano": {
"id": "23312"
},
"Gelasiano": {
"id": "23311"
}
}
}
}
}
}
It's a little hard to understand exactly what you are after here, but it seems like for some reason you have a bunch of nested json and you want to search it for an id and return a list that represents the path down the json nesting. If so, the quick and easy path is to recurse on the dictionary (that you got from json.load) and collect the keys as you go. When you find an 'id' key that matches the id you are searching for you are done. Here is some code that does that:
def all_keys(search_dict, key_id):
def _all_keys(search_dict, key_id, keys=None):
if not keys:
keys = []
for i in search_dict:
if search_dict[i] == key_id:
return keys + [i]
if isinstance(search_dict[i], dict):
potential_keys = _all_keys(search_dict[i], key_id, keys + [i])
if 'id' in potential_keys:
keys = potential_keys
break
return keys
return _all_keys(search_dict, key_id)[:-1]
The reason for the nested function is to strip off the 'id' key that would otherwise be on the end of the list.
This is really just to give you an idea of what a solution might look like. Beware the python recursion limit!
Based on the assumption that you need the full dictionary path until a key named id has a particular value, here's a recursive solution that iterates the whole dict. Bear in mind that:
The code is not optimized at all
For huge json objects it might yield StackOverflow :)
It will stop at first encountered value found (in theory there shouldn't be more than 1 if the json is semantically correct)
The code:
import json
from types import DictType
SEARCH_KEY_NAME = "id"
FOUND_FLAG = ()
CRONO_FILE = "a.jsn"
def decode_crono(crono_file):
with open(crono_file) as json_file:
return json.load(json_file)
def traverse_dict(dict_obj, value):
for key in dict_obj:
key_obj = dict_obj[key]
if key == SEARCH_KEY_NAME and key_obj == value:
return FOUND_FLAG
elif isinstance(key_obj, DictType):
inner = traverse_dict(key_obj, value)
if inner is not None:
return (key,) + inner
return None
if __name__ == "__main__":
value = "23314"
json_dict = decode_crono(CRONO_FILE)
result = traverse_dict(json_dict, value)
print result

Python modify JSON file based on input

I have a JSON configuration file which looks something like this:
{
"generic": {
"loglevel": 2,
...
},
"clients": [
{
"type": "foo",
"bar": {
"bar_1": 0.7,
"bar_2": 0.95
},
...
},
{
"type": "foo",
...
}
]
}
I can modify the contents and store the modified version of it using:
import json
with open("sample.cfg", "r") as config_file:
config = json.load(config_file)
config["clients"][0]["bar"]["bar_1"] = 100
with open("modified.cfg", "w") as config_file:
config_file.write(json.dumps(config))
But I would like to modify the file based on some input. Let's say the input is a string changestring:
changestring = 'clients,0,bar,bar_1:1,2,3'
keyval=changestring.split(':')
keys = keyval[0].split(',')
vals = keyval[1].split(',')
But now I don't know how to use the keys in order to access the config path. Is this actually the right way to do this? Or maybe there is a different way to handle it? Thanks.
This is certainly a viable solution. It will work, but if actual users supply the change string, you probably want some way to ensure that the string is valid.
You also probably want to distinguish between integer indices and string indices!
Assuming your code, you could do the following:
import json
with open("sample.cfg", "r") as config_file:
config = json.load(config_file)
changestring = 'clients,0,bar,bar_1:1,2,3'
keyval = changestring.split(':')
keys = keyval[0].split(',')
vals = keyval[1].split(',')
# Move our "pointer"
obj = config
for key in keys[:-1]:
try: obj = obj[key]
except TypeError:
# Probably want a more general solution
obj = obj[int(key)]
# Update value
obj[keys[-1]] = vals
with open("modified.cfg", "w") as config_file:
config_file.write(json.dumps(config))
Python has aliasing, so by updating a variable down the "index" tree (but not all the way), we can get a mutable copy of what we actually want to modify. This should work for any "depth" of keys supplied. I tested this on python2.7.
if i am interpreting your question right
your keys and vals list will look like this
keys = ["clients", "0", "bar", "bar_1"]
vals = ["1", "2", "3"]
so to update the value of config["clients"][0]["bar"]["bar_1"] you can do like this
config[keys[0]][keys[1]][keys[2]][keys[3]] = vals[index]
index will be the index of value in vals list with which you want to update your json

Categories

Resources