Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
configloader.py288 linesDownload Raw Back to botocore
1# Copyright (c) 2012-2013 Mitch Garnaat http://garnaat.org/2# Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.3#4# Licensed under the Apache License, Version 2.0 (the "License"). You5# may not use this file except in compliance with the License. A copy of6# the License is located at7#8# http://aws.amazon.com/apache2.0/9#10# or in the "license" file accompanying this file. This file is11# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF12# ANY KIND, either express or implied. See the License for the specific13# language governing permissions and limitations under the License.14import configparser15import copy16import os17import shlex18import sys19 20import botocore.exceptions21 22 23def multi_file_load_config(*filenames):24    """Load and combine multiple INI configs with profiles.25 26    This function will take a list of filesnames and return27    a single dictionary that represents the merging of the loaded28    config files.29 30    If any of the provided filenames does not exist, then that file31    is ignored.  It is therefore ok to provide a list of filenames,32    some of which may not exist.33 34    Configuration files are **not** deep merged, only the top level35    keys are merged.  The filenames should be passed in order of36    precedence.  The first config file has precedence over the37    second config file, which has precedence over the third config file,38    etc.  The only exception to this is that the "profiles" key is39    merged to combine profiles from multiple config files into a40    single profiles mapping.  However, if a profile is defined in41    multiple config files, then the config file with the highest42    precedence is used.  Profile values themselves are not merged.43    For example::44 45        FileA              FileB                FileC46        [foo]             [foo]                 [bar]47        a=1               a=2                   a=348                          b=249 50        [bar]             [baz]                [profile a]51        a=2               a=3                  region=e52 53        [profile a]       [profile b]          [profile c]54        region=c          region=d             region=f55 56    The final result of ``multi_file_load_config(FileA, FileB, FileC)``57    would be::58 59        {"foo": {"a": 1}, "bar": {"a": 2}, "baz": {"a": 3},60        "profiles": {"a": {"region": "c"}}, {"b": {"region": d"}},61                    {"c": {"region": "f"}}}62 63    Note that the "foo" key comes from A, even though it's defined in both64    FileA and FileB.  Because "foo" was defined in FileA first, then the values65    for "foo" from FileA are used and the values for "foo" from FileB are66    ignored.  Also note where the profiles originate from.  Profile "a"67    comes FileA, profile "b" comes from FileB, and profile "c" comes68    from FileC.69 70    """71    configs = []72    profiles = []73    for filename in filenames:74        try:75            loaded = load_config(filename)76        except botocore.exceptions.ConfigNotFound:77            continue78        profiles.append(loaded.pop('profiles'))79        configs.append(loaded)80    merged_config = _merge_list_of_dicts(configs)81    merged_profiles = _merge_list_of_dicts(profiles)82    merged_config['profiles'] = merged_profiles83    return merged_config84 85 86def _merge_list_of_dicts(list_of_dicts):87    merged_dicts = {}88    for single_dict in list_of_dicts:89        for key, value in single_dict.items():90            if key not in merged_dicts:91                merged_dicts[key] = value92    return merged_dicts93 94 95def load_config(config_filename):96    """Parse a INI config with profiles.97 98    This will parse an INI config file and map top level profiles99    into a top level "profile" key.100 101    If you want to parse an INI file and map all section names to102    top level keys, use ``raw_config_parse`` instead.103 104    """105    parsed = raw_config_parse(config_filename)106    return build_profile_map(parsed)107 108 109def raw_config_parse(config_filename, parse_subsections=True):110    """Returns the parsed INI config contents.111 112    Each section name is a top level key.113 114    :param config_filename: The name of the INI file to parse115 116    :param parse_subsections: If True, parse indented blocks as117       subsections that represent their own configuration dictionary.118       For example, if the config file had the contents::119 120           s3 =121              signature_version = s3v4122              addressing_style = path123 124        The resulting ``raw_config_parse`` would be::125 126            {'s3': {'signature_version': 's3v4', 'addressing_style': 'path'}}127 128       If False, do not try to parse subsections and return the indented129       block as its literal value::130 131            {'s3': '\nsignature_version = s3v4\naddressing_style = path'}132 133    :returns: A dict with keys for each profile found in the config134        file and the value of each key being a dict containing name135        value pairs found in that profile.136 137    :raises: ConfigNotFound, ConfigParseError138    """139    config = {}140    path = config_filename141    if path is not None:142        path = os.path.expandvars(path)143        path = os.path.expanduser(path)144        if not os.path.isfile(path):145            raise botocore.exceptions.ConfigNotFound(path=_unicode_path(path))146        cp = configparser.RawConfigParser()147        try:148            cp.read([path])149        except (configparser.Error, UnicodeDecodeError) as e:150            raise botocore.exceptions.ConfigParseError(151                path=_unicode_path(path), error=e152            ) from None153        else:154            for section in cp.sections():155                config[section] = {}156                for option in cp.options(section):157                    config_value = cp.get(section, option)158                    if parse_subsections and config_value.startswith('\n'):159                        # Then we need to parse the inner contents as160                        # hierarchical.  We support a single level161                        # of nesting for now.162                        try:163                            config_value = _parse_nested(config_value)164                        except ValueError as e:165                            raise botocore.exceptions.ConfigParseError(166                                path=_unicode_path(path), error=e167                            ) from None168                    config[section][option] = config_value169    return config170 171 172def _unicode_path(path):173    if isinstance(path, str):174        return path175    # According to the documentation getfilesystemencoding can return None176    # on unix in which case the default encoding is used instead.177    filesystem_encoding = sys.getfilesystemencoding()178    if filesystem_encoding is None:179        filesystem_encoding = sys.getdefaultencoding()180    return path.decode(filesystem_encoding, 'replace')181 182 183def _parse_nested(config_value):184    # Given a value like this:185    # \n186    # foo = bar187    # bar = baz188    # We need to parse this into189    # {'foo': 'bar', 'bar': 'baz}190    parsed = {}191    for line in config_value.splitlines():192        line = line.strip()193        if not line:194            continue195        # The caller will catch ValueError196        # and raise an appropriate error197        # if this fails.198        key, value = line.split('=', 1)199        parsed[key.strip()] = value.strip()200    return parsed201 202 203def _parse_section(key, values):204    result = {}205    try:206        parts = shlex.split(key)207    except ValueError:208        return result209    if len(parts) == 2:210        result[parts[1]] = values211    return result212 213 214def build_profile_map(parsed_ini_config):215    """Convert the parsed INI config into a profile map.216 217    The config file format requires that every profile except the218    default to be prepended with "profile", e.g.::219 220        [profile test]221        aws_... = foo222        aws_... = bar223 224        [profile bar]225        aws_... = foo226        aws_... = bar227 228        # This is *not* a profile229        [preview]230        otherstuff = 1231 232        # Neither is this233        [foobar]234        morestuff = 2235 236    The build_profile_map will take a parsed INI config file where each top237    level key represents a section name, and convert into a format where all238    the profiles are under a single top level "profiles" key, and each key in239    the sub dictionary is a profile name.  For example, the above config file240    would be converted from::241 242        {"profile test": {"aws_...": "foo", "aws...": "bar"},243         "profile bar": {"aws...": "foo", "aws...": "bar"},244         "preview": {"otherstuff": ...},245         "foobar": {"morestuff": ...},246         }247 248    into::249 250        {"profiles": {"test": {"aws_...": "foo", "aws...": "bar"},251                      "bar": {"aws...": "foo", "aws...": "bar"},252         "preview": {"otherstuff": ...},253         "foobar": {"morestuff": ...},254        }255 256    If there are no profiles in the provided parsed INI contents, then257    an empty dict will be the value associated with the ``profiles`` key.258 259    .. note::260 261        This will not mutate the passed in parsed_ini_config.  Instead it will262        make a deepcopy and return that value.263 264    """265    parsed_config = copy.deepcopy(parsed_ini_config)266    profiles = {}267    sso_sessions = {}268    services = {}269    final_config = {}270    for key, values in parsed_config.items():271        if key.startswith("profile"):272            profiles.update(_parse_section(key, values))273        elif key.startswith("sso-session"):274            sso_sessions.update(_parse_section(key, values))275        elif key.startswith("services"):276            services.update(_parse_section(key, values))277        elif key == 'default':278            # default section is special and is considered a profile279            # name but we don't require you use 'profile "default"'280            # as a section.281            profiles[key] = values282        else:283            final_config[key] = values284    final_config['profiles'] = profiles285    final_config['sso_sessions'] = sso_sessions286    final_config['services'] = services287    return final_config288 
codekingpro/portable-devtools · Team Ai