Read and parse logic in dictionary format - python

I'm trying to store some logic in dictionary format, then have a Python parser that read and evaluate, for example, in my dictionary I have
rule_dict = {
'logic': 'equal',
'value': '1'
}
And my parser is defined and used like below
def test_data_against_rule_dict(rule_dict, data):
if rule_dict['logic'] == 'equal'
return rule_dict['value'] == data
elif ...
data = 1
result = test_data_against_rule_dict(rule_dict, data)
# result = True
I need to handle other logic like not equal, is in given list, not in given list etc. I can define the name of these logic myself and write the parser for each logic myself, but I wonder if there's existing standard or Python library I can use directly.
Adding another example to explain another one of my use cases, e.g. for the not in given list example:
rule_dict = {
'logic': 'exclude',
'value': '1'
}
def test_data_against_rule_dict(rule_dict, data):
if rule_dict['logic'] == 'equal'
return rule_dict['value'] == data
elif rule_dict['logic'] == 'exclude'
return rule_dict['value'] not in data
...
data = [1,2,3]
result = test_data_against_rule_dict(rule_dict, data)
# result = False

I don't know any library to solve this task. However, you could use the eval method for less code (but the execution will be slower):
Change your rule_dict to the code that should be executed, like this:
rule = '1 not in data'
Your check becomes simple then:
return eval(rule)

Related

Is there a better way to parse a python dictionary?

I have a json dictionary and I need to check the values of the data and see if there is a match. I am using multiple if statements and the in operator like so:
"SomeData":
{
"IsTrue": true,
"ExtraData":
{
"MyID": "1223"
}
}
json_data = MYAPI.get_json()
if 'SomeData' in json_data:
some_data = json_data['SomeData']
if 'IsTrue' in some_data:
if some_data['IsTrue'] is True:
if 'ExtraData' in some_data:
if 'MyID' in some_data['ExtraData']:
if some_data['ExtraData']['MyID'] == "1234":
is_a_match = True
break
I know that in python3 the in operator should be used, but I am thinking there must be a better way than using multiple if statements like I am using.
Is there a better way to parse json data like this?
Yes, you can assume that the keys are present, but catch a KeyError if they aren't.
try:
some_data = json_data['SomeData']
is_a_match = (
some_data['IsTrue'] is True and
some_data['ExtraData']['MyID'] == "1234"
)
except KeyError:
is_a_match = False
This style is called easier to ask for forgiveness than permission (EAFP) and it's used a lot in Python. The alternative is look before you leap (LBYL), which you use in your solution.
I would suggest writing a path function to access values in your nested dictionary. Something along the lines of this (pseudocode):
def get_path_value(json_dict, path):
"""
json_dict - dictionary with json keys and values
path - list of key sequences, e.g. ['SomeData', 'IsTrue']
you can make this a helper function and use an entry point that
splits paths, e.g. "SomeData/IsTrue"
"""
if len(path) == 1:
# last tag, base case
return json_dict[path[0]]
else:
return get_path_value(json_dict[path[0]], path[1:])
Add try/catch if you want something other than bad key, but this will let you navigat the dictionary a little more eloquently. Then you have things like:
if get_path_value(json_dict, ["SomeData", "IsTrue"]) == True and ...
You could even write a nice little class to wrap this all up, e.g. json["SomeData/IsTrue"] == True
Best of luck,
Marie

How to find url multiple parameters and change the value?

