Parse numbers and lists from config files with configparser - python

I'm doing a relatively big project for my final thesis and therefore I am using a .ini file for storing and retrieving settings. However, I am unable to find an elegant solution for how to convert the strings (well, actually the strings inside the dictionary) that Configparser returns to numbers (ints and floats) and/or lists.
Googling the issue, I came across this SO thread which only tackles the 'list' part of my problem but using the top rated solution (defining lists inside the .ini file like that: list=item1,item2) didn't do anything for me, as the 'list' still shows up as a string after parsing. Also, I do not want to change format.
So I decided I would try it myself and came up with this solution:
import configparser
# create a new instance of a 'ConfigParser' class
config = configparser.ConfigParser()
# create mock-content for the config file
config["Test"] = {
"test_string":"string",
"test_int":"2",
"test_float":"3.0",
"test_list":"item1, item2"
}
# get the relevant settings
settings = config["Test"]
# pack the relevant settings into a dictionary
settings = dict(settings)
# iterate through all the key-value pairs and convert them, if possible
for key, value in settings.items():
# try to convert to int
try:
settings[key] = int(value)
# if the value can't be converted to int, it might be a float or a list
except ValueError:
# try converting it to a float
try:
settings[key] = float(value)
# if the value can't be converted to float, try converting to list
except ValueError:
if "," in value:
cont = value.split(",")
settings[key] = [item.strip() for item in cont]
else:
settings[key] = value
print(type(settings["test_string"]))
print(settings)
However, this seems so very inelegant and is so heavily nested and the task itself seems so important that I cannot believe that there is no "more official" solution to this that I am simply unable to find.
So, could please someone help me out here and tell me if there really is no better, more straightforward way to achieve this!?

