from text_data import text_data
import os
import ast
[docs]class ioconfig(text_data):
"""
An ioconfig object is an extension to the text_data_class
it has the same methods of text_data_class, plus an add_param function
and simplified ``write()``, and ``read()`` methods for making visually
formatted csv files so users can edit config files directly.
It is used to generate "config" files, lists of important inputs to a set
of complex functions. Allows you to save inputs and call them over and over
again to avoid tedious input definitions, also facilitates good record keeping
by keeping a hard file with a summary of all inputs alongside any outputs that
might have been generated.
When a param has been added or imported, its value may be accessed with
.. code-block:: python
ioconfig_object['param_name']
:param input_filepath: Optional filepath to read/write this ioconfig object to/from.
If you do not provide a filepath here, you can provide one
when invoking the ``read()`` and ``write()`` methods.
"""
def __init__(self, input_filepath = None):
""" overrides parents __init__ """
self.headers = ["param_name", "param_type", "param_value"]
self.row_data = [] # standard text data object row_data formating
self.conf_dict = {} # config dict of {param_name: param_value}
self.name_len = 1 # number of characters reserved for param names
self.type_len = 16 # number of characters reserved for param types
if input_filepath and os.path.exists(input_filepath):
self.read(input_filepath)
def __getitem__(self, index):
"""
Overrides parent __getitem__. Causes the following external syntax
to behave identically
.. code-blocK:: python
param = ioconfig_object.conf_dict["param_name"]
param = ioconfig_object["param_name"]
:param index: the key for conf_dict
:return value: the value corresponding to input key
"""
return self.conf_dict[index]
def __iter__(self):
""" functions as a generator to iterate through an ioconfig object """
for row in self.row_data:
yield row[0].strip()
[docs] def add_param(self, param_names, param_values):
"""
Adds parameters to the ioconfig object
:param param_names: A string, the name of the parameter
:param param_values: the value of the parameter. May be any built
in datatype. (bool, int, str, list, long, etc)
when a param has been added or imported, its value may be accessed with
.. code-block:: python
ioconfig_object['param_name']
"""
# avoid indexing errors by handling list inputs differently
if isinstance(param_names, list):
for i,param in enumerate(param_names):
# make sure columns are wide enough for visual readability
if len(param) > self.name_len:
self.name_len = len(param)
entry = [param_names[i], str(type(param_values[i])), param_values[i]]
self.row_data.append(entry)
else:
entry = [param_names, str(type(param_values)), param_values]
self.row_data.append(entry)
return
[docs] def write(self, filepath):
"""
Prepares the row data with nice visual formatting then calls
the ``text_data.write_csv`` method.
:param filepath: filepath to write csv to. (.txt, .csv, etc)
"""
# create write_rows variable with extra spaces for easy reading of csv
write_headers = [self.headers[0].ljust(self.name_len),
self.headers[1].ljust(self.type_len),
self.headers[2]]
write_rows = []
for row in self.row_data:
write_rows.append([row[0].ljust(self.name_len),
row[1].ljust(self.type_len),
row[2]])
write_tdo = text_data(headers = write_headers, row_data = write_rows)
write_tdo.write_csv(filepath, delim = " ; ")
return
[docs] def read(self, filepath):
"""
Reads the contents of a csv file generated by ``ioconfig.write()``,
interprets and formats each of the variables to the state it was before
exporting. Once finished, this the ioconfig object will have all
its standard attributes including ``conf_dict``.
:param filepath: filepath to a csv or txt file generated by the
``ioconfig.write`` method.
"""
self.read_csv(filepath, delim = " ; ")
for i, row in enumerate(self.row_data):
param_name = row[0].strip()
# allow display width to adjust to longest parameter name
if len(param_name) > self.name_len:
self.name_len = len(param_name)
self.row_data[i][2] = self._interp(row[1], row[2])
self.conf_dict[param_name] = row[2]
# prints a summary of the read operation
print("Read from config file '{0}'".format(filepath))
print("{0} : Values".format("Keys".ljust(self.name_len)))
for key, value in self.conf_dict.iteritems():
print("{0} : {1}".format(key.ljust(self.name_len),value))
return
[docs] def _interp(self, in_type, in_value):
""" allows interpretation of config values based on type """
in_type = in_type.strip()
if "str" in in_type:
return str(in_value)
else:
try: return ast.literal_eval(in_value)
except: raise TypeError("could not interpret input'{0}'".format(in_type))
# testing area
if __name__ == "__main__":
test_names = ["test long title for just one single str",
"test_bool",
"test_float",
"test_int",
"test_long",
#"test_complex",
"test_list",
"test_dict",
"test_tuple"]
test_vals = ["some test string",
True,
1.12345,
1,
1000000000000000000,
#1 + 1j,
["a","b",75,"d"],
{"string_key" : 0, 1: "string_value"},
(1, 2)]
conf = ioconfig()
conf.add_param(test_names, test_vals)
conf.write("test_data/conf_test.txt")
conf.write_json("test_data/conf_test.json", row_wise = True)
del conf
conf = ioconfig("test_data/conf_test.txt")