Source code for macsypy.config

#########################################################################
# MacSyFinder - Detection of macromolecular systems in protein dataset  #
#               using systems modelling and similarity search.          #
# Authors: Sophie Abby, Bertrand Neron                                  #
# Copyright (c) 2014-2023  Institut Pasteur (Paris) and CNRS.           #
# See the COPYRIGHT file for details                                    #
#                                                                       #
# This file is part of MacSyFinder package.                             #
#                                                                       #
# MacSyFinder is free software: you can redistribute it and/or modify   #
# it under the terms of the GNU General Public License as published by  #
# the Free Software Foundation, either version 3 of the License, or     #
# (at your option) any later version.                                   #
#                                                                       #
# MacSyFinder is distributed in the hope that it will be useful,        #
# but WITHOUT ANY WARRANTY; without even the implied warranty of        #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          #
# GNU General Public License for more details .                         #
#                                                                       #
# You should have received a copy of the GNU General Public License     #
# along with MacSyFinder (COPYING).                                     #
# If not, see <https://www.gnu.org/licenses/>.                          #
#########################################################################

"""
Module to manage both default values and configuration needed by macsyfinder
"""

import os
import shutil
from time import strftime
import logging
from configparser import ConfigParser, ParsingError

from  macsypy.model_conf_parser import ModelConfParser
_log = logging.getLogger(__name__)