Best I can do is this (though it's kinda hacky and probably dangerous too):
for key, value in settings.items():
try: # will handle both ints and floats, even tuples with ints/floats
settings[key] = eval(value)
except NameError: # this means it's a string or a tuple with strings
get_val = list(map(str.strip, value.split(",")))
settings[key] = get_val if get_val[1:] else get_val[0]
This will work correctly for ints, floats as well as your comma separated values (it will evaluated it as a tuple, I guess that should be fine though I added a condition for that anyway).

Related

How to get result from a dictionary with lists as the values

I have a JSON file with n number of dictionaries as listed below in the snippet. I am trying to fetch the value against the key but it fails in my code when the value is defined as a list like in the below example for key affected_packages. I tried to check why my code fails, so it looks like it pulls no data out of it this fails. I just see two brackets [] as output instead of "thunderbird-0:78.9.1-1.el8_1","thunderbird-0:78.9.1-1.el8_2","thunderbird-0:78.9.1-1.el8_3","thunderbird-0:78.9.1-1.el7_9"
{"bugzilla_description":"CVE-2021-23992 Mozilla: A crafted OpenPGP key with an invalid user ID could be used to confuse the user","cvss_score":null,"cvss_scoring_vector":null,"CWE":"CWE-347","affected_packages":["thunderbird-0:78.9.1-1.el8_1","thunderbird-0:78.9.1-1.el8_2","thunderbird-0:78.9.1-1.el8_3","thunderbird-0:78.9.1-1.el7_9"],"resource_url":"https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2021-23992.json","cvss3_scoring_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L","cvss3_score":"4.3"}
I am doing like below in my code as I need to prepare a worksheet. Sample snippet:
for i in range(offs):
ws.cell(row=r+1+i,column=2).value = v['current'][i]
if 'affected_packages' in list(tmp1.keys()):
ws.cell(row=r+1+index1,column=11).value = tmp1['affected_packages']
print("affected_packages done")
if 'advisories' in list(tmp1.keys()):
ws.cell(row=r+1+index2,column=13).value = tmp1['advisories']
print("advisories done")
Is there a way I can pull the value correctly for those defined as a list in the dictionary? I need a way so that it won't hamper my existing logic to pull value for normal key: value since while looking up into my JSON file.
So need something which can fulfil both whether my value in the dictionary is as a list or not as a list and I can get value against the keys in my json file.
As mentioned in the other answers, you can test the type of a variable using
if type(some_variable) == list:
# do what you need to do
You do mention that your code breaks, and I guess it's because inserting into a cell expects a String, not the list you pass in the line
ws.cell(row=r+1+index1,column=11).value = tmp1['affected_packages']
So how do we get a string out of a list of strings? It's pretty easy using the join method.
my_list = ["thunderbird-0:78.9.1-1.el8_1","thunderbird-0:78.9.1-1.el8_2","thunderbird-0:78.9.1-1.el8_3","thunderbird-0:78.9.1-1.el7_9"]
as_one_string = ", ".join(my_list)
print(as_one_string)
# Prints out 'thunderbird-0:78.9.1-1.el8_1, thunderbird-0:78.9.1-1.el8_2, thunderbird-0:78.9.1-1.el8_3, thunderbird-0:78.9.1-1.el7_9'
So combining the two ideas:
if 'affected_packages' in list(tmp1.keys()):
ws.cell(row=r+1+index1,column=11).value = tmp1['affected_packages'] if type(tmp1['affected_packages']) != list else ", ".join(tmp1['affected_packages'])
print("affected_packages done")
Quick feedback because I can't comment yet: Please always include an error message and/or the output you get when running your code when you ask a question
If I understand it correctly, you just need to determine if a value in dict is list. You can do that as below:
for i in d.items(): # gets key, value as a tuple.
if isinstance(i[1],list):
print('its a list, process it accordingly')
else:
print('Not a list')
Regarding your second problem, when you don't know if it is a list or something else, you can just check the type, maybe like this:
if type(tmp1['affected_packages']) == list:
# process the list
else:
# process other types
Since you don't know the data type, having this explicit type check seems necessary.

Can't use data from JSON files for datetime

So I have been trying to make a program that can display a countdown clock to a GUI. It requests a JSON file from a website and then uses that data- getting the JSON file and it's data is working for other parts of my program except using the data inside datetime.
import time
import requests
import json
import datetime as dt
response = requests.get("https://website.website/mission.json")
data = json.loads(response.text)
a = dt.datetime(data['launch'])
b = dt.datetime.now()
print("T- " + time.strftime("%H:%M:%S", time.gmtime(round((a-b).total_seconds()))))
JSON file:
{
"mission": "GlobeSat2",
"launch": "2020,9,13,19,00,00",
"status": "Go"
}
When I use a = dt.datetime(data['launch']) it gives an error of TypeError: an integer is required (got type str) but when I use a = dt.datetime(2020,9,13,19,00,00) there is no error.
The value is the same as if I put it in myself
>print(data['launch'])
>2020,9,13,19,00,00
What I've tried:
I changed the value of the JSON file to "launch": "20200913120000" and gave an error of OverflowError: Python int too large to convert to C long.
I tried to do int(data['launch']) which obviously didn't work.
If your problem is to change that date to datetime format you can easily use this one line of code to do that
a=dt.datetime(*([int(i) for i in data['launch'].split(",")]))
then a will get converted into datetime.
Beginning programmers often make similar mistakes to yours here, confusing print statements with return values and confusing string representations of values with the actual value.
Remember that standard in and out, or the console are just a text-based user interface. They feel technical and close to the language, but they are still just a user interface.
If you run this script:
x = 123
print(123)
Or you do the same thing on the Python CLI:
>>> x = 123
>>> x
123
You might thing Python showed you the value of the x variable of type int, 123. But 123 is just the text representation of that value. There's many ways you could represent 123, for example 0b1111011 or 0x7B - all valid string representations of the exact same integer number.
Your problem is similar, in that you expect these two things to be the same:
a = dt.datetime(2020,9,13,19,00,00)
parameter = '2020,9,13,19,00,00'
b = dt.datetime(parameters)
But in the case of assigning to a, you call .datetime() with 6 integer values, while in the case of assigning to b, you call .datetime() with a single string value. The fact that the string looks very similar when printed to how you would normally write those 6 integer values in code really means nothing to Python. Note the 'when printed' vs. 'in code'.
You can have Python interpret a string to obtain the values you know it represents, but you'll have to tell it how (there's many approaches).
For example:
a = dt.datetime(*[int(part) for part in data['launch'].split(',')])
This takes the string value of data['launch'], splits it over the ',' into 6 strings in a list (['2020','9','13','19','00','00']) and then turns each part of that list into an integer with the int() function, which can turn a string that represents a single integer into its int value. The resulting list is then spread over the parameters using the * operator on the list.
There's many different approaches, but of course you could also consider fixing the JSON if you wrote the server that returned the value:
{
"mission": "GlobeSat2",
"launch": [2020,9,13,19,0,0],
"status": "Go"
}
By replacing the "" with [], JSON now represents the value of "launch" as a list and when read by Python, it will be read as a list of integers.
If that was your JSON, this would be your code:
a = dt.datetime(*data['launch'])
Still using the * to spread it over the parameters of the function, but everything else would be done automatically when interpreting the JSON.

