How to write ini-files without sections? - python

I need to create a file in the following format:
option1 = 99
option2 = 34
do_it = True
...
When I use ConfigParser, I have to put all my data into a section with an artificial name, and then it creates a file which starts with [SECTION].
import ConfigParser
ini_writer = ConfigParser.ConfigParser()
ini_writer.add_section('SECTION')
ini_writer.set('SECTION', 'option1', 99)
ini_writer.set('SECTION', 'option2', 34)
ini_writer.set('SECTION', 'do_it', True)
with open('my.ini', 'w') as f:
ini_writer.write(f)
How can I change it so it outputs the file without the dummy section header? I would like to do it using Python 2.7, but a Python 3 solution would help too (the idea is that I could port it to Python 2.7).
This related question shows how to read such files using minor tweaks to the code.

[NB: the following is written for Python 3; you would need to make a couple of minor changes to make it run under Python 2.]
Maybe something like this; here, I write to an io.StringIO object in memory, then take everything but the first line and write that out to the target file.
import configparser
import io
buf = io.StringIO()
ini_writer = configparser.ConfigParser()
ini_writer.set('DEFAULT', 'option1', '99')
ini_writer.set('DEFAULT', 'option2', '34')
ini_writer.set('DEFAULT', 'do_it', 'True')
ini_writer.write(buf)
buf.seek(0)
next(buf)
with open('my.ini', 'w') as fd:
fd.write(buf.read())
By using the section name DEFAULT we avoid having to create a new section first.
This results in:
$ cat my.ini
option1 = 99
option2 = 34
do_it = True

As ConfigParser doesn't support this I personally would probably opt to monkeypatch the write method to support non-section writing.
from configparser import ConfigParser
def _write_section_custom(self, fp, section_name, section_items, delimiter):
for key, value in section_items:
value = self._interpolation.before_write(self, section_name, key, value)
if value is not None or not self._allow_no_value:
value = delimiter + str(value).replace('\n', '\n\t')
else:
value = ''
fp.write('{}{}\n'.format(key, value))
fp.write('\n')
ini_writer = ConfigParser()
ConfigParser._write_section = _write_section_custom
ini_writer.add_section('SECTION')
ini_writer.set('SECTION', 'option1', '99')
ini_writer.set('SECTION', 'option2', '34')
ini_writer.set('SECTION', 'do_it', 'True')
with open('my.ini', 'w') as f:
ini_writer.write(f)
With that I get:
$ cat my.ini
option1 = 99
option2 = 34
do_it = True
I've tested this under Python 3.8 so you would need to test/adjust for 2.7. Also bear in mind that the reading of the custom ini would need to be adapted/monkeypatched. You could also wrap this into a custom ConfigParser class of your own so that you have it reusable wherever you need it in your project.

Related

Find and replace variable value in a .py file using another python program

