import os
import sys
import inspect
from . config import UserConfig, MasterConfig, check_types
from . utilities import mk_lst, get_inicheck_cmd
import inicheck.checkers
from . changes import ChangeLog
[docs]def get_checkers(module='inicheck.checkers', keywords="check",
ignore=["type","generic","path"]):
"""
Args:
module: The module to search for the classes
keyword: Keywords to look for in class names
ignore: Post parsed keywords to ignore if found in a class name
Returns:
dictionary: Dict of the classes available for checking config entries
"""
keywords = mk_lst(keywords)
funcs = inspect.getmembers(sys.modules[module], inspect.isclass)
func_dict = {}
for name, fn in funcs:
checker_found = False
k = name.lower()
# Remove any keywords
for w in keywords:
z = w.lower()
if z in k:
k = k.replace(z, '')
if k not in ignore:
checker_found = True
break
if checker_found:
func_dict[k] = fn
return func_dict
[docs]def get_merged_checkers(ucfg):
"""
Retrieve the dictionary of checker classes by grabbing all in inicheck and
any prescribed in the master config through another module
Args:
ucfg: User Config object containing modules
Returns:
dictionary: all_checks - dictionary of all the checkers from inicheck
and any modules assigned to the master config.
"""
# Grab all the original
all_checks = get_checkers()
# Add any checker modules if provided
if ucfg.mcfg.checker_modules:
for c in ucfg.mcfg.checker_modules:
new_checks = get_checkers(module=c)
all_checks.update(new_checks)
return all_checks
[docs]def check_config(config_obj):
"""
Looks at the users provided config file and checks it to a master
config file looking at correctness and missing info.
Args:
config_obj - UserConfig object produced by
:class:`~inicheck.config.UserConfig`
Returns:
tuple:
- **warnings** - Returns a list of string messages that are
consider non-critical issues with config file.
- **errors** - Returns a list of string messages that are
consider critical issues with the config file.
"""
msg = "{: <20} {: <30} {: <60}"
errors = []
warnings = []
mcfg = config_obj.mcfg.cfg
cfg = config_obj.cfg
# Grab a dictionary of all the checkers
all_checks = get_merged_checkers(config_obj)
# Compare user config file to our master config
for s, configured in cfg.items():
# Section does not exists in master config
if s not in mcfg.keys():
err = "Not a valid section."
errors.append(msg.format(s, " ", err))
else:
for i in configured.keys():
# lower case item
li = i.lower()
# Item does not exist in the Master Config
if li not in mcfg[s].keys():
wrn = "Not a registered option."
warnings.append(msg.format(s, i, wrn))
else:
issues = []
fn = all_checks[mcfg[s][i].type]
b = fn(config=config_obj, item=i, section=s)
issues = b.check()
# Examine the issues
num_issues = len([True for p in issues if p != None])
for ii, issue in enumerate(issues):
if issue != None:
pi = i
# If we had a list, provide position
if num_issues > 1:
# Show 1 based lists
pi += "[{}]".format(ii + 1)
full_msg = msg.format(s, pi, issue)
if b.msg_level == 'warning':
warnings.append(full_msg)
else:
errors.append(full_msg)
return warnings, errors
[docs]def cast_all_variables(config_obj, mcfg_obj):
"""
Cast all values into the appropiate type using checkers, other_types
and the master config.
Args:
config_obj: The object of the user config from
mcfg_obj: The object used for manage the master config from
class MasterConfig
other_types: User provided list to add any custom types
Returns:
ucfg: The users config dictionary containing the correct value types
"""
ucfg = config_obj.cfg
mcfg = mcfg_obj.cfg
# Grab a dictionary of all the checkers
all_checks = get_merged_checkers(config_obj)
# Confirm checks are valid
check_types(mcfg, all_checks)
# Cast all variables
for s in ucfg.keys():
if s in mcfg.keys():
for i in ucfg[s].keys():
values = []
# Ensure it is an item we can check
if i in mcfg[s].keys():
fn = all_checks[mcfg[s][i].type]
b = fn(config=config_obj, section=s, item=i)
values = b.cast()
# Not recognized items, keep them anyways
else:
values.append(ucfg[s][i])
values = mk_lst(values, unlst=True)
# Reassign the values
ucfg[s][i] = values
config_obj.cfg = ucfg
return config_obj
[docs]def get_user_config(config_file, master_files=None, modules=None,
mcfg=None, changelog_file=None, cli=False):
"""
Returns the users config as the object UserConfig.
Args:
config_file: real path to existing config file
master_file: real path to a Core Config file
modules: a module or list of modules with a string attribute
__CoreConfig__ which is the path to a CoreConfig
mcfg: the master config object after it has been read in.
changelog_file: Path to a changlog showing any changes to the config
file the developers have made
cli: boolean determining whether to attempt to process the changes
which should be done from the CLI only
Returns:
ucfg: Users config as an object
"""
if modules == None and master_files == None and mcfg == None:
raise IOError("ERROR: Please provide either a module or a path to a"
" master config, or a master config object")
sys.exit()
if os.path.isfile(config_file):
if master_files != None or modules != None:
if master_files != None:
master_files = mk_lst(master_files)
if modules != None:
modules = mk_lst(modules)
mcfg = MasterConfig(path=master_files, modules=modules)
else:
raise IOError("Config file path {0} doesn't exist."
"".format(config_file))
# Get users config object
ucfg = UserConfig(config_file, mcfg=mcfg)
# If were not running the CLI, raise exceptions for issues
# Check out any change logs for issues
chlog = ChangeLog(paths = ucfg.mcfg.changelogs, mcfg=ucfg.mcfg)
potentials, required = chlog.get_active_changes(ucfg)
# Required Changes that broke things
if len(required) != 0 and not cli:
cmd = get_inicheck_cmd(config_file, modules=modules,
master_files=master_files)
raise ValueError("\n\nUser's Config has deprecated information and"
" needs adjustment. To see what needs to change:"
"\n\n>> {}".format(cmd))
# Fill in the gaps and make sure they're the right types
ucfg.apply_recipes()
ucfg = cast_all_variables(ucfg, mcfg)
return ucfg
[docs]def config_documentation(out_f, paths=None, modules=None,
section_link_dict={}):
"""
Auto documents the core config file. Outputs to a file which is can then be
used for documentation. Specifically formulated for sphinx
Args:
out_f: string path to output location for the auto documentation
paths: paths to master config files to use for creating docs
modules: modules with attributes __core_config__ for creating docs
section_link_dict: dictionary containing special documentation
for a section in the config file reference
"""
if paths == None and modules == None:
raise ValueError("inicheck function config_documentation args paths or"
" module must be specified!")
master = MasterConfig(path=paths, modules=modules)
mcfg = master.cfg
# Beginning
config_doc = "\nFor configuration file syntax information please visit"
config_doc += " http://inicheck.readthedocs.io/en/latest/\n\n"
for section in mcfg.keys():
# Section header
config_doc += "\n"
config_doc += "{0}\n".format(section)
config_doc += "-" * len(section) + '\n'
# Allow for custom info in a description about the section, useful
# for linking modules for auto documenting
if section in section_link_dict.keys():
intro = section_link_dict[section]
else:
intro = ''
config_doc += intro
config_doc += "\n"
# Auto document config file according to master config contents
for item,v in sorted(mcfg[section].items()):
# Check for attributes that are lists
for att in ['default', 'options']:
z = getattr(v,att)
if type(z) == list:
combo = ' '
doc_s = combo.join([str(s) for s in z])
setattr(v, att, doc_s)
# Bold item with definition
config_doc += "| **{0}**\n".format(item)
# Add the item description
config_doc += "| \t{0}\n".format(v.description)
# Default
config_doc += "| \t\t*Default: {0}*\n".format(v.default)
# Add expected type
config_doc += "| \t\t*Type: {0}*\n".format(v.type)
# Print options should they be available
if v.options:
config_doc += "| \t\t*Options:*\n *{0}*\n".format(v.options)
config_doc += "| \n"
config_doc += "\n"
path = os.path.abspath(out_f)
if not os.path.isdir(os.path.dirname(path)):
raise IOError("Config Documentation output file does not exist."
"\n{0}".format(out_f))
print("Writing auto documentation for config file to:\n{0}".format(path))
with open(path,'w+') as f:
f.writelines(config_doc)
f.close()