<commit_before>""" Read configuration settings from files, environment variables, and function arguments

:Author: <NAME> <<EMAIL>>
:Date: 2016-10-25
:Copyright: 2016-2018, Karr Lab
:License: MIT
"""

from configobj import ConfigObj
from configobj import flatten_errors, get_extra_values
from validate import Validator, is_boolean, is_float, is_integer, is_list, is_string, VdtTypeError
from wc_utils.util.dict import DictUtil
from copy import deepcopy
import math
import os
import pkg_resources
import six
import string
import sys


class ConfigPaths(object):
    """ Paths to configuration files and schema

    Attributes:
        default (:obj:`str`): the default config filename
        schema (:obj:`str`): the config schema filename
        user (:obj:`list`): an iterable of other config files
    """

    def __init__(self, default=None, schema=None, user=None):
        self.default = default
        self.schema = schema
        self.user = user or ()

    def deepcopy(self):
        """ Returns a deep copy of the object

        Returns:
            :obj:`ConfigPaths`: deep copy of the object
        """
        return deepcopy(self)


class ConfigManager(object):
    """Obtain configuration information from ini files, environment variables, and/or function arguments.

    Load configuration information from an ini format file, environment variables, and/or function arguments.
    Validate the configuration against a configuration schema. Return the configuration
    as a nested dictionary.

    Optionally, configuration values can be templates for substitution with :obj:`string.Template`.

    Attributes:
        paths (:obj:`ConfigPaths`): paths to configuration files and schema
    """

    def __init__(self, paths=None):
        self.paths = paths

    def get_config(self, extra=None, context=None):
        """ Setup configuration from config file(s), environment variables, and/or function arguments.

        1. Setup configuration from default values specified in `paths.default`.
        2. If `paths.user` is set, find the first file in it that exists, and override
           the default configuration with the values specified in the file.
        3. Override configuration with values from environment variables. Environment variables
           can be set with the following syntax::

               CONFIG.level1.level2...=val

        4. Override configuration with additional configuration in `extra`.
        5. Substitute context into templates
        6. Validate configuration against the schema specified in `paths.schema`.

        Args:
            extra (:obj:`dict`, optional): additional configuration to override
            context (:obj:`dict`, optional): context for template substitution

        Returns:
            :obj:`configobj.ConfigObj`: nested dictionary with the configuration settings loaded from the configuration source(s).

        Raises:
            :obj:`InvalidConfigError`: if configuration doesn't validate against schema
            :obj:`ValueError`: if no configuration is found
        """

        # read configuration schema/specification
        config_specification = ConfigObj(self.paths.schema, list_values=False, _inspec=True)

        # read default configuration
        config = ConfigObj(infile=self.paths.default, configspec=config_specification)

        # read user's configuration files
        for user_config_filename in self.paths.user:
            if os.path.isfile(user_config_filename):
                override_config = ConfigObj(infile=user_config_filename, configspec=config_specification)
                config.merge(override_config)
                break

        # read configuration from environment variables
        for key, val in os.environ.items():
            if key.startswith('CONFIG__DOT__'):
                DictUtil.nested_set(config, key[13:].replace('__DOT__', '.'), val)

        # merge extra configuration
        if extra is None:
            extra = {}
        config.merge(extra)

        # ensure that a configuration is found
        if not config:
            raise ValueError("no config found in envt. variables or {}, {}, or {}".format(<commit_msg>preventing applying unrelated config from env vars<commit_after>""" Read configuration settings from files, environment variables, and function arguments

:Author: <NAME> <<EMAIL>>
:Date: 2016-10-25
:Copyright: 2016-2018, Karr Lab
:License: MIT
"""

from configobj import ConfigObj
from configobj import flatten_errors, get_extra_values
from validate import Validator, is_boolean, is_float, is_integer, is_list, is_string, VdtTypeError
from wc_utils.util.dict import DictUtil
from copy import deepcopy
import math
import os
import pkg_resources
import six
import string
import sys


class ConfigPaths(object):
    """ Paths to configuration files and schema

    Attributes:
        default (:obj:`str`): the default config filename
        schema (:obj:`str`): the config schema filename
        user (:obj:`list`): an iterable of other config files
    """

    def __init__(self, default=None, schema=None, user=None):
        self.default = default
        self.schema = schema
        self.user = user or ()

    def deepcopy(self):
        """ Returns a deep copy of the object

        Returns:
            :obj:`ConfigPaths`: deep copy of the object
        """
        return deepcopy(self)


class ConfigManager(object):
    """Obtain configuration information from ini files, environment variables, and/or function arguments.

    Load configuration information from an ini format file, environment variables, and/or function arguments.
    Validate the configuration against a configuration schema. Return the configuration
    as a nested dictionary.

    Optionally, configuration values can be templates for substitution with :obj:`string.Template`.

    Attributes:
        paths (:obj:`ConfigPaths`): paths to configuration files and schema
    """

    def __init__(self, paths=None):
        self.paths = paths

    def get_config(self, extra=None, context=None):
        """ Setup configuration from config file(s), environment variables, and/or function arguments.

        1. Setup configuration from default values specified in `paths.default`.
        2. If `paths.user` is set, find the first file in it that exists, and override
           the default configuration with the values specified in the file.
        3. Override configuration with values from environment variables. Environment variables
           can be set with the following syntax::

               CONFIG.level1.level2...=val

        4. Override configuration with additional configuration in `extra`.
        5. Substitute context into templates
        6. Validate configuration against the schema specified in `paths.schema`.

        Args:
            extra (:obj:`dict`, optional): additional configuration to override
            context (:obj:`dict`, optional): context for template substitution

        Returns:
            :obj:`configobj.ConfigObj`: nested dictionary with the configuration settings loaded from the configuration source(s).

        Raises:
            :obj:`InvalidConfigError`: if configuration doesn't validate against schema
            :obj:`ValueError`: if no configuration is found
        """

        # read configuration schema/specification
        config_specification = ConfigObj(self.paths.schema, list_values=False, _inspec=True)

        # read default configuration
        config = ConfigObj(infile=self.paths.default, configspec=config_specification)

        # read user's configuration files
        for user_config_filename in self.paths.user:
            if os.path.isfile(user_config_filename):
                override_config = ConfigObj(infile=user_config_filename, configspec=config_specification)
                config.merge(override_config)
                break

        # read configuration from environment variables
        for key, val in os.environ.items():
            if key.startswith('CONFIG__DOT__'):
                nested_keys = key[13:].split('__DOT__')
                if nested_keys[0] in config:
                    DictUtil.nested_set(config, nested_keys, val)

        # merge extra configuration
        if extra is None:
            extra = {}
        config.merge(extra)

        # ensure that a configuration is found
        if not config:
            raise ValueError("no config found in envt. variables or {}, {}, or {}".format(
{'fraction_non_alphanumeric': 0.07698646262507357, 'fraction_numerical': 0.006238964096527369, 'mean_word_length': 2.911602209944751, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2192125', 'n_tokens_mistral': 2100, 'n_tokens_neox': 1985, 'n_words': 781}