Here is my challenge. I have a radioConfig.py file which contains variable values which need to be changed if and when the user changes location or scan times. This would be used with students so i'm programming a GUI, pysimplegui, to change the values of those variable.
Here is what i have so far but its not working. Its replacing the variable name, not the value.
I'm using a Rpi and python3. I studied electronics and my program skills are with C. I'm not sure if this is the best way to solve my challenge nor do i know of the python options which exist that could be useful. Any help would be amazing.
#File: GuiTest.py before code is executed
freqCenter = 21000000
freqBandwidth = 8000000
upconvFreqHz = 125000000
fftWindow = "BlacHarr"
notes = "Test"
rtlSampleRateHz = 2000000
#-----------------------------------------------------------------------
#Program which will be a gui asking user for input values
freqCenterGUI = 20800280
with open('GuiTest.py', 'r') as file :
filedata = file.read()
filedata = filedata.replace('freqCenter', str(freqCenterGUI).strip('()'))
with open('GuiTest.py', 'w') as file:
file.write(filedata)
#File: GuiTest.py after code is executed
20800280 = 21000000
freqBandwidth = 8000000
upconvFreqHz = 125000000
notes = "Test"
rtlSampleRateHz = 2000000
#-----------------------------------------------------------------------
I'd say : use a config file.
Modifying a script is really not good practice !
In your cfg.ini :
[_]
freqCenter = 21000000
freqBandwidth = 8000000
upconvFreqHz = 125000000
fftWindow = BlacHarr
notes = Test
rtlSampleRateHz = 2000000
Then use configparser :
import configparser
cfg = configparser.ConfigParser()
with open('cfg.ini', encoding='utf-8') as f:
cfg.read_file(f)
cfg['_']['freqCenter'] = '20800280'
with open('cfg.ini', 'w', encoding='utf-8') as f:
cfg.write(f)
EDIT :
Or, as suggested by #juanpa.arrivillaga, using a json is also a great solution !
Matter of taste... :)
Quick and dirty:
File 1:
freqCenter = 21000000
freqBandwidth = 8000000
upconvFreqHz = 125000000
fftWindow = "BlacHarr"
notes = "Test"
rtlSampleRateHz = 2000000
File 2
#Program which will be a gui asking user for input values
freqCenterGUI = "20800280"
my_dict={}
file_data=open("GuiTest.py", "r")
in_lines=file_data.readlines()
for line in in_lines:
var, val = line.split('=')
if var.strip() == "freqCenter":
val = freqCenterGUI + "\n"
my_dict[var]=val
f_out=open("GuiTest.py", "w")
for key,val in my_dict.items():
f_out.write(str(key)+"="+str(val))
f_out.close()

read parameters from txt

I have a txt file containing some parameters. The txt file looks like this:
par1 = 10.81
par2 = 0.3
par3 = 0.5
I would like to read it in python and create automatically all the variables specified in the txt.
What is the best file to read a configuration file and create the parameters listed there?
in particular say that I have a class
how can I have the following?
class MyClass:
def __init__(self)
self.par1 = par1
self.par2 = par2
self.par3 = par3
DANGEROUS CODE AHEAD
You could use exec.
Consider the file foo.txt:
a = 1
b = 2
c = 3
and the code:
with open('foo.txt') as f:
for line in f:
exec(line)
print(a)
# 1
print(b)
# 2
print(c)
# 3
BUT, as the header says, this is a dangerous approach as foo.txt can be easily modified to contain
import os
os.remove('C:\system\a_very_important_system_file')
and exec will happily execute it.
THE SAFE APPROACH
Change foo.txt to JSON format:
{"a": 1,
"b": 2,
"c": 3}
And use the json module to load it in your code:
import json
with open('foo.txt') as f:
variables = json.load(f)
print(variables["a"])
# 1
EDIT
If you need to dynamically initiate instance attributes from an unknown JSON file you can use setattr:
import json
class MyClass:
def __init__(self):
with open('foo.txt') as f:
variables = json.load(f)
for key, value in variables.items():
setattr(self, key, value)
In addition to the answer provided by #DeepSpace, you could restrict the allowed values with a regular expression:
import re
string = """
par1 = 10.81
par2 = 0.3
par3 = 0.5
"""
rx = re.compile(r'^(?P<key>\w+)\s*=\s*(?P<value>[\d.]+)', re.MULTILINE)
for var in rx.finditer(string):
exec("{} = {}".format(var.group('key'), var.group('value')))
print(par1)
# 10.81
Here, only numerical values are allowed for the values (0-9.) and a-z_ for the variable names. Adjust as needed (ie to allow strings as well).
An alternative would be to use a container for the parameters, ie a dict:
params = {match.group('key'): match.group('value')
for match in rx.finditer(string)}
print(params)
# {'par1': '10.81', 'par2': '0.3', 'par3': '0.5'}
You would then call your variables via params['par1'].
After you read the file with readlines()
with open("sample.txt") as f:
fr = f.readlines()
fr = ['par1 = 10.81\n', 'par2 = 0.3\n', 'par3 = 0.5\n', 'par4 = 0.7\n', 'par5 = 0.9\n'] # Output
fr = [f[:-1] for f in fr]
frs = [f.split(" = ") for f in fr]
for i, j in frs:
exec("%s = %s" % (i,j))
You are looking for the eval function.
with open('foo.txt','r') as fil:
for line in fil:
eval(line)