[docs]class MacsyDefaults(dict): """ Handle all default values for macsyfinder. the default values must be defined here, **NOT** in argument parser nor in config the argument parser or config must use a MacsyDefaults object """
[docs] def __init__(self, **kwargs): """ :param kwargs: allow to overwrite a default value. It mainly used in unit tests To define a new default value just add an attribute with the default value """ super().__init__() self.__dict__ = self common_path = os.path.join('share', 'macsyfinder', 'models') virtual_env = os.environ.get('VIRTUAL_ENV') if virtual_env: system_models_dir = os.path.join(virtual_env, common_path) else: system_models_dir = '' # os.path.exists('') -> False prefixes = ('/', os.path.join('/', 'usr', 'local')) for root_prefix in prefixes: root_path = os.path.join(root_prefix, common_path) if os.path.exists(root_path) and os.path.isdir(root_path): system_models_dir = root_path # depending on distrib it's installed in /share or /usr/local/share # if it's installed with --user # install models in ~/.macsyfinder instead of ~/.local/share/macsyfinder self.cfg_file = kwargs.get('cfg_file', None) self.coverage_profile = kwargs.get('coverage_profile', 0.5) self.e_value_search = kwargs.get('e_value_search', 0.1) self.cut_ga = kwargs.get('cut_ga', True) self.db_type = kwargs.get('db_type', None) self.hmmer = kwargs.get('hmmer', shutil.which('hmmsearch')) self.i_evalue_sel = kwargs.get('i_evalue_sel', 0.001) self.idx = kwargs.get('idx', False) self.index_dir = kwargs.get('index_dir', None) self.inter_gene_max_space = kwargs.get('inter_gene_max_space', None) self.log_level = kwargs.get('log_level', logging.INFO) self.log_file = kwargs.get('log_file', 'macsyfinder.log') self.max_nb_genes = kwargs.get('max_nb_genes', None) self.min_genes_required = kwargs.get('min_genes_required', None) self.min_mandatory_genes_required = kwargs.get('min_mandatory_genes_required', None) self.models = kwargs.get('models', []) self.system_models_dir = kwargs.get('system_models_dir', [path for path in (system_models_dir, os.path.join(os.path.expanduser('~'), '.macsyfinder', 'models')) if os.path.exists(path)] ) self.models_dir = kwargs.get('models_dir', None) self.multi_loci = kwargs.get('multi_loci', set()) self.mute = kwargs.get('mute', False) self.out_dir = kwargs.get('out_dir', None) self.previous_run = kwargs.get('previous_run', None) self.profile_suffix = kwargs.get('profile_suffix', '.hmm') self.quiet = kwargs.get('quiet', 0) self.relative_path = kwargs.get('relative_path', False) self.replicon_topology = kwargs.get('replicon_topology', 'circular') self.res_extract_suffix = kwargs.get('res_extract_suffix', '.res_hmm_extract') self.res_search_dir = kwargs.get('res_search_dir', '.') self.res_search_suffix = kwargs.get('res_search_suffix', '.search_hmm.out') self.sequence_db = kwargs.get('sequence_db', None) self.topology_file = kwargs.get('topology_file', None) self.verbosity = kwargs.get('verbosity', 0) self.worker = kwargs.get('worker', 1) self.mandatory_weight = kwargs.get('mandatory_weight', 1.0) self.accessory_weight = kwargs.get('accessory_weight', .5) self.neutral_weight = kwargs.get('neutral_weight', 0.0) self.exchangeable_weight = kwargs.get('exchangeable_weight', .8) self.out_of_cluster_weight = kwargs.get('out_of_cluster_weight', .7) self.itself_weight = kwargs.get('itself_weight', 1.0) self.redundancy_penalty = kwargs.get('redundancy_penalty', 1.5) self.timeout = kwargs.get('timeout', 0)
[docs]class Config: """ Handle configuration values for macsyfinder. This values come from default and ar superseded by the configuration files, then the command line settings. """ cfg_opts = [('base', ('db_type', 'idx', 'replicon_topology', 'sequence_db', 'topology_file')), ('models_opt', ('inter_gene_max_space', 'max_nb_genes', 'min_mandatory_genes_required', 'min_genes_required', 'multi_loci')), ('models', tuple()), ('hmmer', ('coverage_profile', 'e_value_search', 'cut_ga', 'i_evalue_sel', 'hmmer')), ('score_opt', ('mandatory_weight', 'accessory_weight', 'neutral_weight', 'exchangeable_weight', 'itself_weight', 'redundancy_penalty', 'out_of_cluster_weight')), ('directories', ('models_dir', 'system_models_dir', 'out_dir', 'profile_suffix', 'res_search_dir', 'res_search_suffix', 'res_extract_suffix', 'index_dir')), ('general', ('cfg_file', 'log_file', 'log_level', 'previous_run', 'relative_path', 'verbosity', 'quiet', 'mute', 'worker', 'timeout')), ] model_opts = ('itself', 'exchangeable', 'mandatory', 'accessory', 'neutral', 'out_of_cluster', 'redundancy_penalty', 'e_value_search', 'e_value_sel', 'coverage_profile', 'cut_ga') path_opts = ('sequence_db', 'topology_file', 'cfg_file', 'log_file', 'models_dir', 'system_models_dir', 'out_dir', 'profile_suffix', 'res_search_dir', 'res_search_suffix', 'res_extract_suffix', 'index_dir')
[docs] def __init__(self, defaults, parsed_args): """ Store macsyfinder configuration options and propose an interface to access to them. The config object is populated in several steps, the rules of precedence are system wide conf < user home conf < model conf < (project conf | previous run) < command line system wide conf = etc/macsyfinder/macsyfinder.conf user home conf = ~/.macsyfinder/macsyfinder.conf model conf = model_conf.xml at the root of the model package project conf = macsyfinder.conf where the analysis is run previous run = macsyfinder.conf in previous run results dir command line = the options set on the command line :param defaults: :type defaults: a :class:`MacsyDefaults` object :param parsed_args: the command line arguments parsed :type parsed_args: a :class:`argspace.Namescape` object """ self._defaults = defaults self.cfg_name = "macsyfinder.conf" virtual_env = os.environ.get('VIRTUAL_ENV') if virtual_env: system_wide_config_file = os.path.join(virtual_env, 'etc', 'macsyfinder', self.cfg_name) else: # not in virtual_env var_env = os.environ.get('MACSY_CONF') if var_env: system_wide_config_file = var_env else: system_wide_config_file = os.path.join('/', 'etc', 'macsyfinder', self.cfg_name) self._options = {} self._tmp_opts = {} self._set_default_config() if os.path.exists(system_wide_config_file): self._set_system_wide_config(system_wide_config_file) user_wide_config_file = os.path.join(os.path.expanduser('~'), '.macsyfinder', self.cfg_name) if os.path.exists(user_wide_config_file): self._set_user_wide_config(user_wide_config_file) project_config_file = os.path.join(os.getcwd(), 'macsyfinder.conf') if os.path.exists(project_config_file): self._set_project_config_file(project_config_file) if hasattr(parsed_args, 'cfg_file') and parsed_args.cfg_file: user_config_file = parsed_args.cfg_file if os.path.exists(user_config_file): self._set_user_config_file(user_config_file) else: raise ValueError(f"Config file {user_config_file} not found.") previous_run = False if hasattr(parsed_args, 'previous_run') and parsed_args.previous_run: prev_config = os.path.normpath(os.path.join(parsed_args.previous_run, self.cfg_name)) previous_run = True if not os.path.exists(prev_config): raise ValueError(f"No config file found in dir {parsed_args.previous_run}") self._set_previous_run_config(prev_config) if previous_run: if hasattr(parsed_args, 'sequence_db') and parsed_args.sequence_db: _log.warning(f"ignore sequence_db '{parsed_args.sequence_db}' use sequence_db " f"from previous_run '{parsed_args.previous_run}'.") parsed_args.sequence_db = None self._set_command_line_config(parsed_args) models = self.models() if models: # the config is also used for macsydata # in this case the models are not necessary model_family_root, _ = models # create list of model_root respecting precedence model_family_root_path = [os.path.join(p, model_family_root) for p in self.models_dir()] model_family_root_path = [p for p in model_family_root_path if os.path.exists(p)] # check if the last model_family_root_path (with higher precedence) as a model config file if model_family_root_path: model_config_file = os.path.join(model_family_root_path[-1], 'model_conf.xml') if os.path.exists(model_config_file): self._set_model_config(model_config_file) # superseed options (potentially in model_conf) # by the values provided by previous-run, project conf, the users on the commandline self._options.update(self._tmp_opts) # check that hmmsearch exists if not self.hmmer(): msg = "'hmmsearch' NOT found in your PATH, Please specify hmmsearch path with --hmmer opt " \ "or install 'hmmer' package." _log.critical(msg) raise ValueError(msg)
[docs] def _set_options(self, options): """ set key, value in the general config :param options: the options to specify in general config :type options: a dictionary with option name as keys and values as values """ for opt, val in options.items(): if val not in (None, [], set()): met_name = f'_set_{opt}' if hasattr(self, met_name): # config has a specific method to parse and store the value # for this option getattr(self, met_name)(val) else: # config has no method defined to set this option self._options[opt] = val
[docs] def _set_default_config(self): """ set the value comming from MacsyDefaults """ # the special methods are not used to fill with defaults values self._options = self._defaults.copy() # except for loglevel because log_level can accept int and string as value # need conversion in int which is the internal format self._options['log_level'] = self._convert_log_level(self._defaults.log_level)
[docs] def _set_system_wide_config(self, config_path): """ set the options from the system wide configuration file :param str config_path: """ system_wide_config = self._config_file_2_dict(config_path) if 'models_dir' in system_wide_config: # models_dir cannot be modified in general configuration # set system_model_dir instead del system_wide_config['models_dir'] self._set_options(system_wide_config)
[docs] def _set_user_wide_config(self, config_path): """ Set the options from the ~/.macsyfinder/macsyfinder.conf file :param str config_path: The path to the ~/.macsyfinder/macsyfinder.conf """ user_wide_config = self._config_file_2_dict(config_path) self._set_options(user_wide_config)
[docs] def _set_model_config(self, model_conf_path): """ Set the options from the model package model_conf.xml file :param str model_conf_path: The path to the model_conf.xml file """ mcp = ModelConfParser(model_conf_path) model_conf = mcp.parse() self._set_options(model_conf)
[docs] def _set_project_config_file(self, config_path): """ Set the options from the macsyfinder.conf present in the current directory :param str config_path: the path to the configuration file """ project_config = self._config_file_2_dict(config_path) for opt in self.model_opts: if opt in project_config: self._tmp_opts[opt] = project_config[opt] del project_config[opt] self._set_options(project_config)
[docs] def _set_user_config_file(self, config_path): """ Set the options specified by the user on the command line via the --cfg-file option :param str config_path: The path to the configuration path """ user_config = self._config_file_2_dict(config_path) for opt in self.model_opts: if opt in user_config: self._tmp_opts[opt] = user_config[opt] del user_config[opt] self._set_options(user_config)
[docs] def _set_previous_run_config(self, prev_config_path): """ Set the options specified by the user on the command line via --previous-run :param prev_config_path: """ previous_conf = self._config_file_2_dict(prev_config_path) if 'out_dir' in previous_conf: # set the out_dir from the previous_run is a non sense del previous_conf['out_dir'] for opt in self.model_opts: if opt in previous_conf: self._tmp_opts[opt] = previous_conf[opt] del previous_conf[opt] self._set_options(previous_conf)
[docs] def _set_command_line_config(self, parsed_args): """ :param parsed_args: the argument set on the command line :type parsed_args: :class:`argparse.Namespace` object. """ # do not iter on args special attribute # do not set option if the value is None (not specified on command line) args_dict = {k: v for k, v in vars(parsed_args).items() if not k.startswith('__') and v is not None} for opt in self.model_opts: if opt in args_dict: self._tmp_opts[opt] = args_dict[opt] del args_dict[opt] self._set_options(args_dict)
[docs] def _config_file_2_dict(self, file): """ Parse a configuration file in ini format in dictionnary :param str file: path to the configuration file :return: the parsed options :rtype: dict """ parser = ConfigParser() parse_meth = {int: parser.getint, float: parser.getfloat, bool: parser.getboolean } try: parser.read([file]) _log.debug(f"Configuration file {file} parsed.") except ParsingError as err: raise ParsingError(f"The macsyfinder configuration file '{file}' is not well formed: {err}") from None opts = {} sections = list(parser.sections()) for section in sections: for option in parser.options(section): opt_type = type(self._defaults.get(option, None)) if option == 'log_level': self._set_log_level(parser.get(section, option)) else: try: opt_value = parse_meth.get(opt_type, parser.get)(section, option) except (ValueError, TypeError) as err: raise ValueError(f"Invalid value in config_file for option '{option}': {err}") opts[option] = opt_value return opts
def __getattr__(self, option_name): # some getter return just a value they can be transformed in property # but some other need extra argument so they cannot be a property, they must be methods # to have something generic and with the same behavior # that mean need to call all of them # for generic getter, that mean no code in config # I simulate a function (lambda) which can be called without argument if option_name in self._options: return lambda: self._options[option_name] else: raise AttributeError(f"config object has no attribute '{option_name}'")
[docs] def _str_2_tuple(self, value): """ transform a string with syntax {model_fqn int} in list of tuple :param str value: the string to parse :return: :rtype: [(model_fqn, int), ...] """ try: it = iter(value.split()) res = [(a, next(it)) for a in it] return res except StopIteration: raise ValueError(f"You must provide a list of model name and value separated by spaces: {value}")
[docs] def save(self, path_or_buf=None): """ save itself in a file in ini format. .. note:: the undefined options (set to None) are omitted :param path_or_buf: where to serialize itself. :type path_or_buf: str or file like object """ def serialize(): conf_str = '' for section, options in self.cfg_opts: conf_str += f"[{section}]\n" if section == 'models': # [(model_family, (def_name1, ...)), ... ] model_family, model_names = self.models() conf_str += f"models = {model_family} {' '.join(model_names)}\n" else: for opt in options: opt_value = self._options[opt] if opt_value in (None, [], set()): continue elif isinstance(opt_value, dict): value = "" for model, v in opt_value.items(): value += f"{model} {v} " opt_value = value elif isinstance(opt_value, (set, list)): opt_value = ', '.join(opt_value) elif opt_value in self.path_opts and self.relative_path: opt_value = os.path.relpath(opt_value) conf_str += f"{opt} = {opt_value}\n" return conf_str if path_or_buf is None: path_or_buf = os.path.join(self.out_dir(), self.cfg_name) if isinstance(path_or_buf, str): with open(path_or_buf, 'w') as cfg_file: print(serialize(), file=cfg_file) else: print(serialize(), file=path_or_buf)
[docs] def _set_db_type(self, value): """ set value for 'db_type' option :param str value: the value for db_type, allowed values are : 'ordered_replicon', 'gembase', 'unordered' :raise ValueError: if value is not allowed """ auth_values = ('ordered_replicon', 'gembase', 'unordered') if value in auth_values: self._options['db_type'] = value else: raise ValueError(f"db_type as unauthorized value : '{value}'.")
[docs] def _set_inter_gene_max_space(self, value): """ set value for 'inter_gene_max_space' option :param str value: the string parse representing the model fully qualified name and it's associated value and so on the model_fqn is a string, the associated value must be cast in int :raise ValueError: if value is not well formed """ opt = {} if isinstance(value, str): try: value = self._str_2_tuple(value) except ValueError as err: raise ValueError(f"Invalid syntax for 'inter_gene_max_space': {err}.") for model_fqn, quorum in value: try: opt[model_fqn] = int(quorum) except ValueError: raise ValueError(f"The value for 'inter_gene_max_space' option for model {model_fqn} must be an integer" f", but you provided {quorum}") self._options['inter_gene_max_space'] = opt
[docs] def inter_gene_max_space(self, model_fqn): """ :param str model_fqn: the model fully qualifed name :return: the gene_max_space for the model_fqn or None if it's does not specify :rtype: int or None """ if self._options['inter_gene_max_space']: return self._options['inter_gene_max_space'].get(model_fqn, None) else: return None
[docs] def _set_max_nb_genes(self, value): """ set value for 'max_nb_genes' option :param str value: the string parse representing the model fully qualified name and it's associated value and so on the model_fqn is a string, the associated value must be cast in int :raise ValueError: if value is not well formed """ opt = {} if isinstance(value, str): try: value = self._str_2_tuple(value) except ValueError as err: raise ValueError(f"Invalid syntax for 'max_nb_genes': {err}.") for model_fqn, quorum in value: try: opt[model_fqn] = int(quorum) except ValueError: raise ValueError(f"The value for 'max_nb_genes' option for model {model_fqn} must be an integer, " f"but you provided {quorum}") self._options['max_nb_genes'] = opt
[docs] def max_nb_genes(self, model_fqn): """ :param str model_fqn: the model fully qualifed name :return: the max_nb_genes for the model_fqn or None if it's does not specify :rtype: int or None """ if self._options['max_nb_genes']: return self._options['max_nb_genes'].get(model_fqn, None) else: return None
[docs] def _set_min_genes_required(self, value): """ set value for 'min_genes_required' option :param str value: the string parse representing the model fully qualified name and it's associated value and so on the model_fqn is a string, the associated value must be cast in int :raise ValueError: if value is not well formed """ opt = {} if isinstance(value, str): try: value = self._str_2_tuple(value) except ValueError as err: raise ValueError(f"Invalid syntax for 'min_genes_required': {err}.") for model_fqn, quorum in value: try: opt[model_fqn] = int(quorum) except ValueError: raise ValueError(f"The value for 'min_genes_required' option for model {model_fqn} must be an integer, " f"but you provided {quorum}") self._options['min_genes_required'] = opt
[docs] def min_genes_required(self, model_fqn): """ :param str model_fqn: the model fully qualifed name :return: the min_genes_required for the model_fqn or None if it's does not specify :rtype: int or None """ if self._options['min_genes_required']: return self._options['min_genes_required'].get(model_fqn, None) else: return None
[docs] def _set_min_mandatory_genes_required(self, value): """ set value for 'min_mandatory_genes_required' option :param str value: the string parse representing the model fully qualified name and it's associated value and so on the model_fqn is a string, the associated value must be cast in int :raise ValueError: if value is not well formed """ opt = {} if isinstance(value, str): try: value = self._str_2_tuple(value) except ValueError as err: raise ValueError(f"Invalid syntax for 'min_mandatory_genes_required': {err}.") for model_fqn, quorum in value: try: opt[model_fqn] = int(quorum) except ValueError: raise ValueError(f"The value for 'min_mandatory_genes_required' option " f"for model {model_fqn} must be an integer, but you provided {quorum}") self._options['min_mandatory_genes_required'] = opt
[docs] def min_mandatory_genes_required(self, model_fqn): """ :param str model_fqn: the model fully qualifed name :return: the min_mandatory_genes_required for the model_fqn or None if it's does not specify :rtype: int or None """ if self._options['min_mandatory_genes_required']: return self._options['min_mandatory_genes_required'].get(model_fqn, None) else: return None
[docs] def _set_models(self, value): """ :param value: The models to search as return by the command line parsing or the configuration files if value come from command_line ['model1', 'def1', 'def2', 'def3'] if value come from config file ['set_1', 'T9SS T3SS T4SS_typeI')] [(model_family, [def_name1, ...]), ... ] """ if isinstance(value, str): # it comes from a config_file # value = model_family_name model1 model2 model_family_name, *models_name = value.split() else: # it come from the command line # value = ['model_family_name', 'model1', 'model2'] model_family_name = value[0] models_name = value[1:] if model_family_name and not models_name: models_name = ['all'] self._options['models'] = (model_family_name, models_name)
[docs] def out_dir(self): """ :return: the path to the directory where the results are stored """ out_dir = self._options['out_dir'] if out_dir: return out_dir else: out_dir = os.path.join(self._options['res_search_dir'], f"macsyfinder-{strftime('%Y%m%d_%H-%M-%S')}") self._options['out_dir'] = out_dir return out_dir
[docs] def working_dir(self): """ alias to :py:meth:`config.Config.out_dir` """ return self.out_dir()
[docs] def _set_replicon_topology(self, value): """ set the default replicon topology :param str value: 'circular' or 'linear' """ auth_values = ('linear', 'circular') value_low = value.lower() new_topo = None for topo in auth_values: if topo.startswith(value_low): new_topo = topo break if new_topo is not None: self._options['replicon_topology'] = new_topo else: raise ValueError(f"replicon_topology as unauthorized value : '{value}'.")
[docs] def _set_sequence_db(self, path): """ :param str path: set the path to the sequence file (in fasta format) to analysed """ if os.path.exists(path) and os.path.isfile(path): self._options['sequence_db'] = path else: raise ValueError(f"sequence_db '{path}' does not exists or is not a file.")
[docs] def _set_topology_file(self, path): """ test if the path exists and set it in config :param str path: the path to the topology file """ if os.path.exists(path) and os.path.isfile(path): self._options['topology_file'] = path else: raise ValueError(f"topology_file '{path}' does not exists or is not a file.")
[docs] def _set_system_models_dir(self, value): """ :param value: the path of the models dir set by the system (vs set by the user) :type value: list of string or a single string :return: """ if isinstance(value, str): # it comes from a config_file # value = path1, path2 value = value.split(', ') self._options['system_models_dir'] = value
[docs] def _set_models_dir(self, path): """ :param str path: the path to the models (definitions + profiles) are stored. """ # if models_dir is provide by the user this value mask canonical ones # prefix_data, 'models' # os.path.expanduser('~'), '.macsyfinder', 'data' # models_dir must return a list of path if os.path.exists(path) and os.path.isdir(path): self._options['models_dir'] = [path] else: raise ValueError(f"models_dir '{path}' does not exists or is not a directory.")
[docs] def _set_multi_loci(self, value): """ :param str value: the models fqn list separated by comma of multi loc models """ models_fqn = {v for v in [v.strip() for v in value.split(',')] if v} self._options['multi_loci'] = set(models_fqn)
def _convert_log_level(self, value): try: log_level = int(value) except ValueError: value = value.upper() try: log_level = getattr(logging, value) except AttributeError: raise ValueError(f"Invalid value for log_level: {value}.") from None return log_level
[docs] def _set_log_level(self, value): """ :param value: :return: """ self._options['log_level'] = self._convert_log_level(value)
[docs] def _set_no_cut_ga(self, value): """ :param value: :type value: :return: :rtype: """ self._options['cut_ga'] = not value
[docs] def models_dir(self): """ :return: list of models dir path :rtype: list of str """ if self._options['models_dir']: # the models_dir has been set by user return self._options['models_dir'] else: # use canonical location return self._options['system_models_dir']
[docs] def multi_loci(self, model_fqn): """ :param str model_fqn: the model fully qualified name :return: True if the model is multi loci, False otherwise :rtype: bool """ return model_fqn in self._options['multi_loci']
[docs] def hmmer_dir(self): """ :return: The name of the directory containing the hmmsearch results (output, error, parsing) """ return 'hmmer_results'
[docs] def hit_weights(self): """ :return: the options used in scoring systems (mandatory_weight, accessory_weight, itself_weight, exchangeable_weight, out_of_cluster_weight) :rtype: dict """ return {'mandatory': self._options['mandatory_weight'], 'accessory': self._options['accessory_weight'], 'neutral': self._options['neutral_weight'], 'itself': self._options['itself_weight'], 'exchangeable': self._options['exchangeable_weight'], 'out_of_cluster': self._options['out_of_cluster_weight'] }
[docs] def log_level(self): """ :return: the verbosity output level :rtype: int """ level = self._options['log_level'] - (10 * self.verbosity()) + (10 * self.quiet()) level = min(50, max(10, level)) return level
[docs]class NoneConfig: """ Minimalist Config object just use in some special case wher config is require by api but not used for instance in :class:`macsypy.package.Package` """ def __getattr__(self, prop): if prop in ('multi_loci', 'min_mandatory_genes_required', 'max_nb_genes', 'inter_gene_max_space', 'min_genes_required'): return lambda x: None else: return lambda: None