#!/usr/bin/env python
# Contributors:
# Christopher P. Barnes <senrabc@gmail.com>
# Andrei Sura: github.com/indera
# Mohan Das Katragadda <mohan.das142@gmail.com>
# Philip Chase <philipbchase@gmail.com>
# Ruchi Vivek Desai <ruchivdesai@gmail.com>
# Taeber Rapczak <taeber@ufl.edu>
# Nicholas Rejack <nrejack@ufl.edu>
# Josh Hanna <josh@hanna.io>
# Copyright (c) 2015, University of Florida
# All rights reserved.
#
# Distributed under the BSD 3-Clause License
# For full text of the BSD 3-Clause License see http://opensource.org/licenses/BSD-3-Clause
"""
SimpleConfigParser
Simple configuration file parser: Python module to parse configuration files
without sections. Based on ConfigParser from the standard library.
Author: Philippe Lagadec
Project website: http://www.decalage.info/python/configparser
Inspired from an idea posted by Fredrik Lundh:
http://mail.python.org/pipermail/python-dev/2002-November/029987.html
Usage: see end of source code and http://docs.python.org/library/configparser.html
"""
__author__ = 'Philippe Lagadec'
__version__ = '0.02'
#--- LICENSE ------------------------------------------------------------------
# The SimpleConfigParser Python module is copyright (c) Philippe Lagadec 2009-2010
#
# By obtaining, using, and/or copying this software and/or its associated
# documentation, you agree that you have read, understood, and will comply with
# the following terms and conditions:
#
# Permission to use, copy, modify, and distribute this software and its
# associated documentation for any purpose and without fee is hereby granted,
# provided that the above copyright notice appears in all copies, and that both
# that copyright notice and this permission notice appear in supporting
# documentation, and that the name of the author not be used in advertising or
# publicity pertaining to distribution of the software without specific,
# written prior permission.
#
# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
# THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#-------------------------------------------------------------------------
# CHANGELOG
# 2009-02-12 v0.01 PL: - initial version
# 2010-03-15 v0.02 PL: - updated tests and comments
#-------------------------------------------------------------------------
# TODO:
# - implement read() using the base class code
#=== IMPORTS ==================================================================
import ConfigParser
import StringIO
import logging
import os
import sys
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
#=== CONSTANTS ================================================================
# section name for options without section:
NOSECTION = 'NOSECTION'
DEFAULT_MESSAGE_NO_VALUE = "Required parameter '{0}' does not have a"\
" value in {1}."
DEFAULT_MESSAGE = "\nPlease set it with the appropriate value. Refer to "\
"config-example/settings.ini for assistance.\nProgram "\
"will now terminate..."
# Dictionary containing required file-related parameters along with custom
# message to be displayed in case of error
required_files_dict = {
"raw_xml_file": "\nIt should specify the name of the file containing raw"\
" data. ",
"translation_table_file": "\nIt should specify the name of the required "\
"xml file containing translation table. ",
"form_events_file": "\nIt should specify the name of the required xml "\
"file containing empty form events. ",
"research_id_to_redcap_id": "\nIt should specify the name of the xml "\
"file containing mapping of research ids to redcap ids. ",
"component_to_loinc_code_xml": "\nIt should specify the name of the "\
"required xml file containing a mapping of clinical component ids to "\
"loinc codes. "
}
required_server_parameters_list = [
'redcap_uri',
'token',
'redcap_support_receiver_email',
'smtp_host_for_outbound_mail',
'smtp_port_for_outbound_mail',
'emr_sftp_server_hostname',
'emr_sftp_server_username',
'emr_sftp_project_name',
'emr_data_file']
# Dictionary containing optional parameters along with their default values
optional_parameters_dict = {
"report_file_path": "report.xml",
"input_date_format": "%Y-%m-%d %H:%M:%S",
"output_date_format": "%Y-%m-%d",
"report_file_path2": "report.html",
"sender_email": "please-do-not-reply@example.com",
"project": "DEFAULT_PROJECT",
"rules": {},
"preprocessors": {},
"batch_warning_days": 13,
"rate_limiter_value_in_redcap": 600,
"batch_info_database": "redi.db",
"send_email": 'N',
"receiver_email": "test@example.com",
"verify_ssl": True,
"replace_fields_in_raw_data_xml": None,
"include_rule_errors_in_report": False,
"redcap_support_sender_email": 'please-do-not-reply@example.com',
"emr_sftp_server_port": 22,
"emr_sftp_server_password": None,
"emr_sftp_server_private_key": None,
"emr_sftp_server_private_key_pass": None,
"is_sort_by_lab_id": True,
"max_retry_count": 10,
}
[docs]class ConfigurationError(Exception):
pass
#=== CLASSES ==================================================================
[docs]class SimpleConfigParser(ConfigParser.RawConfigParser):
"""
Simple configuration file parser: based on ConfigParser from the standard
library, slightly modified to parse configuration files without sections.
Inspired from an idea posted by Fredrik Lundh:
http://mail.python.org/pipermail/python-dev/2002-November/029987.html
"""
[docs] def read(self, filename):
if not os.path.exists(filename):
logger.exception("Cannot find settings file: {0}. Program will "\
"now terminate...".format(filename))
sys.exit()
self.filename = filename
text = open(filename).read()
f = StringIO.StringIO("[%s]\n" % NOSECTION + text)
self.readfp(f, filename)
[docs] def getoption(self, option):
'get the value of an option'
opt_as_string = self.get(NOSECTION, option)
try:
# if the conversion to boolean fails we keep the string value
opt_as_bool = to_bool(opt_as_string)
return opt_as_bool
except ValueError:
pass
return opt_as_string
[docs] def getoptionslist(self):
'get a list of available options'
return self.options(NOSECTION)
[docs] def hasoption(self, option):
"""
return True if an option is available, False otherwise.
(NOTE: do not confuse with the original has_option)
"""
return self.has_option(NOSECTION, option)
[docs] def set_attributes(self):
# Check if configuration file is empty
if not self.getoptionslist():
message = "ERROR: Configuration file '{0}' is empty! Program "\
"will now terminate...".format(self.filename)
logger.error(message)
sys.exit()
else:
self.check_parameters()
[docs] def check_parameters(self):
"""
handle required and default optional_parameters_dict
"""
# check for required file parameters
# handled separately as these need a custom message to be displayed
for option in required_files_dict:
if not self.hasoption(option) or self.getoption(option) == "":
message = DEFAULT_MESSAGE_NO_VALUE.format(option, \
self.filename) + required_files_dict[option] +\
DEFAULT_MESSAGE
logger.error(message)
sys.exit()
else:
setattr(self, option, self.getoption(option))
# check for required server and emr parameters
for option in required_server_parameters_list:
if not self.hasoption(option) or self.getoption(option) == "":
message = DEFAULT_MESSAGE_NO_VALUE.format(option, \
self.filename) + DEFAULT_MESSAGE
logger.error(message)
sys.exit()
else:
logger.debug("Setting required parameter {} = {} "\
.format(option, self.getoption(option)))
setattr(self, option, self.getoption(option))
# check for receiver email if send_email = 'Y'
if self.hasoption('send_email') and self.getoption('send_email'):
if not self.hasoption('receiver_email') or \
self.getoption('receiver_email') == "":
message = DEFAULT_MESSAGE_NO_VALUE.format(option, \
self.filename) + DEFAULT_MESSAGE
logger.error(message)
sys.exit()
# set optional parameters with default values if missing
for option in optional_parameters_dict:
if not self.hasoption(option) or self.getoption(option) == "":
logger.warn("Parameter '{0}' in {1} does not have"\
" a value. Default value '{2}' applied.".format(option, \
self.filename, optional_parameters_dict[option]))
setattr(self, option, optional_parameters_dict[option])
else:
setattr(self, option, self.getoption(option))
#=== End class ================================================================
[docs]def to_bool(value):
"""
Helper function for translating strings into booleans
@see test/TestReadConfig.py
"""
valid = {
'true': True, 't': True, '1': True, 'y' : True,
'false': False, 'f': False, '0': False, 'n' : False
}
if not isinstance(value, str):
raise ValueError('Cannot check boolean value. Not a string.')
lower_value = value.lower()
if lower_value in valid:
return valid[lower_value]
else:
raise ValueError('Not a boolean string: "%s"' % value)
#=== MAIN =====================================================================
if __name__ == '__main__':
# simple tests when launched as a script instead of imported as module:
##cp = ConfigParser.ConfigParser()
# this raises an exception:
# ConfigParser.MissingSectionHeaderError: File contains no section headers.
# cp.read('config_without_section.ini')
print 'SimpleConfigParser tests:'
filename = 'sample_config_no_section.ini'
cp = SimpleConfigParser()
print 'Parsing %s...' % filename
cp.read(filename)
print 'Sections:', cp.sections()
# print cp.items(NOSECTION)
print 'getoptionslist():', cp.getoptionslist()
for option in cp.getoptionslist():
print "getoption('%s') = '%s'" % (option, cp.getoption(option))
print "hasoption('wrongname') =", cp.hasoption('wrongname')
print
print "Print out options by attribute instead of recursing the list"
cp.set_attributes()
print cp.option1
print cp.option2