Append Function Nested Inside IF Statement Body Not Working

I am fairly new to Python (just started learning in the last two weeks) and am trying to write a script to parse a csv file to extract some of the fields into a List:
from string import Template
import csv
import string
site1 = 'D1'
site2 = 'D2'
site3 = 'D5'
site4 = 'K0'
site5 = 'K1'
site6 = 'K2'
site7 = '0'
site8 = '0'
site9 = '0'
lbl = 1
portField = 'y'
sw = 5
swpt = 6
cd = 0
pt = 0
natList = []
with open(name=r'C:\Users\dtruman\Documents\PROJECTS\SCRIPTING - NATAERO DEPLOYER\NATAERO DEPLOYER V1\nataero_deploy.csv') as rcvr:
for line in rcvr:
fields = line.split(',')
Site = fields[0]
siteList = [site1,site2,site3,site4,site5,site6,site7,site8,site9]
while Site in siteList == True:
Label = fields[lbl]
Switch = fields[sw]
if portField == 'y':
Switchport = fields[swpt]
natList.append([Switch,Switchport,Label])
else:
Card = fields[cd]
Port = fields[pt]
natList.append([Switch,Card,Port,Label])
print natList
Even if I strip the ELSE statement away and break into my code right after the IF clause-- i can verify that "Switchport" (first statement in IF clause) is successfully being populated with a Str from my csv file, as well as "Switch" and "Label". However, "natList" is not being appended with the fields parsed from each line of my csv for some reason. Python returns no errors-- just does not append "natList" at all.
This is actually going to be a function (once I get the code itself to work), but for now, I am simply setting the function parameters as global variables for the sake of being able to run it in an iPython console without having to call the function.
The "lbl", "sw", "swpt", "cd", and "pt" refer to column#'s in my csv (the finished function will allow user to enter values for these variables).
I assume I am running into some issue with "natList" scope-- but I have tried moving the "natList = []" statement to various places in my code to no avail.
I can run the above in a console, and then run "append.natList([Switch,Switchport,Label])" separately and it works for some reason....?
Thanks for any assistance!
It seems to be that the while condition needs an additional parenthesis. Just add some in this way while (Site in siteList) == True: or a much cleaner way suggested by Padraic while Site in siteList:.
It was comparing boolean object against string object.
Change
while Site in siteList == True:
to
if Site in siteList:
You might want to look into the csv module as this module attempts to make reading and writing csv files simpler, e.g.:
import csv
with open('<file>') as fp:
...
reader = csv.reader(fp)
if portfield == 'y':
natlist = [[row[i] for i in [sw, swpt, lbl]] for row in fp if row[0] in sitelist]
else:
natlist = [[row[i] for i in [sw, cd, pt, lbl]] for row in fp if row[0] in sitelist]
print natlist
Or alternatively using a csv.DictReader which takes the first row as the fieldnames and then returns dictionaries:
import csv
with open('<file>') as fp:
...
reader = csv.DictReader(fp)
if portfield == 'y':
fields = ['Switch', 'card/port', 'Label']
else:
fields = ['Switch', '??', '??', 'Label']
natlist = [[row[f] for f in fields] for row in fp if row['Building/Site'] in sitelist]
print natlist

Python script how can it do short