generate array element if not exists in python

What is the right way to check if element does not exist in Python ?
The element is expected to be present most of the time, and if it is empty it is not an "error" and need to be processed normally:
def checkElement(self, x, y):
if not (self.map[x][y]):
self.map[x][y] = 'element {}:{}'.format(x, y)
return self.map[x][y]
tldr
Your own code together with triplee's answer cover the common cases. I want to point out ambiguity in your question. How you check for "empty" very much depends on what your definition of empty is.
This is a tricky question because the semantics of "empty" are not exactly clear. Assuming that the data structure is a nested dict as could be inferred from your example, then it could be the case that empty means the inner/outer key is not contained in the dictionary. In that case you'd want to go with what triplee suggests. Similarly if the container is a nested list, but instead of KeyError you'd catch IndexError.
Alternatively, it could also be the case that "empty" means both the inner and outer keys are in the dictionary (or list) but the value at that position is some signifier for "empty". In this case the most natural "empty" in Python would be None, so you'd want to check if the value under those keys is None. None evaluates to False in boolean expressions so your code would work just fine.
However, depending on how your application defines empty these are not the only alternatives. If you're loading json data and the producer of said json has been prudent, empty values are null in json and map to None when loaded into Python. More often than not the producer of the json has not been prudent and empty values are actually just empty strings {firstName:''}, this happens more often than one would like. It turns out that if not self.map[x][y] works in this case as well because an empty string also evaluates to False, same applies to an empty list, an empty set and an empty dict.
We can generalise the meaning of empty further and say that "empty" is any value that is not recognised as actionable or valid content by the application and should therefore be considered "empty" - but you can already see how this is completely dependent on what the application is. Would {firstName: ' '} a string that only contains white space be empty, is a partially filled in email address empty?
The Best way to check if any object (Lists, Dicts, etc) exist or not is to wrap it within a try...except Block. Your checkElement Function could be re-written thus:
def checkElement
try:
self.map[x][y]
except:
# HANDLE THE CASE WHERE self.map[x][y] ISN'T SET...
self.map[x][y] = 'element {}:{}'.format(x, y)
The answer to what you seem to be asking is simply
try:
result = self.map[x][y]
except KeyError:
result = 'element {}:{}'.format(x, y)
self.map[x][y] = result
return result
Of course, if self.map[x] might also not exist, you have to apply something similar to that; or perhaps redefine it to be a defaultdict() instead, or perhaps something else entirely, depending on what sort of structure this is.
KeyError makes sense for a dict; if self[x] is a list, probably trap IndexError instead.

Why is this an index error?