How i can change multiple parameters value in this url: https://google.com/?test=sadsad&again=tesss&dadasd=asdaas
You can see my code: i can just change 2 value!
This is the response https://google.com/?test=aaaaa&dadasd=howwww
again parameter not in the response! how i can change the value and add it to the url?
def between(value, a, b):
pos_a = value.find(a)
if pos_a == -1: return ""
pos_b = value.rfind(b)
if pos_b == -1: return ""
adjusted_pos_a = pos_a + len(a)
if adjusted_pos_a >= pos_b: return ""
return value[adjusted_pos_a:pos_b]
def before(value, a):
pos_a = value.find(a)
if pos_a == -1: return ""
return value[0:pos_a]
def after(value, a):
pos_a = value.rfind(a)
if pos_a == -1: return ""
adjusted_pos_a = pos_a + len(a)
if adjusted_pos_a >= len(value): return ""
return value[adjusted_pos_a:]
test = "https://google.com/?test=sadsad&again=tesss&dadasd=asdaas"
if "&" in test:
print(test.replace(between(test, "=", "&"), 'aaaaa').replace(after(test, "="), 'howwww'))
else:
print(test.replace(after(test, "="), 'test'))
Thanks!
From your code it seems like you are probably fairly new to programming, so first of all congratulations on having attempted to solve your problem.
As you might expect, there are language features you may not know about yet that can help with problems like this. (There are also libraries specifically for parsing URLs, but point you to those wouldn't help your progress in Python quite as much - if you are just trying to get some job done they might be a godsend).
Since the question lacks a little clarity (don't worry - I can only speak and write English, so you are ahead of me there), I'll try to explain a simpler approach to your problem. From the last block of your code I understand your intent to be:
"If there are multiple parameters, replace the value of the first with 'aaaaa' and the others with 'howwww'. If there is only one, replace its value with 'test'."
Your code is a fair attempt (at what I think you want to do). I hope the following discussion will help you. First, set url to your example initially.
>>> url = "https://google.com/?test=sadsad&again=tesss&dadasd=asdaas"
While the code deals with multiple arguments or one, it doesn't deal with no arguments at all. This may or may not matter, but I like to program defensively, having made too many silly mistakes in the past. Further, detecting that case early simplifies the remaining logic by eliminating an "edge case" (something the general flow of your code does not handle). If I were writing a function (good when you want to repeat actions) I'd start it with something like
if "?" not in url:
return url
I skipped this here because I know what the sample string is and I'm not writing a function. Once you know there are arguments, you can split them out quite easily with
>>> stuff, args = url.split("?", 1)
The second argument to split is another defensive measure, telling it to ignore all but the first question mark. Since we know there is at least one, this guarantees there will always be two elements in the result, and Python won't complain about a different number of names as values in that assignment. Let's confirm their values:
>>> stuff, args
('https://google.com/', 'test=sadsad&again=tesss&dadasd=asdaas')
Now we have the arguments alone, we can split them out into a list:
>>> key_vals = args.split("&")
>>> key_vals
['test=sadsad', 'again=tesss', 'dadasd=asdaas']
Now you can create a list of key,value pairs:
>>> kv_pairs = [kv.split("=", 1) for kv in key_vals]
>>> kv_pairs
[['test', 'sadsad'], ['again', 'tesss'], ['dadasd', 'asdaas']]
At this point you can do whatever is appropriate do the keys and values - deleting elements, changing values, changing keys, and so on. You could create a dictionary from them, but beware repeated keys. I assume you can change kv_pairs to reflect the final URL you want.
Once you have made the necessary changes, putting the return value back together is relatively simple: we have to put an "=" between each key and value, then a "&" between each resulting string, then join the stuff back up with a "?". One step at a time:
>>> [f"{k}={v}" for (k, v) in kv_pairs]
['test=sadsad', 'again=tesss', 'dadasd=asdaas']
>>> "&".join(f"{k}={v}" for (k, v) in kv_pairs)
'test=sadsad&again=tesss&dadasd=asdaas'
>>> stuff + "?" + "&".join(f"{k}={v}" for (k, v) in kv_pairs)
'https://google.com/?test=sadsad&again=tesss&dadasd=asdaas'
I would use urllib since it handles this for you.
First lets break down the URL.
import urllib
u = urllib.parse.urlparse('https://google.com/?test=sadsad&again=tesss&dadasd=asdaas')
ParseResult(scheme='https', netloc='google.com', path='/', params='', query='test=sadsad&again=tesss&dadasd=asdaas', fragment='')
Then lets isolate the query element.
data = dict(urllib.parse.parse_qsl(u.query))
{'test': 'sadsad', 'again': 'tesss', 'dadasd': 'asdaas'}
Now lets update some elements.
data.update({
'test': 'foo',
'again': 'fizz',
'dadasd': 'bar'})
Now we should encode it back to the proper format.
encoded = urllib.parse.urlencode(data)
'test=foo&again=fizz&dadasd=bar'
And finally let us assemble the whole URL back together.
new_parts = (u.scheme, u.netloc, u.path, u.params, encoded, u.fragment)
final_url = urllib.parse.urlunparse(new_parts)
'https://google.com/?test=foo&again=fizz&dadasd=bar'
Is it necessary to do it from scartch? If not use the urllib already included in vanilla Python.
from urllib.parse import urlparse, parse_qsl, urlencode, urlunparse
url = "https://google.com/?test=sadsad&again=tesss&dadasd=asdaas"
parsed_url = urlparse(url)
qs = dict(parse_qsl(parsed_url.query))
# {'test': 'sadsad', 'again': 'tesss', 'dadasd': 'asdaas'}
if 'again' in qs:
del qs['again']
# {'test': 'sadsad', 'dadasd': 'asdaas'}
parts = list(parsed_url)
parts[4] = urlencode(qs)
# ['https', 'google.com', '/', '', 'test=sadsad&dadasd=asdaas', '']
new_url = urlunparse(parts)
# https://google.com/?test=sadsad&dadasd=asdaas

Two return statements for one method? Python

I have a method which should print reports. There are two reports that are printed using the same method but has different conditions. I have given the if-else condition but some reason the else part is not being executed!
Kindly help me with the issue
count = 80
a = 20
if a > count:
return xyz
else:
return abc
abc and xyz are two different types of reports that I have.
Edit:
This is my actual function. In each I'm fetching my records.
for inv_no in each:
if inv_no.invoice_date > '2017-06-30':
return {
'type': 'ir.actions.report.xml',
'report_name': 'gst_invoice_print',
'datas': datas,
}
else:
return {
'type': 'ir.actions.report.xml',
'report_name': 'invoice_print',
'datas': datas,
}
I saw your last commend so this is how you should compare dates.
Odoo dates are not comparable until you convert them to datetime objects, so to convert odoo date to datetime object use:
a = datetime.strptime(self.date_field1, "%Y-%m-%d")
b = datetime.strptime(self.date_field1, "%Y-%m-%d")
# where date_field1 and date_field2 are something like this 2017-01-01
# now you can compare a and b
if a < b:
drink beer
else:
drink more beer
I don't understand your problem, because your code works perfectly.
This code below works for me:
count = 80
a = 20
def test(a, count):
if a > count:
return "xyz"
else:
return "abc"
print test(a,count)
it returns "abc"...
So now that we know exactly what is going on, Python's return statement actually 'exits' the function not continue it afterwards. What you need is to keep track of all the reports and then return it rather than doing so immediately. A way to do so is with a list or tuple.
I prefer lists as they are mutable for future use but if you need to use a tuple use ( ) instead of [ ].
reports = []
for inv_no in each:
if inv_no.invoice_date > '2017-06-30':
reports.append({
'type': 'ir.actions.report.xml',
'report_name': 'gst_invoice_print',
'datas': datas,
})
else:
reports.append({
'type': 'ir.actions.report.xml',
'report_name': 'invoice_print',
'datas': datas,
})
return reports
This way you can get the appropriate reports for each date. To access the reports later on in the code, you do so with reports["index of list"]["key of dict"]
Well, the code you provided is always going to print the report abc since what you are comparing always returns false (As everyone else had said).
If you do want to retrieve either of the two reports depending on the situation, (In the case of a compare) either one or both values should be mutable. So they need to be inputed by the user, a parameter for the function, or computer inputted (such as a variable provided by the computer. Like time)
Now if the snippet of code you provided represents a portion of your function and assuming the count variable is keeping track of an iteration, you would need to have the return statement after the iteration to make sure everything is accounted for.
for count in list:
# Do something
if a > count:
return xyz
else:
return abc
However, the a variable or list variable (don't use list as a name for a variable, this is just an example) must be mutable. Otherwise the function will always return the same thing.
As a note:
If you want more relevant answers, include more code that better represents what you are asking (Such as a function, class, or the areas where input is retrieved and then manipulated). Don't just provide the code where there is the error but also what leads to it.

Cleaner way to represent Rules (if-else) in Python

I am trying to find a design pattern (or maybe an algorithm) which will help me write these rules in a cleaner way. Any suggestions?
def get_rules(user, value):
if 500 <= value < 5000 and not user.address:
return [REQUEST_ADDRESS]
if value >= 5000:
if not user.address and not user.phone:
return [REQUEST_ADDRESS, REQUEST_PHONE]
if user.address and not user.phone:
return [REQUEST_PHONE]
if not user.address and user.phone:
return [REQUEST_ADDRESS]
# Potentially ~20 more conditions here based on various attributes of user
return [STATES.REQUEST_NONE]
Note: I am not looking for a rules engine since I don't want to complicate my code by adding "business friendly" DSL in python. Python itself is a simple language to write these rules.
Interesting read: http://martinfowler.com/bliki/RulesEngine.html (but I am still trying to stay away from a "framework" to do this for me).
You're checking lots of different combinations with your "if a and not b else check not a and b else check not a and not b" strategy to figure out what combination of requests you need to send.
Instead, only check what you're missing:
missing = []
if not user.phone:
missing.append(REQUEST_PHONE)
if not user.address:
missing.append(REQUEST_ADDRESS)
return missing or [REQUEST_NONE]
You can use a dict in this case:
resdict = {(False, False): [REQUEST_ADDRESS, REQUEST_PHONE],
(True, False): [REQUEST_PHONE],
(False, True): [REQUEST_ADDRESS]}
return resdict[(user.address, user.phone)]
You can also use a list comprehension:
return [req for req, haveit in zip([REQUEST_ADDRESS, REQUEST_PHONE], [user.address, user.phone]) if not haveit]
Or a simpler list append:
res = []
if not user.address:
res.append(REQUEST_ADDRESS)
if not user.phone:
res.append(REQUEST_PHONE)
If I understood the question right, you have a list of attributes for the user. If one is false a REQUEST value schould be added to the list. Then this could help:
# define all your combinations here:
mapping = {'address': REQUEST_ADDRESS, 'phone': REQUEST_PHONE, …)
return [value for key, value in mapping.items()
if not getattr(user, key, None)]
Looks like your "rules" boil down to this: Request values for fields that are not present as attributes in the object user. I will assume that the mapping of attributes to requests can be arbitrary; you can represent it as a dictionary mapping, e.g. like this:
rulemap = {
"address": REQUEST_ADDRESS,
"phone": REQUEST_PHONE,
# etc.
}
You can then get a list of the requests to issue by checking which of the keys in rulemap are not present as attributes in the object user:
return [ rulemap[fld] for fld in rulemap.keys() if fld not in user.__dict__ ]

Define a dictionary name within a function

I am writing a function that will take a parameter and, among other things, make a dictionary. I would like the dictionary's name to be based off the name of the input file. Say ht input file is input.xml , i would like the name of the dictionary to be input. Ideally I would use something like this:
def function(input):
for x in y: list(get value)
input[:4][key] = [value]
I am wondering if you know a better way to do this but what i am using now is an extra name in the function:
def function(input, dictname):
for x in y: list(get value)
dictname[key] = [value]
right now I am simply adding a second name to my function but am wondering if there is a way to do this to require fewer inputs.
Edit
I am including a longer version of the function I am using so you guys can get the context. This uses a BioPython module to iterate through an XML file of hits. I am using [temp] to hold the hits for each query and then making a dictionary of for each set of query/hits. I would like this dictionary to be named the same as my input file.
from Bio.Blast import NCBIXML
def make_blast_dictionary(blastxml, maxhits, blastdict):
temp=[]
for record in NCBIXML.parse(open(blastxml):
for number, align in enumerate(record.alignments):
if number == int(maxhits): break
temp.append(str(align.title).split("|")[1])
blastdict[str(record.query_id)] = [temp]
The thing about named variables is that you can call them whatever you like. It's best to name them specific to the context you're using them with.
It would be a better move to simply return a dictionary from your method, instead.
The other respondents are legitimately concerned about why you would want to do this or whether you should do this. That being said, here is how you could do it:
import os.path
def function(filename):
d = {'red': 10, 'blue': 20}
name, ext = os.path.splitext(filename)
globals()[name] = d
function('input.xml')
print input
def make_name(input):
return = input.split('.')[0]
def function(input):
"""Note: this function is incomplete and assumes additional parameters are in your original script
"""
for x in y: list(get value)
dict_name[key] = [value]
return dict_name
def make_dict(input):
dict_name = make_name(input)
dict_name = {}
dict_name = function(input)
return dict_name
Is this what you need?

Categories

Resources