I have written a script on a python "icecast server", and I changed some strings in "/etc/icecast2/icecast.xml" like this:
import os,sys,re
def ices2():
changedir=open(pathh + "icecast3.xml", "w")
data=open("/etc/icecast2/icecast.xml").read()
changedir.write(re.sub("<source-password>hackme</source-password>","<source-password>123</source-password>" % x,data))
changedir.close()
ices2()
def ices1():
changedir1=open(pathh + "icecast2.xml", "w")
data=open(pathh + "icecast3.xml").read()
changedir1.write(re.sub("<relay-password>hackme</relay-password>", "<relay-password>123</relay-password>" % x,data))
changedir1.close()
os.remove(pathh + "icecast3.xml")
ices1()
def ices():
changedir2=open("/etc/icecast2/icecast.xml", "w")
data=open(pathh + "icecast2.xml").read()
changedir2.write(re.sub("<admin-password>hackme</admin-password>","<admin-password>123</admin-password>" % x,data))
changedir2.close()
os.remove(pathh + "icecast2.xml")
ices()
...but it's too long for the script. How can I shorten it? I need to do some changes in one file, open it to make changes and close it without any lost data. I know that it can be done in one function, but how to do it I don't know.
I need three changes in one function like this:
def ices():
changedir=open(pathh + "icecast3.xml", "w")
data=open("/etc/icecast2/icecast.xml").read()
changedir.write(re.sub("<source-password>hackme</source-password>","<source-password>123</source-password>",data))
changedir1.write(re.sub("<relay-password>hackme</relay-password>", "<relay-password>123</relay-password>",data))
changedir2.write(re.sub("<admin-password>hackme</admin-password>","<admin-password>123</admin-password>",data))
changedir.close()
i did it in one function and my script short than upper one. But it's wrong i need do it correctly
changedir=open(pathh + "icecast3.xml", "w")
data=open("/etc/icecast2/icecast.xml").read()
Here I create a new file "pathh + "icecast3.xml" (pathh-/home/user/Downloads), but I need to open file:
"/etc/icecast2/icecast.xml"
...read it and write changes to the same file.
All three functions do the same so you can join them into one. This is not complete solution but I think that you could go on from here on your own:
import os,sys,re
def ices(in_path, out_path, remove=False):
changedir = open(out_path, "w")
data = open(in_path, 'r')
changedir.write(re.sub("<source-password>hackme</source-password>","<source-password>123</source-password>" % x,data.read())) # this is wrong as well but I take it as an example
changedir.close()
data.close()
if remove:
os.remove(in_path)
You can call this function with:
ices(base_path + 'icecast2.xml', base_path + 'icecast3.xml', True)
Hints:
it's better to use os.path.join for creating the full paths (as opposed to string concatenation)
look at with statement and cosider using it for increased readability
EDIT (respecting the clarification in comment):
Sorry I missed the different strings in write. You can do it simply like this:
f = open(filename, 'r')
data = f.read()
f.close()
for tag in ['source', 'relay', 'admin']
sub_str = "<{tag_name}>%s</{tag_name}>".format(tag_name=tag+'-password')
data = re.sub(sub_str % 'hackme', sub_str % '123', data)
f = open(filename+'.new', 'w')
f.write(data)
f.close()

How python ConfigParser module translate the string "\r\n" to CRLF?

The testing code:
import ConfigParser
config = ConfigParser.ConfigParser()
config.read('test_config.ini')
orig_str = """line1\r\nline2"""
config_str = config.get('General', 'orig_str')
print orig_str == config_str
and the content of test_config.ini is:
[General]
orig_str = line1\r\nline2
What I want is the config_str can be the same value as the orig_str.
Any help would be appreciated!
If you want to write multiline configuration values, you just need to prepend a space at the beginning of the following lines. For example:
[General]
orig_str = line1
line2
returns 'line1\nline2' in my OS (Linux), but maybe in a different OS might return the '\r' as well. Otherwise, it would be easy to replace '\n' with '\r\n' as you need.
I'd say that whatever library you're using to encode the message should take care of the replacement. Anyway, if that's not the case, you can create your own ConfigParser as follows:
from ConfigParser import ConfigParser
class MyConfigParser(ConfigParser):
def get(self, section, option):
return ConfigParser.get(self, section, option).replace('\n', '\r\n')
config = MyConfigParser()
config.read('test_config.ini')
orig_str = """line1\r\nline2"""
config_str = config.get('General', 'orig_str')
print orig_str == config_str # Prints True

Categories

Resources