Lists in ConfigParser - python
The typical ConfigParser generated file looks like:
[Section]
bar=foo
[Section 2]
bar2= baz
Now, is there a way to index lists like, for instance:
[Section 3]
barList={
item1,
item2
}
Related question: Python’s ConfigParser unique keys per section
I am using a combination of ConfigParser and JSON:
[Foo]
fibs: [1,1,2,3,5,8,13]
just read it with:
>>> json.loads(config.get("Foo","fibs"))
[1, 1, 2, 3, 5, 8, 13]
You can even break lines if your list is long (thanks #peter-smit):
[Bar]
files_to_check = [
"/path/to/file1",
"/path/to/file2",
"/path/to/another file with space in the name"
]
Of course i could just use JSON, but i find config files much more readable, and the [DEFAULT] Section very handy.
There is nothing stopping you from packing the list into a delimited string and then unpacking it once you get the string from the config. If you did it this way your config section would look like:
[Section 3]
barList=item1,item2
It's not pretty but it's functional for most simple lists.
I recently implemented this with a dedicated section in a config file for a list:
[paths]
path1 = /some/path/
path2 = /another/path/
...
and using config.items( "paths" ) to get an iterable list of path items, like so:
path_items = config.items( "paths" )
for key, path in path_items:
#do something with path
One thing a lot of people don't know is that multi-line configuration-values are allowed. For example:
;test.ini
[hello]
barlist =
item1
item2
The value of config.get('hello','barlist') will now be:
"\nitem1\nitem2"
Which you easily can split with the splitlines method (don't forget to filter empty items).
If we look to a big framework like Pyramid they are using this technique:
def aslist_cronly(value):
if isinstance(value, string_types):
value = filter(None, [x.strip() for x in value.splitlines()])
return list(value)
def aslist(value, flatten=True):
""" Return a list of strings, separating the input based on newlines
and, if flatten=True (the default), also split on spaces within
each line."""
values = aslist_cronly(value)
if not flatten:
return values
result = []
for value in values:
subvalues = value.split()
result.extend(subvalues)
return result
Source
Myself, I would maybe extend the ConfigParser if this is a common thing for you:
class MyConfigParser(ConfigParser):
def getlist(self,section,option):
value = self.get(section,option)
return list(filter(None, (x.strip() for x in value.splitlines())))
def getlistint(self,section,option):
return [int(x) for x in self.getlist(section,option)]
Note that there are a few things to look out for when using this technique
New lines that are items should start with whitespace (e.g. a space or a tab)
All following lines that start with whitespace are considered to be part of the previous item. Also if it has an = sign or if it starts with a ; following the whitespace.
No mention of the converters kwarg for ConfigParser() in any of these answers was rather disappointing.
According to the documentation you can pass a dictionary to ConfigParser that will add a get method for both the parser and section proxies. So for a list:
example.ini
[Germ]
germs: a,list,of,names, and,1,2, 3,numbers
Parser example:
cp = ConfigParser(converters={'list': lambda x: [i.strip() for i in x.split(',')]})
cp.read('example.ini')
cp.getlist('Germ', 'germs')
['a', 'list', 'of', 'names', 'and', '1', '2', '3', 'numbers']
cp['Germ'].getlist('germs')
['a', 'list', 'of', 'names', 'and', '1', '2', '3', 'numbers']
This is my personal favorite as no subclassing is necessary and I don't have to rely on an end user to perfectly write JSON or a list that can be interpreted by ast.literal_eval.
If you want to literally pass in a list then you can use:
ast.literal_eval()
For example configuration:
[section]
option=["item1","item2","item3"]
The code is:
import ConfigParser
import ast
my_list = ast.literal_eval(config.get("section", "option"))
print(type(my_list))
print(my_list)
output:
<type'list'>
["item1","item2","item3"]
I landed here seeking to consume this...
[global]
spys = richard.sorge#cccp.gov, mata.hari#deutschland.gov
The answer is to split it on the comma and strip the spaces:
SPYS = [e.strip() for e in parser.get('global', 'spys').split(',')]
To get a list result:
['richard.sorge#cccp.gov', 'mata.hari#deutschland.gov']
It may not answer the OP's question exactly but might be the simple answer some people are looking for.
This is what I use for lists:
config file content:
[sect]
alist = a
b
c
code :
l = config.get('sect', 'alist').split('\n')
it work for strings
in case of numbers
config content:
nlist = 1
2
3
code:
nl = config.get('sect', 'alist').split('\n')
l = [int(nl) for x in nl]
thanks.
So another way, which I prefer, is to just split the values, for example:
#/path/to/config.cfg
[Numbers]
first_row = 1,2,4,8,12,24,36,48
Could be loaded like this into a list of strings or integers, as follows:
import configparser
config = configparser.ConfigParser()
config.read('/path/to/config.cfg')
# Load into a list of strings
first_row_strings = config.get('Numbers', 'first_row').split(',')
# Load into a list of integers
first_row_integers = [int(x) for x in config.get('Numbers', 'first_row').split(',')]
This method prevents you from needing to wrap your values in brackets to load as JSON.
I completed similar task in my project with section with keys without values:
import configparser
# allow_no_value param says that no value keys are ok
config = configparser.ConfigParser(allow_no_value=True)
# overwrite optionxform method for overriding default behaviour (I didn't want lowercased keys)
config.optionxform = lambda optionstr: optionstr
config.read('./app.config')
features = list(config['FEATURES'].keys())
print(features)
Output:
['BIOtag', 'TextPosition', 'IsNoun', 'IsNomn']
app.config:
[FEATURES]
BIOtag
TextPosition
IsNoun
IsNomn
Only primitive types are supported for serialization by config parser. I would use JSON or YAML for that kind of requirement.
To take Grr's answer (my favorite) a step further, instead of enclosing list items in quotes in the .ini file, you can use the map function. This allows you to pythonically specify list item datatypes.
Config file:
[section]
listKey1: 1001, 1002, 1003
listKey2: AAAA, BBBB, CCCC
Code:
cfgFile = 'config.ini'
parser = ConfigParser(converters={'list': lambda x: [i.strip() for i in x.split(',')]})
parser.read(cfgFile)
list1 = list(map(int, parser.getlist('section', 'listKey1')))
list2 = list(map(str, parser.getlist('section', 'listKey2')))
print(list1)
print(list2)
Output:
[1001, 1002, 1003]
['AAAA', 'BBBB', 'CCCC']
If this is your config.ini:
[Section 3]
barList=item1,item2
Then with configparser you could do this:
from configparser import ConfigParser
config = ConfigParser()
config.read('config.ini')
my_list = config['Section 3']['barList'].split(',')
You will get:
my_list = ['item1', 'item2']
The split()-method will return a list, see Python string docs.
If you have white spaces in your config.ini like this:
[Section 3]
barList= item1, item2
Then you'd better do this:
my_list = [x.strip() for x in config['Section 3']['barList'].split(',')]
If your items are numbers (integers for instance), just apply:
my_list_of_ints = list(map(int, my_list))
You will get:
my_list_of_ints = [item1, item2]
I faced the same problem in the past. If you need more complex lists, consider creating your own parser by inheriting from ConfigParser. Then you would overwrite the get method with that:
def get(self, section, option):
""" Get a parameter
if the returning value is a list, convert string value to a python list"""
value = SafeConfigParser.get(self, section, option)
if (value[0] == "[") and (value[-1] == "]"):
return eval(value)
else:
return value
With this solution you will also be able to define dictionaries in your config file.
But be careful! This is not as safe: this means anyone could run code through your config file. If security is not an issue in your project, I would consider using directly python classes as config files. The following is much more powerful and expendable than a ConfigParser file:
class Section
bar = foo
class Section2
bar2 = baz
class Section3
barList=[ item1, item2 ]
import ConfigParser
import os
class Parser(object):
"""attributes may need additional manipulation"""
def __init__(self, section):
"""section to retun all options on, formatted as an object
transforms all comma-delimited options to lists
comma-delimited lists with colons are transformed to dicts
dicts will have values expressed as lists, no matter the length
"""
c = ConfigParser.RawConfigParser()
c.read(os.path.join(os.path.dirname(__file__), 'config.cfg'))
self.section_name = section
self.__dict__.update({k:v for k, v in c.items(section)})
#transform all ',' into lists, all ':' into dicts
for key, value in self.__dict__.items():
if value.find(':') > 0:
#dict
vals = value.split(',')
dicts = [{k:v} for k, v in [d.split(':') for d in vals]]
merged = {}
for d in dicts:
for k, v in d.items():
merged.setdefault(k, []).append(v)
self.__dict__[key] = merged
elif value.find(',') > 0:
#list
self.__dict__[key] = value.split(',')
So now my config.cfg file, which could look like this:
[server]
credentials=username:admin,password:$3<r3t
loggingdirs=/tmp/logs,~/logs,/var/lib/www/logs
timeoutwait=15
Can be parsed into fine-grained-enough objects for my small project.
>>> import config
>>> my_server = config.Parser('server')
>>> my_server.credentials
{'username': ['admin'], 'password', ['$3<r3t']}
>>> my_server.loggingdirs:
['/tmp/logs', '~/logs', '/var/lib/www/logs']
>>> my_server.timeoutwait
'15'
This is for very quick parsing of simple configs, you lose all ability to fetch ints, bools, and other types of output without either transforming the object returned from Parser, or re-doing the parsing job accomplished by the Parser class elsewhere.
json.loads & ast.literal_eval seems to be working but simple list within config is treating each character as byte so returning even square bracket....
meaning if config has fieldvalue = [1,2,3,4,5]
then config.read(*.cfg)
config['fieldValue'][0] returning [ in place of 1
As mentioned by Peter Smit (https://stackoverflow.com/a/11866695/7424596)
You might want to extend ConfigParser, in addition, an Interpolator can be used to automatically convert into and from the list.
For reference at the bottom you can find code which automatically converts config like:
[DEFAULT]
keys = [
Overall cost structure, Capacity, RAW MATERIALS,
BY-PRODUCT CREDITS, UTILITIES, PLANT GATE COST,
PROCESS DESCRIPTION, AT 50% CAPACITY, PRODUCTION COSTS,
INVESTMENT, US$ MILLION, PRODUCTION COSTS, US ¢/LB,
VARIABLE COSTS, PRODUCTION COSTS, MAINTENANCE MATERIALS
]
So if you request keys you will get:
<class 'list'>: ['Overall cost structure', 'Capacity', 'RAW MATERIALS', 'BY-PRODUCT CREDITS', 'UTILITIES', 'PLANT GATE COST', 'PROCESS DESCRIPTION', 'AT 50% CAPACITY', 'PRODUCTION COSTS', 'INVESTMENT', 'US$ MILLION', 'PRODUCTION COSTS', 'US ¢/LB', 'VARIABLE COSTS', 'PRODUCTION COSTS', 'MAINTENANCE MATERIALS']
Code:
class AdvancedInterpolator(Interpolation):
def before_get(self, parser, section, option, value, defaults):
is_list = re.search(parser.LIST_MATCHER, value)
if is_list:
return parser.getlist(section, option, raw=True)
return value
class AdvancedConfigParser(ConfigParser):
_DEFAULT_INTERPOLATION = AdvancedInterpolator()
LIST_SPLITTER = '\s*,\s*'
LIST_MATCHER = '^\[([\s\S]*)\]$'
def _to_list(self, str):
is_list = re.search(self.LIST_MATCHER, str)
if is_list:
return re.split(self.LIST_SPLITTER, is_list.group(1))
else:
return re.split(self.LIST_SPLITTER, str)
def getlist(self, section, option, conv=lambda x:x.strip(), *, raw=False, vars=None,
fallback=_UNSET, **kwargs):
return self._get_conv(
section, option,
lambda value: [conv(x) for x in self._to_list(value)],
raw=raw,
vars=vars,
fallback=fallback,
**kwargs
)
def getlistint(self, section, option, *, raw=False, vars=None,
fallback=_UNSET, **kwargs):
return self.getlist(section, option, int, raw=raw, vars=vars,
fallback=fallback, **kwargs)
def getlistfloat(self, section, option, *, raw=False, vars=None,
fallback=_UNSET, **kwargs):
return self.getlist(section, option, float, raw=raw, vars=vars,
fallback=fallback, **kwargs)
def getlistboolean(self, section, option, *, raw=False, vars=None,
fallback=_UNSET, **kwargs):
return self.getlist(section, option, self._convert_to_boolean,
raw=raw, vars=vars, fallback=fallback, **kwargs)
Ps keep in mind importance of indentdation. As reads in ConfigParser doc string:
Values can span multiple lines, as long as they are indented deeper
than the first line of the value. Depending on the parser's mode, blank
lines may be treated as parts of multiline values or ignored.
you can use list in config file then parse it in python
from ast import literal_eval
literal_eval("[1,2,3,4]")
import json
json.loads("[1,2,3,4]")
and also you can use json file behind your config file like this:
your config file :
[A]
json_dis = .example.jason
--------------------
your code :
import configparser
config = configparser.ConfigParser()
config.read('config.ini')
# getting items of section A
config.items('A')
# result is a list of key-values
An improvement on split(',') might be to treat the comma separated values as a record in a CSV file
import csv
my_list = list(csv.reader([config['Section 3']['barList']], dialect=csv.excel))[0]
You can configure a dialect to parse whatever style of CSV you like.
Related
Using Regular Expressions to Parse Based on Unique Character Sequence
I'm hoping to get some Python assistance with parsing out a column in a DataFrame that has a unique character sequence. Each record can have a variable number of parameter name/value pairings. The only way to determine where each name/value pairing ends is by looking for an equal sign and then finding the most immediate preceding comma. This gets a little tricky as some of the values will continue commas, so using a comma to parse won't always yield clean results. Example below: String NAME=TEST,TEST ID=1234,ENTRY DESCR=Verify,ENTRY CLASS=CCD,TRACE NO=124313523,12414,ENTRY DATE=210506 DESCRIPTION=TEST,TEST,TEST End Result: String1 String2 String3 String4 String5 String6 NAME=TEST TEST ID=1234 ENTRY DESCR=Verify ENTRY CLASS=CCD TRACE NO=124313523,12414 ENTRY DATE=210506 DESCRIPTION=TEST,TEST,TEST Thanks in advance for your help!
This can certainly be done with Regexs, but for a quick and dirty parser I would do it manually. First a test suite: import pytest #pytest.mark.parametrize( "encoded,parsed", [ ("X=Y", {"X": "Y"}), ("DESCRIPTION=TEST,TEST,TEST", {"DESCRIPTION": "TEST,TEST,TEST"}), ( "NAME=TEST,TEST ID=1234,ENTRY DESCR=Verify,ENTRY CLASS=CCD,TRACE NO=124313523,12414,ENTRY DATE=210506", { "NAME": "TEST", "TEST ID": "1234", "ENTRY DESCR": "Verify", "ENTRY CLASS": "CCD", "TRACE NO": "124313523,12414", "ENTRY DATE": "210506", }, ), ], ) def test_parser(encoded, parsed): assert parser(encoded) == parsed You'll need to pip install pytest if you don't already have it. Then a parser: def parser(encoded: str) -> dict[str, str]: parsed = {} val = [] for token in reversed(encoded.split("=")): if val: *vals, token = token.split(",") parsed[token] = ",".join(val) val = vals else: val = token.split(",") return parsed This is not a 'proper' parser (i.e. the traditional token, lex, parse) but handles this format. It works as follows: step backwards through all the something=val pairs. split the val (this is strictly pointless, but see below) split something at the last comma (using a * expression to collect all the other components) add a new entry into the parsed dict, joining the val back up again with commas Note that this would work just as well with val = [token]. But you probably don't want a parser which returns a format which in turn needs parsing. You probably want it to turn , separated values into a list of appropriate types. Currently you have three types: strs, ints and a datetime. Thus "".join(val) could profitably be replaced with [convert(x) for x in val]. convert might look something like this: from datetime import datetime def convert(x: str) -> Union[date, int, str]: for candidate in ( lambda x: datetime.strptime(x, "%y%m%d").date(), lambda x: int(x), lambda x: x, ): try: return candidate(x) except ValueError: pass This would then be used by doing something like this in the parser: converted = [convert(x) for x in val] if len(converted) == 1: converted = converted[0] parsed[token] = converted However, this conversion function has a problem---it falsely identifies one number as a date. How exactly to fix this depends on the input data. Perhaps the date parsing function can be context-agnostic, and just check for a 6-digit input before parsing (or manually split the str and pass to datetime.date). Perhaps the decision needs to be made in the parser, based on whether the word "DATE" is in the key. If you really want to use regexs, have a look at negative lookaheads.
You can do this: def parse(s: str) -> dict: # Split by "=" and by "," raw = [x.split(",") for x in s.split("=")] # Keys are the last element of each row, besides the last keys = [k[-1] for k in raw[:-1]] # Values are all the elements before the last, shifted by one values = [",".join(k[:-1]) for k in raw[1:-1]] + [",".join(raw[-1])] return dict(zip(keys, values)) If we try: s1 = "NAME=TEST,TEST ID=1234,ENTRY DESCR=Verify,ENTRY CLASS=CCD,TRACE NO=124313523,12414,ENTRY DATE=210506" s2 = "DESCRIPTION=TEST,TEST,TEST" print(parse(s1)) print(parse(s2)) We get: >>> {'NAME': 'TEST', 'TEST ID': '1234', 'ENTRY DESCR': 'Verify', 'ENTRY CLASS': 'CCD', 'TRACE NO': '124313523,12414', 'ENTRY DATE': '210506'} >>> {'DESCRIPTION': 'TEST,TEST,TEST'}
Thanks for the suggestions, everyone! I wound up figuring a way using RegEx and did this in a two lines of code. s1 = "NAME=TEST,TEST ID=1234,ENTRY DESCR=Verify,ENTRY CLASS=CCD,TRACE NO=124313523,12414,ENTRY DATE=210506" regex=re.compile(',(?=[^,]+=)') regex.split(s1)
How to convert a list to string when retrieved from a config file? [duplicate]
The typical ConfigParser generated file looks like: [Section] bar=foo [Section 2] bar2= baz Now, is there a way to index lists like, for instance: [Section 3] barList={ item1, item2 } Related question: Python’s ConfigParser unique keys per section
I am using a combination of ConfigParser and JSON: [Foo] fibs: [1,1,2,3,5,8,13] just read it with: >>> json.loads(config.get("Foo","fibs")) [1, 1, 2, 3, 5, 8, 13] You can even break lines if your list is long (thanks #peter-smit): [Bar] files_to_check = [ "/path/to/file1", "/path/to/file2", "/path/to/another file with space in the name" ] Of course i could just use JSON, but i find config files much more readable, and the [DEFAULT] Section very handy.
There is nothing stopping you from packing the list into a delimited string and then unpacking it once you get the string from the config. If you did it this way your config section would look like: [Section 3] barList=item1,item2 It's not pretty but it's functional for most simple lists.
I recently implemented this with a dedicated section in a config file for a list: [paths] path1 = /some/path/ path2 = /another/path/ ... and using config.items( "paths" ) to get an iterable list of path items, like so: path_items = config.items( "paths" ) for key, path in path_items: #do something with path
One thing a lot of people don't know is that multi-line configuration-values are allowed. For example: ;test.ini [hello] barlist = item1 item2 The value of config.get('hello','barlist') will now be: "\nitem1\nitem2" Which you easily can split with the splitlines method (don't forget to filter empty items). If we look to a big framework like Pyramid they are using this technique: def aslist_cronly(value): if isinstance(value, string_types): value = filter(None, [x.strip() for x in value.splitlines()]) return list(value) def aslist(value, flatten=True): """ Return a list of strings, separating the input based on newlines and, if flatten=True (the default), also split on spaces within each line.""" values = aslist_cronly(value) if not flatten: return values result = [] for value in values: subvalues = value.split() result.extend(subvalues) return result Source Myself, I would maybe extend the ConfigParser if this is a common thing for you: class MyConfigParser(ConfigParser): def getlist(self,section,option): value = self.get(section,option) return list(filter(None, (x.strip() for x in value.splitlines()))) def getlistint(self,section,option): return [int(x) for x in self.getlist(section,option)] Note that there are a few things to look out for when using this technique New lines that are items should start with whitespace (e.g. a space or a tab) All following lines that start with whitespace are considered to be part of the previous item. Also if it has an = sign or if it starts with a ; following the whitespace.
No mention of the converters kwarg for ConfigParser() in any of these answers was rather disappointing. According to the documentation you can pass a dictionary to ConfigParser that will add a get method for both the parser and section proxies. So for a list: example.ini [Germ] germs: a,list,of,names, and,1,2, 3,numbers Parser example: cp = ConfigParser(converters={'list': lambda x: [i.strip() for i in x.split(',')]}) cp.read('example.ini') cp.getlist('Germ', 'germs') ['a', 'list', 'of', 'names', 'and', '1', '2', '3', 'numbers'] cp['Germ'].getlist('germs') ['a', 'list', 'of', 'names', 'and', '1', '2', '3', 'numbers'] This is my personal favorite as no subclassing is necessary and I don't have to rely on an end user to perfectly write JSON or a list that can be interpreted by ast.literal_eval.
If you want to literally pass in a list then you can use: ast.literal_eval() For example configuration: [section] option=["item1","item2","item3"] The code is: import ConfigParser import ast my_list = ast.literal_eval(config.get("section", "option")) print(type(my_list)) print(my_list) output: <type'list'> ["item1","item2","item3"]
I landed here seeking to consume this... [global] spys = richard.sorge#cccp.gov, mata.hari#deutschland.gov The answer is to split it on the comma and strip the spaces: SPYS = [e.strip() for e in parser.get('global', 'spys').split(',')] To get a list result: ['richard.sorge#cccp.gov', 'mata.hari#deutschland.gov'] It may not answer the OP's question exactly but might be the simple answer some people are looking for.
This is what I use for lists: config file content: [sect] alist = a b c code : l = config.get('sect', 'alist').split('\n') it work for strings in case of numbers config content: nlist = 1 2 3 code: nl = config.get('sect', 'alist').split('\n') l = [int(nl) for x in nl] thanks.
So another way, which I prefer, is to just split the values, for example: #/path/to/config.cfg [Numbers] first_row = 1,2,4,8,12,24,36,48 Could be loaded like this into a list of strings or integers, as follows: import configparser config = configparser.ConfigParser() config.read('/path/to/config.cfg') # Load into a list of strings first_row_strings = config.get('Numbers', 'first_row').split(',') # Load into a list of integers first_row_integers = [int(x) for x in config.get('Numbers', 'first_row').split(',')] This method prevents you from needing to wrap your values in brackets to load as JSON.
I completed similar task in my project with section with keys without values: import configparser # allow_no_value param says that no value keys are ok config = configparser.ConfigParser(allow_no_value=True) # overwrite optionxform method for overriding default behaviour (I didn't want lowercased keys) config.optionxform = lambda optionstr: optionstr config.read('./app.config') features = list(config['FEATURES'].keys()) print(features) Output: ['BIOtag', 'TextPosition', 'IsNoun', 'IsNomn'] app.config: [FEATURES] BIOtag TextPosition IsNoun IsNomn
Only primitive types are supported for serialization by config parser. I would use JSON or YAML for that kind of requirement.
To take Grr's answer (my favorite) a step further, instead of enclosing list items in quotes in the .ini file, you can use the map function. This allows you to pythonically specify list item datatypes. Config file: [section] listKey1: 1001, 1002, 1003 listKey2: AAAA, BBBB, CCCC Code: cfgFile = 'config.ini' parser = ConfigParser(converters={'list': lambda x: [i.strip() for i in x.split(',')]}) parser.read(cfgFile) list1 = list(map(int, parser.getlist('section', 'listKey1'))) list2 = list(map(str, parser.getlist('section', 'listKey2'))) print(list1) print(list2) Output: [1001, 1002, 1003] ['AAAA', 'BBBB', 'CCCC']
If this is your config.ini: [Section 3] barList=item1,item2 Then with configparser you could do this: from configparser import ConfigParser config = ConfigParser() config.read('config.ini') my_list = config['Section 3']['barList'].split(',') You will get: my_list = ['item1', 'item2'] The split()-method will return a list, see Python string docs. If you have white spaces in your config.ini like this: [Section 3] barList= item1, item2 Then you'd better do this: my_list = [x.strip() for x in config['Section 3']['barList'].split(',')] If your items are numbers (integers for instance), just apply: my_list_of_ints = list(map(int, my_list)) You will get: my_list_of_ints = [item1, item2]
I faced the same problem in the past. If you need more complex lists, consider creating your own parser by inheriting from ConfigParser. Then you would overwrite the get method with that: def get(self, section, option): """ Get a parameter if the returning value is a list, convert string value to a python list""" value = SafeConfigParser.get(self, section, option) if (value[0] == "[") and (value[-1] == "]"): return eval(value) else: return value With this solution you will also be able to define dictionaries in your config file. But be careful! This is not as safe: this means anyone could run code through your config file. If security is not an issue in your project, I would consider using directly python classes as config files. The following is much more powerful and expendable than a ConfigParser file: class Section bar = foo class Section2 bar2 = baz class Section3 barList=[ item1, item2 ]
import ConfigParser import os class Parser(object): """attributes may need additional manipulation""" def __init__(self, section): """section to retun all options on, formatted as an object transforms all comma-delimited options to lists comma-delimited lists with colons are transformed to dicts dicts will have values expressed as lists, no matter the length """ c = ConfigParser.RawConfigParser() c.read(os.path.join(os.path.dirname(__file__), 'config.cfg')) self.section_name = section self.__dict__.update({k:v for k, v in c.items(section)}) #transform all ',' into lists, all ':' into dicts for key, value in self.__dict__.items(): if value.find(':') > 0: #dict vals = value.split(',') dicts = [{k:v} for k, v in [d.split(':') for d in vals]] merged = {} for d in dicts: for k, v in d.items(): merged.setdefault(k, []).append(v) self.__dict__[key] = merged elif value.find(',') > 0: #list self.__dict__[key] = value.split(',') So now my config.cfg file, which could look like this: [server] credentials=username:admin,password:$3<r3t loggingdirs=/tmp/logs,~/logs,/var/lib/www/logs timeoutwait=15 Can be parsed into fine-grained-enough objects for my small project. >>> import config >>> my_server = config.Parser('server') >>> my_server.credentials {'username': ['admin'], 'password', ['$3<r3t']} >>> my_server.loggingdirs: ['/tmp/logs', '~/logs', '/var/lib/www/logs'] >>> my_server.timeoutwait '15' This is for very quick parsing of simple configs, you lose all ability to fetch ints, bools, and other types of output without either transforming the object returned from Parser, or re-doing the parsing job accomplished by the Parser class elsewhere.
json.loads & ast.literal_eval seems to be working but simple list within config is treating each character as byte so returning even square bracket.... meaning if config has fieldvalue = [1,2,3,4,5] then config.read(*.cfg) config['fieldValue'][0] returning [ in place of 1
As mentioned by Peter Smit (https://stackoverflow.com/a/11866695/7424596) You might want to extend ConfigParser, in addition, an Interpolator can be used to automatically convert into and from the list. For reference at the bottom you can find code which automatically converts config like: [DEFAULT] keys = [ Overall cost structure, Capacity, RAW MATERIALS, BY-PRODUCT CREDITS, UTILITIES, PLANT GATE COST, PROCESS DESCRIPTION, AT 50% CAPACITY, PRODUCTION COSTS, INVESTMENT, US$ MILLION, PRODUCTION COSTS, US ¢/LB, VARIABLE COSTS, PRODUCTION COSTS, MAINTENANCE MATERIALS ] So if you request keys you will get: <class 'list'>: ['Overall cost structure', 'Capacity', 'RAW MATERIALS', 'BY-PRODUCT CREDITS', 'UTILITIES', 'PLANT GATE COST', 'PROCESS DESCRIPTION', 'AT 50% CAPACITY', 'PRODUCTION COSTS', 'INVESTMENT', 'US$ MILLION', 'PRODUCTION COSTS', 'US ¢/LB', 'VARIABLE COSTS', 'PRODUCTION COSTS', 'MAINTENANCE MATERIALS'] Code: class AdvancedInterpolator(Interpolation): def before_get(self, parser, section, option, value, defaults): is_list = re.search(parser.LIST_MATCHER, value) if is_list: return parser.getlist(section, option, raw=True) return value class AdvancedConfigParser(ConfigParser): _DEFAULT_INTERPOLATION = AdvancedInterpolator() LIST_SPLITTER = '\s*,\s*' LIST_MATCHER = '^\[([\s\S]*)\]$' def _to_list(self, str): is_list = re.search(self.LIST_MATCHER, str) if is_list: return re.split(self.LIST_SPLITTER, is_list.group(1)) else: return re.split(self.LIST_SPLITTER, str) def getlist(self, section, option, conv=lambda x:x.strip(), *, raw=False, vars=None, fallback=_UNSET, **kwargs): return self._get_conv( section, option, lambda value: [conv(x) for x in self._to_list(value)], raw=raw, vars=vars, fallback=fallback, **kwargs ) def getlistint(self, section, option, *, raw=False, vars=None, fallback=_UNSET, **kwargs): return self.getlist(section, option, int, raw=raw, vars=vars, fallback=fallback, **kwargs) def getlistfloat(self, section, option, *, raw=False, vars=None, fallback=_UNSET, **kwargs): return self.getlist(section, option, float, raw=raw, vars=vars, fallback=fallback, **kwargs) def getlistboolean(self, section, option, *, raw=False, vars=None, fallback=_UNSET, **kwargs): return self.getlist(section, option, self._convert_to_boolean, raw=raw, vars=vars, fallback=fallback, **kwargs) Ps keep in mind importance of indentdation. As reads in ConfigParser doc string: Values can span multiple lines, as long as they are indented deeper than the first line of the value. Depending on the parser's mode, blank lines may be treated as parts of multiline values or ignored.
you can use list in config file then parse it in python from ast import literal_eval literal_eval("[1,2,3,4]") import json json.loads("[1,2,3,4]") and also you can use json file behind your config file like this: your config file : [A] json_dis = .example.jason -------------------- your code : import configparser config = configparser.ConfigParser() config.read('config.ini') # getting items of section A config.items('A') # result is a list of key-values
An improvement on split(',') might be to treat the comma separated values as a record in a CSV file import csv my_list = list(csv.reader([config['Section 3']['barList']], dialect=csv.excel))[0] You can configure a dialect to parse whatever style of CSV you like.
how to replace the values of a dict in a txt file in python
I have a text file something.txt holds data like : sql_memory: 300 sql_hostname: server_name sql_datadir: DEFAULT i have a dict parameter={"sql_memory":"900", "sql_hostname":"1234" } I need to replace the values of paramter dict into the txt file , if parameters keys are not matching from keys in txt file then values in txt should left as it is . For example, sql_datadir is not there in parameter dict . so, no change for the value in txt file. Here is what I have tried : import json def create_json_file(): with open(something.txt_path, 'r') as meta_data: lines = meta_data.read().splitlines() lines_key_value = [line.split(':') for line in lines] final_dict = {} for lines in lines_key_value: final_dict[lines[0]] = lines[1] with open(json_file_path, 'w') as foo: json.dumps(final_dict,foo, indent=4) def generate_server_file(parameters): create_json_file() with open(json_file_path, 'r') as foo: server_json_data = json.load(foo) for keys in parameters: if keys not in server_json_data: raise KeyError("Cannot find keys") # Need to update the paramter in json file # and convert json file into txt again x={"sql_memory":"900", "sql_hostname":"1234" } generate_server_file(x) Is there a way I can do this without converting the txt file into a JSON ? Expected output file(something.txt) : sql_memory: 900 sql_hostname: 1234 sql_datadir: DEFAULT Using Python 3.6
If you want to import data from a text file use numpy.genfromtxt. My Code: import numpy data = numpy.genfromtxt("something.txt", dtype='str', delimiter=';') print(data) something.txt: Name;Jeff Age;12 My Output: [['Name' 'Jeff'] ['Age' '12']] It`s very useful and I use it all of the time.
If your full example is using Python dict literals, a way to do this would be to implement a serializer and a deserializer. Since yours closely follows object literal syntax, you could try using ast.literal_eval, which safely parses a literal from a string. Notice, it will not handle variable names. import ast def split_assignment(string): '''Split on a variable assignment, only splitting on the first =.''' return string.split('=', 1) def deserialize_collection(string): '''Deserialize the collection to a key as a string, and a value as a dict.''' key, value = split_assignment(string) return key, ast.literal_eval(value) def dict_doublequote(dictionary): '''Print dictionary using double quotes.''' pairs = [f'"{k}": "{v}"' for k, v in dictionary.items()] return f'{{{", ".join(pairs)}}}' def serialize_collection(key, value): '''Serialize the collection to a string''' return f'{key}={dict_doublequote(value)}' And example using the data above produces: >>> data = 'parameter={"sql_memory":"900", "sql_hostname":"1234" }' >>> key, value = deserialize_collection(data) >>> key, value ('parameter', {'sql_memory': '900', 'sql_hostname': '1234'}) >>> serialize_collection(key, value) 'parameter={"sql_memory": "900", "sql_hostname": "1234"}' Please note you'll probably want to use JSON.dumps rather than the hack I implemented to serialize the value, since it may incorrectly quote some complicated values. If single quotes are fine, a much more preferable solution would be: def serialize_collection(key, value): '''Serialize the collection to a string''' return f'{key}={str(value)}'
Parse fasta sequence to the dictionary
I need most trivial solution to convert fasta.txt containing multiple nucleotide sequences like >seq1 TAGATTCTGAGTTATCTCTTGCATTAGCAGGTCATCCTGGTCAAACCGCTACTGTTCCGG CTTTCTGATAATTGATAGCATACGCTGCGAACCCACGGAAGGGGGTCGAGGACAGTGGTG >seq2 TCCCTCTAGAGGCTCTTTACCGTGATGCTACATCTTACAGGTATTTCTGAGGCTCTTTCA AACAGGTGCGCGTGAACAACAACCCACGGCAAACGAGTACAGTGTGTACGCCTGAGAGTA >seq3 GGTTCCGCTCTAAGCCTCTAACTCCCGCACAGGGAAGAGATGTCGATTAACTTGCGCCCA TAGAGCTCTGCGCGTGCGTCGAAGGCTCTTTTCGCGATATCTGTGTGGTCTCACTTTGGT to the dictionary(name,value) object where name will be the >header and value will be assigned to corresponded sequence. Below you can find my failed attempt do it via 2 lists (does not work for long sequence containing >1 line ) f = open('input2.txt', 'r') list={} names=[] seq=[] for line in f: if line.startswith('>'): names.append(line[1:-1]) elif line.startswith('A') or line.startswith('C') or line.startswith('G') or line.startswith('T'): seq.append(line) list = dict(zip(names, seq)) I'll be thankful if you provide me with the solution of how fix it and example how to do it via separate function. Thanks for help, Gleb
It is better to use biopython library from Bio import SeqIO input_file = open("input.fasta") my_dict = SeqIO.to_dict(SeqIO.parse(input_file, "fasta"))
a simple correction to your code: from collections import defaultdict #this will make your life simpler f = open('input2.txt','r') list=defaultdict(str) name = '' for line in f: #if your line starts with a > then it is the name of the following sequence if line.startswith('>'): name = line[1:-1] continue #this means skips to the next line #This code is only executed if it is a sequence of bases and not a name. list[name]+=line.strip() UPDATE: Since I've got a notification that this old answer was upvoted, I've decided to present what I now think is the proper solution using Python 3.7. Translation to Python 2.7 only requires removing the typing import line and the function annotations: from collections import OrderedDict from typing import Dict NAME_SYMBOL = '>' def parse_sequences(filename: str, ordered: bool=False) -> Dict[str, str]: """ Parses a text file of genome sequences into a dictionary. Arguments: filename: str - The name of the file containing the genome info. ordered: bool - Set this to True if you want the result to be ordered. """ result = OrderedDict() if ordered else {} last_name = None with open(filename) as sequences: for line in sequences: if line.startswith(NAME_SYMBOL): last_name = line[1:-1] result[last_name] = [] else: result[last_name].append(line[:-1]) for name in result: result[name] = ''.join(result[name]) return result Now, I realize that the OP asked for the "most trivial solution", however since they are working with genome data, it seems fair to assume that each sequence could potentially be very large. In that case it makes sense to optimize a little bit by collecting the sequence lines into a list, and then to use the str.join method on those lists at the end to produce the final result.
Python ConfigParser - values between quotes
When using ConfigParser module I would like to use values containing of multiple words set in cfg file. In this case seems trivial for me to surround the string with quotes like (example.cfg): [GENERAL] onekey = "value in some words" My problem is that in this case python appends the quotes to the string as well when using the value like this: config = ConfigParser() config.read(["example.cfg"]) print config.get('GENERAL', 'onekey') I am sure there is an in-built feature to manage to print only 'value in some words' instead of '"value in some words"'. How is it possible? Thanks.
I didn't see anything in the configparser manual, but you could just use the .strip method of strings to get rid of the leading and trailing double quotes. >>> s = '"hello world"' >>> s '"hello world"' >>> s.strip('"') 'hello world' >>> s2 = "foo" >>> s2.strip('"') 'foo' As you can see, .strip does not modify the string if it does not start and end with the specified string.
import ConfigParser class MyConfigParser(ConfigParser.RawConfigParser): def get(self, section, option): val = ConfigParser.RawConfigParser.get(self, section, option) return val.strip('"') if __name__ == "__main__": #config = ConfigParser.RawConfigParser() config = MyConfigParser() config.read(["example.cfg"]) print config.get('GENERAL', 'onekey')
Sorry, the solution was trivial as well - I can simply leave the quotes, it looks python simply takes the right side of equal sign.
The question is quite old already, but in 2.6 at least you don't need to use quotes as spaces are retained. from ConfigParser import RawConfigParser from StringIO import StringIO s = RawConfigParser() s.readfp(StringIO('[t]\na= 1 2 3')) s.get('t','a') > '1 2 3' That doesn't apply though either to leading or trailing spaces! If you want to retain those, you will need to enclose them in quotes an proceed as suggested. Refrain from using the eval keyword as you'll have a huge security hole.
can write configuration reading function as follows, which returns configuration in dictionary form. def config_reader(): """ Reads configuration from configuration file. """ configuration = ConfigParser.ConfigParser() configuration.read(__file__.split('.')[0] + '.cfg') config = {} for section in configuration.sections(): config[section] = {} for option in configuration.options(section): config[section][option] = (configuration.get(section, option)).strip('"').strip("'") return config
Davey, As you say you can just leave the quotes off your string. For a project I'm working on I wanted to be able to represent almost any Python string literal as a value for some of my config options and more to the point I wanted to be able to handle some of them as raw string literals. (I want that config to be able to handle things like \n, \x1b, and so on). In that case I used something like: def EvalStr(s, raw=False): r'''Attempt to evaluate a value as a Python string literal or return s unchanged. Attempts are made to wrap the value in one, then the form of triple quote. If the target contains both forms of triple quote, we'll just punt and return the original argument unmodified. Examples: (But note that this docstring is raw!) >>> EvalStr(r'this\t is a test\n and only a \x5c test') 'this\t is a test\n and only a \\ test' >>> EvalStr(r'this\t is a test\n and only a \x5c test', 'raw') 'this\\t is a test\\n and only a \\x5c test' ''' results = s ## Default returns s unchanged if raw: tmplate1 = 'r"""%s"""' tmplate2 = "r'''%s'''" else: tmplate1 = '"""%s"""' tmplate2 = "'''%s'''" try: results = eval(tmplate1 % s) except SyntaxError: try: results = eval(tmplate2 %s) except SyntaxError: pass return results ... which I think will handle anything that doesn't contain both triple-single and triple-double quoted strings. (That one corner case is way beyond my requirements). There is an oddity of this code here on SO; the Syntax highlighter seems to be confused by the fact that my docstring is a raw string. That was necessary to make doctest happy for this particular function).
I had to face the same problem. Instead of a configparser object, I prefer to work with normal dictionaries. So first I read the .ini file, then convert the configparser object to dict, and finally I remove quotes (or apostrophes) from string values. Here is my solution: preferences.ini [GENERAL] onekey = "value in some words" [SETTINGS] resolution = '1024 x 768' example.py #!/usr/bin/env python3 from pprint import pprint import preferences prefs = preferences.Preferences("preferences.ini") d = prefs.as_dict() pprint(d) preferences.py import sys import configparser import json from pprint import pprint def remove_quotes(original): d = original.copy() for key, value in d.items(): if isinstance(value, str): s = d[key] if s.startswith(('"', "'")): s = s[1:] if s.endswith(('"', "'")): s = s[:-1] d[key] = s # print(f"string found: {s}") if isinstance(value, dict): d[key] = remove_quotes(value) # return d class Preferences: def __init__(self, preferences_ini): self.preferences_ini = preferences_ini self.config = configparser.ConfigParser() self.config.read(preferences_ini) self.d = self.to_dict(self.config._sections) def as_dict(self): return self.d def to_dict(self, config): """ Nested OrderedDict to normal dict. Also, remove the annoying quotes (apostrophes) from around string values. """ d = json.loads(json.dumps(config)) d = remove_quotes(d) return d The line d = remove_quotes(d) is responsible for removing the quotes. Comment / uncomment this line to see the difference. Output: $ ./example.py {'GENERAL': {'onekey': 'value in some words'}, 'SETTINGS': {'resolution': '1024 x 768'}}
At this situation, the most simple solution is "eval()". However, you may worry about the security stuff.But you could still do this by: def literal_eval(node_or_string): """ Safely evaluate an expression node or a string containing a Python expression. The string or node provided may only consist of the following Python literal structures: strings, numbers, tuples, lists, dicts,booleans, and None. """ as a sample: import ast config = ConfigParser() config.read(["example.cfg"]) print ast.literal_eval(config.get('GENERAL', 'onekey')) # value in some words