This is my first post, so I apologize if this has been answered previously. I have tried to look through the Python 3 documentation on string formatting and lists, and reviewed similar formatting questions here on SO.
I want to take the string (data1), break it into a list (bigData), and print out a statement using the list items. Eventually, the idea would be to read in a csv file, break it up, and print out a response, but I've tried to simplify the process since there's an error.
"Hello, John Doe. Your current balance is $53.44."
However, I'm not sure why the following code is throwing an IndexError, much less a tuple index.
data1 = "John,Doe,53.44"
bigData = data1.split(",")
bigData[-1] = float(bigData[-1])
print(bigData) # test - []'s indicate a list, not tuple?
greeting = "Hello, {} {}. Your current balance is ${}."
print(greeting.format(bigData))
My guess is that bigData is heterogeneous, which implies a tuple. If I substitute a string value instead of 53.44 (so data1 and bigData are homogeneous), it throws the same error.
data1 = "John,Doe,random"
bigData = data1.split(",")
print(bigData) # test - []'s indicate a list, not tuple?
greeting = "Hello, {} {}. Your current balance is {}."
print(greeting.format(bigData))
However, if I convert the original to Python 2.x string formatting, it formats correctly without an error.
data1 = "John,Doe,53.44"
bigData = data1.split(",")
bigData[-1] = float(bigData[-1])
print(bigData) # test - []'s indicate a list, not tuple?
greeting = "Hello, %s %s. Your current balance is $%.2f."
print(greeting % tuple(bigData))
Why is it converting my string to a tuple?
How do I write this work in Python 3?
Thank you.
Use the splat (*) to unpack your arguments (your format string wants three arguments but you only give it one, a list containter).
print(greeting.format(*bigData))
Also, you may want:
bigData[-1] = str(round(float(bigData[-1]), 2))
The str.format method takes positional arguments, not a single list. You need to unpack your list bigData using the * operator:
data1 = "John,Doe,random"
bigData = data1.split(",")
print(bigData) # test - []'s indicate a list, not tuple?
greeting = "Hello, {} {}. Your current balance is {}."
print(greeting.format(*bigData)) # here's the change
You're correct that bigData is a list, not a tuple, str.split returns a list.
The str.split() method returns a list, by definition.
I think you've misunderstood something you've read - heterogeneous vs. homogeneous refer to typical use cases of tuples vs. lists. Having the types of all the elements match or not does not magically cause the container to change to the other type!
I can see how this is surprising, though what surprises me is that the traceback doesn't show that the exception occurs in the format call.
Python's lists can be heterogenous just like tuples; this is because the common type they store is object references, which all things in Python are. The tuple is actually the argument list to the format method, in this case (bigData,). It ran out of arguments when looking for things to format, since you had three {} placeholders but only one argument (the list bigData). You can use greeting.format(*bigData) to unpack the list and use its contents as arguments.
The % formatting doesn't encounter this error because it actually expects a tuple (or one item) in the right operand.
A more idiomatic and legible approach might actually be to go to the csv module already:
import csv, io
data1 = "John,Doe,random"
for row in csv.DictReader(io.StringIO(data1),
"givenname surname balance".split()):
greeting = "Hello, {givenname} {surname}. Your current balance is {balance}."
print(greeting.format(**row))
This lets us assign meaningful names to the columns, including reordering them in the format string if needed. I've left out the float conversion, and by the way, decimal.Decimal may be better for that use.

Variable method name in python

I have a dictionnary fields={} which contains some field names that I named the Qt comobox objects after. For example keys combobox1 and combobox2, both contain a list with values that I would like to add to the
fields={}
fields['combobox1']=['value1','someting else']
fields['combobox2']=['bla','another value']
for key,values in fields.items():
for value in values:
Qt_ui. _____key_____ .addItem(value)
what is the correct syntax for the last line, so that ____key____ is replaced with the keys from the dictionary? I tried ui.__getattribute__(key).addItem(value) but it does't seem to work. Any suggestions are appreciated.
TypeError: 'PySide.QtGui.QComboBox.addItem' called with wrong argument types:
PySide.QtGui.QComboBox.addItem(float)
Supported signatures:
PySide.QtGui.QComboBox.addItem(PySide.QtGui.QIcon, unicode, QVariant = QVariant())
PySide.QtGui.QComboBox.addItem(unicode, QVariant = QVariant())
In fact the problem was somewhere else getattribute(key) is correct but the added item needs to be a string. Any way, I thought this is an interesting problem and will leave the post anyway.
ui.__getattribute__(key).addItem(str(value))

Categories

Resources