about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/botocore/configprovider.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/botocore/configprovider.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/botocore/configprovider.py')
-rw-r--r--.venv/lib/python3.12/site-packages/botocore/configprovider.py1061
1 files changed, 1061 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/botocore/configprovider.py b/.venv/lib/python3.12/site-packages/botocore/configprovider.py
new file mode 100644
index 00000000..55c4654a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/botocore/configprovider.py
@@ -0,0 +1,1061 @@
+# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"). You
+# may not use this file except in compliance with the License. A copy of
+# the License is located at
+#
+# http://aws.amazon.com/apache2.0/
+#
+# or in the "license" file accompanying this file. This file is
+# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+# ANY KIND, either express or implied. See the License for the specific
+# language governing permissions and limitations under the License.
+"""This module contains the interface for controlling how configuration
+is loaded.
+"""
+
+import copy
+import logging
+import os
+
+from botocore import utils
+from botocore.exceptions import InvalidConfigError
+
+logger = logging.getLogger(__name__)
+
+
+#: A default dictionary that maps the logical names for session variables
+#: to the specific environment variables and configuration file names
+#: that contain the values for these variables.
+#: When creating a new Session object, you can pass in your own dictionary
+#: to remap the logical names or to add new logical names.  You can then
+#: get the current value for these variables by using the
+#: ``get_config_variable`` method of the :class:`botocore.session.Session`
+#: class.
+#: These form the keys of the dictionary.  The values in the dictionary
+#: are tuples of (<config_name>, <environment variable>, <default value>,
+#: <conversion func>).
+#: The conversion func is a function that takes the configuration value
+#: as an argument and returns the converted value.  If this value is
+#: None, then the configuration value is returned unmodified.  This
+#: conversion function can be used to type convert config values to
+#: values other than the default values of strings.
+#: The ``profile`` and ``config_file`` variables should always have a
+#: None value for the first entry in the tuple because it doesn't make
+#: sense to look inside the config file for the location of the config
+#: file or for the default profile to use.
+#: The ``config_name`` is the name to look for in the configuration file,
+#: the ``env var`` is the OS environment variable (``os.environ``) to
+#: use, and ``default_value`` is the value to use if no value is otherwise
+#: found.
+#: NOTE: Fixing the spelling of this variable would be a breaking change.
+#: Please leave as is.
+BOTOCORE_DEFAUT_SESSION_VARIABLES = {
+    # logical:  config_file, env_var, default_value, conversion_func
+    'profile': (None, ['AWS_DEFAULT_PROFILE', 'AWS_PROFILE'], None, None),
+    'region': ('region', 'AWS_DEFAULT_REGION', None, None),
+    'data_path': ('data_path', 'AWS_DATA_PATH', None, None),
+    'config_file': (None, 'AWS_CONFIG_FILE', '~/.aws/config', None),
+    'ca_bundle': ('ca_bundle', 'AWS_CA_BUNDLE', None, None),
+    'api_versions': ('api_versions', None, {}, None),
+    # This is the shared credentials file amongst sdks.
+    'credentials_file': (
+        None,
+        'AWS_SHARED_CREDENTIALS_FILE',
+        '~/.aws/credentials',
+        None,
+    ),
+    # These variables only exist in the config file.
+    # This is the number of seconds until we time out a request to
+    # the instance metadata service.
+    'metadata_service_timeout': (
+        'metadata_service_timeout',
+        'AWS_METADATA_SERVICE_TIMEOUT',
+        1,
+        int,
+    ),
+    # This is the number of request attempts we make until we give
+    # up trying to retrieve data from the instance metadata service.
+    'metadata_service_num_attempts': (
+        'metadata_service_num_attempts',
+        'AWS_METADATA_SERVICE_NUM_ATTEMPTS',
+        1,
+        int,
+    ),
+    'ec2_metadata_service_endpoint': (
+        'ec2_metadata_service_endpoint',
+        'AWS_EC2_METADATA_SERVICE_ENDPOINT',
+        None,
+        None,
+    ),
+    'ec2_metadata_service_endpoint_mode': (
+        'ec2_metadata_service_endpoint_mode',
+        'AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE',
+        None,
+        None,
+    ),
+    'ec2_metadata_v1_disabled': (
+        'ec2_metadata_v1_disabled',
+        'AWS_EC2_METADATA_V1_DISABLED',
+        False,
+        utils.ensure_boolean,
+    ),
+    'imds_use_ipv6': (
+        'imds_use_ipv6',
+        'AWS_IMDS_USE_IPV6',
+        False,
+        utils.ensure_boolean,
+    ),
+    'use_dualstack_endpoint': (
+        'use_dualstack_endpoint',
+        'AWS_USE_DUALSTACK_ENDPOINT',
+        None,
+        utils.ensure_boolean,
+    ),
+    'use_fips_endpoint': (
+        'use_fips_endpoint',
+        'AWS_USE_FIPS_ENDPOINT',
+        None,
+        utils.ensure_boolean,
+    ),
+    'ignore_configured_endpoint_urls': (
+        'ignore_configured_endpoint_urls',
+        'AWS_IGNORE_CONFIGURED_ENDPOINT_URLS',
+        None,
+        utils.ensure_boolean,
+    ),
+    'parameter_validation': ('parameter_validation', None, True, None),
+    # Client side monitoring configurations.
+    # Note: These configurations are considered internal to botocore.
+    # Do not use them until publicly documented.
+    'csm_enabled': (
+        'csm_enabled',
+        'AWS_CSM_ENABLED',
+        False,
+        utils.ensure_boolean,
+    ),
+    'csm_host': ('csm_host', 'AWS_CSM_HOST', '127.0.0.1', None),
+    'csm_port': ('csm_port', 'AWS_CSM_PORT', 31000, int),
+    'csm_client_id': ('csm_client_id', 'AWS_CSM_CLIENT_ID', '', None),
+    # Endpoint discovery configuration
+    'endpoint_discovery_enabled': (
+        'endpoint_discovery_enabled',
+        'AWS_ENDPOINT_DISCOVERY_ENABLED',
+        'auto',
+        None,
+    ),
+    'retry_mode': ('retry_mode', 'AWS_RETRY_MODE', 'legacy', None),
+    'defaults_mode': ('defaults_mode', 'AWS_DEFAULTS_MODE', 'legacy', None),
+    # We can't have a default here for v1 because we need to defer to
+    # whatever the defaults are in _retry.json.
+    'max_attempts': ('max_attempts', 'AWS_MAX_ATTEMPTS', None, int),
+    'user_agent_appid': ('sdk_ua_app_id', 'AWS_SDK_UA_APP_ID', None, None),
+    'request_min_compression_size_bytes': (
+        'request_min_compression_size_bytes',
+        'AWS_REQUEST_MIN_COMPRESSION_SIZE_BYTES',
+        10240,
+        None,
+    ),
+    'disable_request_compression': (
+        'disable_request_compression',
+        'AWS_DISABLE_REQUEST_COMPRESSION',
+        False,
+        utils.ensure_boolean,
+    ),
+    'sigv4a_signing_region_set': (
+        'sigv4a_signing_region_set',
+        'AWS_SIGV4A_SIGNING_REGION_SET',
+        None,
+        None,
+    ),
+    'request_checksum_calculation': (
+        'request_checksum_calculation',
+        'AWS_REQUEST_CHECKSUM_CALCULATION',
+        "when_supported",
+        None,
+    ),
+    'response_checksum_validation': (
+        'response_checksum_validation',
+        'AWS_RESPONSE_CHECKSUM_VALIDATION',
+        "when_supported",
+        None,
+    ),
+    'account_id_endpoint_mode': (
+        'account_id_endpoint_mode',
+        'AWS_ACCOUNT_ID_ENDPOINT_MODE',
+        'preferred',
+        None,
+    ),
+    'disable_host_prefix_injection': (
+        'disable_host_prefix_injection',
+        'AWS_DISABLE_HOST_PREFIX_INJECTION',
+        None,
+        utils.ensure_boolean,
+    ),
+}
+
+# Evaluate AWS_STS_REGIONAL_ENDPOINTS settings
+try:
+    # This is not a public interface and is subject to abrupt breaking changes.
+    # Any usage is not advised or supported in external code bases.
+    from botocore.customizations.sts import (
+        sts_default_setting as _sts_default_setting,
+    )
+except ImportError:
+    _sts_default_setting = 'legacy'
+
+_STS_DEFAULT_SETTINGS = {
+    'sts_regional_endpoints': (
+        'sts_regional_endpoints',
+        'AWS_STS_REGIONAL_ENDPOINTS',
+        _sts_default_setting,
+        None,
+    ),
+}
+BOTOCORE_DEFAUT_SESSION_VARIABLES.update(_STS_DEFAULT_SETTINGS)
+
+
+# A mapping for the s3 specific configuration vars. These are the configuration
+# vars that typically go in the s3 section of the config file. This mapping
+# follows the same schema as the previous session variable mapping.
+DEFAULT_S3_CONFIG_VARS = {
+    'addressing_style': (('s3', 'addressing_style'), None, None, None),
+    'use_accelerate_endpoint': (
+        ('s3', 'use_accelerate_endpoint'),
+        None,
+        None,
+        utils.ensure_boolean,
+    ),
+    'use_dualstack_endpoint': (
+        ('s3', 'use_dualstack_endpoint'),
+        None,
+        None,
+        utils.ensure_boolean,
+    ),
+    'payload_signing_enabled': (
+        ('s3', 'payload_signing_enabled'),
+        None,
+        None,
+        utils.ensure_boolean,
+    ),
+    'use_arn_region': (
+        ['s3_use_arn_region', ('s3', 'use_arn_region')],
+        'AWS_S3_USE_ARN_REGION',
+        None,
+        utils.ensure_boolean,
+    ),
+    'us_east_1_regional_endpoint': (
+        [
+            's3_us_east_1_regional_endpoint',
+            ('s3', 'us_east_1_regional_endpoint'),
+        ],
+        'AWS_S3_US_EAST_1_REGIONAL_ENDPOINT',
+        None,
+        None,
+    ),
+    's3_disable_multiregion_access_points': (
+        ('s3', 's3_disable_multiregion_access_points'),
+        'AWS_S3_DISABLE_MULTIREGION_ACCESS_POINTS',
+        None,
+        utils.ensure_boolean,
+    ),
+}
+# A mapping for the proxy specific configuration vars. These are
+# used to configure how botocore interacts with proxy setups while
+# sending requests.
+DEFAULT_PROXIES_CONFIG_VARS = {
+    'proxy_ca_bundle': ('proxy_ca_bundle', None, None, None),
+    'proxy_client_cert': ('proxy_client_cert', None, None, None),
+    'proxy_use_forwarding_for_https': (
+        'proxy_use_forwarding_for_https',
+        None,
+        None,
+        utils.normalize_boolean,
+    ),
+}
+
+
+def create_botocore_default_config_mapping(session):
+    chain_builder = ConfigChainFactory(session=session)
+    config_mapping = _create_config_chain_mapping(
+        chain_builder, BOTOCORE_DEFAUT_SESSION_VARIABLES
+    )
+    config_mapping['s3'] = SectionConfigProvider(
+        's3',
+        session,
+        _create_config_chain_mapping(chain_builder, DEFAULT_S3_CONFIG_VARS),
+    )
+    config_mapping['proxies_config'] = SectionConfigProvider(
+        'proxies_config',
+        session,
+        _create_config_chain_mapping(
+            chain_builder, DEFAULT_PROXIES_CONFIG_VARS
+        ),
+    )
+    return config_mapping
+
+
+def _create_config_chain_mapping(chain_builder, config_variables):
+    mapping = {}
+    for logical_name, config in config_variables.items():
+        mapping[logical_name] = chain_builder.create_config_chain(
+            instance_name=logical_name,
+            env_var_names=config[1],
+            config_property_names=config[0],
+            default=config[2],
+            conversion_func=config[3],
+        )
+    return mapping
+
+
+class DefaultConfigResolver:
+    def __init__(self, default_config_data):
+        self._base_default_config = default_config_data['base']
+        self._modes = default_config_data['modes']
+        self._resolved_default_configurations = {}
+
+    def _resolve_default_values_by_mode(self, mode):
+        default_config = self._base_default_config.copy()
+        modifications = self._modes.get(mode)
+
+        for config_var in modifications:
+            default_value = default_config[config_var]
+            modification_dict = modifications[config_var]
+            modification = list(modification_dict.keys())[0]
+            modification_value = modification_dict[modification]
+            if modification == 'multiply':
+                default_value *= modification_value
+            elif modification == 'add':
+                default_value += modification_value
+            elif modification == 'override':
+                default_value = modification_value
+            default_config[config_var] = default_value
+        return default_config
+
+    def get_default_modes(self):
+        default_modes = ['legacy', 'auto']
+        default_modes.extend(self._modes.keys())
+        return default_modes
+
+    def get_default_config_values(self, mode):
+        if mode not in self._resolved_default_configurations:
+            defaults = self._resolve_default_values_by_mode(mode)
+            self._resolved_default_configurations[mode] = defaults
+        return self._resolved_default_configurations[mode]
+
+
+class ConfigChainFactory:
+    """Factory class to create our most common configuration chain case.
+
+    This is a convenience class to construct configuration chains that follow
+    our most common pattern. This is to prevent ordering them incorrectly,
+    and to make the config chain construction more readable.
+    """
+
+    def __init__(self, session, environ=None):
+        """Initialize a ConfigChainFactory.
+
+        :type session: :class:`botocore.session.Session`
+        :param session: This is the session that should be used to look up
+            values from the config file.
+
+        :type environ: dict
+        :param environ: A mapping to use for environment variables. If this
+            is not provided it will default to use os.environ.
+        """
+        self._session = session
+        if environ is None:
+            environ = os.environ
+        self._environ = environ
+
+    def create_config_chain(
+        self,
+        instance_name=None,
+        env_var_names=None,
+        config_property_names=None,
+        default=None,
+        conversion_func=None,
+    ):
+        """Build a config chain following the standard botocore pattern.
+
+        In botocore most of our config chains follow the the precendence:
+        session_instance_variables, environment, config_file, default_value.
+
+        This is a convenience function for creating a chain that follow
+        that precendence.
+
+        :type instance_name: str
+        :param instance_name: This indicates what session instance variable
+            corresponds to this config value. If it is None it will not be
+            added to the chain.
+
+        :type env_var_names: str or list of str or None
+        :param env_var_names: One or more environment variable names to
+            search for this value. They are searched in order. If it is None
+            it will not be added to the chain.
+
+        :type config_property_names: str/tuple or list of str/tuple or None
+        :param config_property_names: One of more strings or tuples
+            representing the name of the key in the config file for this
+            config option. They are searched in order. If it is None it will
+            not be added to the chain.
+
+        :type default: Any
+        :param default: Any constant value to be returned.
+
+        :type conversion_func: None or callable
+        :param conversion_func: If this value is None then it has no effect on
+            the return type. Otherwise, it is treated as a function that will
+            conversion_func our provided type.
+
+        :rvalue: ConfigChain
+        :returns: A ConfigChain that resolves in the order env_var_names ->
+            config_property_name -> default. Any values that were none are
+            omitted form the chain.
+        """
+        providers = []
+        if instance_name is not None:
+            providers.append(
+                InstanceVarProvider(
+                    instance_var=instance_name, session=self._session
+                )
+            )
+        if env_var_names is not None:
+            providers.extend(self._get_env_providers(env_var_names))
+        if config_property_names is not None:
+            providers.extend(
+                self._get_scoped_config_providers(config_property_names)
+            )
+        if default is not None:
+            providers.append(ConstantProvider(value=default))
+
+        return ChainProvider(
+            providers=providers,
+            conversion_func=conversion_func,
+        )
+
+    def _get_env_providers(self, env_var_names):
+        env_var_providers = []
+        if not isinstance(env_var_names, list):
+            env_var_names = [env_var_names]
+        for env_var_name in env_var_names:
+            env_var_providers.append(
+                EnvironmentProvider(name=env_var_name, env=self._environ)
+            )
+        return env_var_providers
+
+    def _get_scoped_config_providers(self, config_property_names):
+        scoped_config_providers = []
+        if not isinstance(config_property_names, list):
+            config_property_names = [config_property_names]
+        for config_property_name in config_property_names:
+            scoped_config_providers.append(
+                ScopedConfigProvider(
+                    config_var_name=config_property_name,
+                    session=self._session,
+                )
+            )
+        return scoped_config_providers
+
+
+class ConfigValueStore:
+    """The ConfigValueStore object stores configuration values."""
+
+    def __init__(self, mapping=None):
+        """Initialize a ConfigValueStore.
+
+        :type mapping: dict
+        :param mapping: The mapping parameter is a map of string to a subclass
+            of BaseProvider. When a config variable is asked for via the
+            get_config_variable method, the corresponding provider will be
+            invoked to load the value.
+        """
+        self._overrides = {}
+        self._mapping = {}
+        if mapping is not None:
+            for logical_name, provider in mapping.items():
+                self.set_config_provider(logical_name, provider)
+
+    def __deepcopy__(self, memo):
+        config_store = ConfigValueStore(copy.deepcopy(self._mapping, memo))
+        for logical_name, override_value in self._overrides.items():
+            config_store.set_config_variable(logical_name, override_value)
+
+        return config_store
+
+    def __copy__(self):
+        config_store = ConfigValueStore(copy.copy(self._mapping))
+        for logical_name, override_value in self._overrides.items():
+            config_store.set_config_variable(logical_name, override_value)
+
+        return config_store
+
+    def get_config_variable(self, logical_name):
+        """
+        Retrieve the value associated with the specified logical_name
+        from the corresponding provider. If no value is found None will
+        be returned.
+
+        :type logical_name: str
+        :param logical_name: The logical name of the session variable
+            you want to retrieve.  This name will be mapped to the
+            appropriate environment variable name for this session as
+            well as the appropriate config file entry.
+
+        :returns: value of variable or None if not defined.
+        """
+        if logical_name in self._overrides:
+            return self._overrides[logical_name]
+        if logical_name not in self._mapping:
+            return None
+        provider = self._mapping[logical_name]
+        return provider.provide()
+
+    def get_config_provider(self, logical_name):
+        """
+        Retrieve the provider associated with the specified logical_name.
+        If no provider is found None will be returned.
+
+        :type logical_name: str
+        :param logical_name: The logical name of the session variable
+            you want to retrieve.  This name will be mapped to the
+            appropriate environment variable name for this session as
+            well as the appropriate config file entry.
+
+        :returns: configuration provider or None if not defined.
+        """
+        if (
+            logical_name in self._overrides
+            or logical_name not in self._mapping
+        ):
+            return None
+        provider = self._mapping[logical_name]
+        return provider
+
+    def set_config_variable(self, logical_name, value):
+        """Set a configuration variable to a specific value.
+
+        By using this method, you can override the normal lookup
+        process used in ``get_config_variable`` by explicitly setting
+        a value.  Subsequent calls to ``get_config_variable`` will
+        use the ``value``.  This gives you per-session specific
+        configuration values.
+
+        ::
+            >>> # Assume logical name 'foo' maps to env var 'FOO'
+            >>> os.environ['FOO'] = 'myvalue'
+            >>> s.get_config_variable('foo')
+            'myvalue'
+            >>> s.set_config_variable('foo', 'othervalue')
+            >>> s.get_config_variable('foo')
+            'othervalue'
+
+        :type logical_name: str
+        :param logical_name: The logical name of the session variable
+            you want to set.  These are the keys in ``SESSION_VARIABLES``.
+
+        :param value: The value to associate with the config variable.
+        """
+        self._overrides[logical_name] = value
+
+    def clear_config_variable(self, logical_name):
+        """Remove an override config variable from the session.
+
+        :type logical_name: str
+        :param logical_name: The name of the parameter to clear the override
+            value from.
+        """
+        self._overrides.pop(logical_name, None)
+
+    def set_config_provider(self, logical_name, provider):
+        """Set the provider for a config value.
+
+        This provides control over how a particular configuration value is
+        loaded. This replaces the provider for ``logical_name`` with the new
+        ``provider``.
+
+        :type logical_name: str
+        :param logical_name: The name of the config value to change the config
+            provider for.
+
+        :type provider: :class:`botocore.configprovider.BaseProvider`
+        :param provider: The new provider that should be responsible for
+            providing a value for the config named ``logical_name``.
+        """
+        self._mapping[logical_name] = provider
+
+
+class SmartDefaultsConfigStoreFactory:
+    def __init__(self, default_config_resolver, imds_region_provider):
+        self._default_config_resolver = default_config_resolver
+        self._imds_region_provider = imds_region_provider
+        # Initializing _instance_metadata_region as None so we
+        # can fetch region in a lazy fashion only when needed.
+        self._instance_metadata_region = None
+
+    def merge_smart_defaults(self, config_store, mode, region_name):
+        if mode == 'auto':
+            mode = self.resolve_auto_mode(region_name)
+        default_configs = (
+            self._default_config_resolver.get_default_config_values(mode)
+        )
+        for config_var in default_configs:
+            config_value = default_configs[config_var]
+            method = getattr(self, f'_set_{config_var}', None)
+            if method:
+                method(config_store, config_value)
+
+    def resolve_auto_mode(self, region_name):
+        current_region = None
+        if os.environ.get('AWS_EXECUTION_ENV'):
+            default_region = os.environ.get('AWS_DEFAULT_REGION')
+            current_region = os.environ.get('AWS_REGION', default_region)
+        if not current_region:
+            if self._instance_metadata_region:
+                current_region = self._instance_metadata_region
+            else:
+                try:
+                    current_region = self._imds_region_provider.provide()
+                    self._instance_metadata_region = current_region
+                except Exception:
+                    pass
+
+        if current_region:
+            if region_name == current_region:
+                return 'in-region'
+            else:
+                return 'cross-region'
+        return 'standard'
+
+    def _update_provider(self, config_store, variable, value):
+        original_provider = config_store.get_config_provider(variable)
+        default_provider = ConstantProvider(value)
+        if isinstance(original_provider, ChainProvider):
+            chain_provider_copy = copy.deepcopy(original_provider)
+            chain_provider_copy.set_default_provider(default_provider)
+            default_provider = chain_provider_copy
+        elif isinstance(original_provider, BaseProvider):
+            default_provider = ChainProvider(
+                providers=[original_provider, default_provider]
+            )
+        config_store.set_config_provider(variable, default_provider)
+
+    def _update_section_provider(
+        self, config_store, section_name, variable, value
+    ):
+        section_provider_copy = copy.deepcopy(
+            config_store.get_config_provider(section_name)
+        )
+        section_provider_copy.set_default_provider(
+            variable, ConstantProvider(value)
+        )
+        config_store.set_config_provider(section_name, section_provider_copy)
+
+    def _set_retryMode(self, config_store, value):
+        self._update_provider(config_store, 'retry_mode', value)
+
+    def _set_stsRegionalEndpoints(self, config_store, value):
+        self._update_provider(config_store, 'sts_regional_endpoints', value)
+
+    def _set_s3UsEast1RegionalEndpoints(self, config_store, value):
+        self._update_section_provider(
+            config_store, 's3', 'us_east_1_regional_endpoint', value
+        )
+
+    def _set_connectTimeoutInMillis(self, config_store, value):
+        self._update_provider(config_store, 'connect_timeout', value / 1000)
+
+
+class BaseProvider:
+    """Base class for configuration value providers.
+
+    A configuration provider has some method of providing a configuration
+    value.
+    """
+
+    def provide(self):
+        """Provide a config value."""
+        raise NotImplementedError('provide')
+
+
+class ChainProvider(BaseProvider):
+    """This provider wraps one or more other providers.
+
+    Each provider in the chain is called, the first one returning a non-None
+    value is then returned.
+    """
+
+    def __init__(self, providers=None, conversion_func=None):
+        """Initalize a ChainProvider.
+
+        :type providers: list
+        :param providers: The initial list of providers to check for values
+            when invoked.
+
+        :type conversion_func: None or callable
+        :param conversion_func: If this value is None then it has no affect on
+            the return type. Otherwise, it is treated as a function that will
+            transform provided value.
+        """
+        if providers is None:
+            providers = []
+        self._providers = providers
+        self._conversion_func = conversion_func
+
+    def __deepcopy__(self, memo):
+        return ChainProvider(
+            copy.deepcopy(self._providers, memo), self._conversion_func
+        )
+
+    def provide(self):
+        """Provide the value from the first provider to return non-None.
+
+        Each provider in the chain has its provide method called. The first
+        one in the chain to return a non-None value is the returned from the
+        ChainProvider. When no non-None value is found, None is returned.
+        """
+        for provider in self._providers:
+            value = provider.provide()
+            if value is not None:
+                return self._convert_type(value)
+        return None
+
+    def set_default_provider(self, default_provider):
+        if self._providers and isinstance(
+            self._providers[-1], ConstantProvider
+        ):
+            self._providers[-1] = default_provider
+        else:
+            self._providers.append(default_provider)
+
+        num_of_constants = sum(
+            isinstance(provider, ConstantProvider)
+            for provider in self._providers
+        )
+        if num_of_constants > 1:
+            logger.info(
+                'ChainProvider object contains multiple '
+                'instances of ConstantProvider objects'
+            )
+
+    def _convert_type(self, value):
+        if self._conversion_func is not None:
+            return self._conversion_func(value)
+        return value
+
+    def __repr__(self):
+        return '[{}]'.format(', '.join([str(p) for p in self._providers]))
+
+
+class InstanceVarProvider(BaseProvider):
+    """This class loads config values from the session instance vars."""
+
+    def __init__(self, instance_var, session):
+        """Initialize InstanceVarProvider.
+
+        :type instance_var: str
+        :param instance_var: The instance variable to load from the session.
+
+        :type session: :class:`botocore.session.Session`
+        :param session: The botocore session to get the loaded configuration
+            file variables from.
+        """
+        self._instance_var = instance_var
+        self._session = session
+
+    def __deepcopy__(self, memo):
+        return InstanceVarProvider(
+            copy.deepcopy(self._instance_var, memo), self._session
+        )
+
+    def provide(self):
+        """Provide a config value from the session instance vars."""
+        instance_vars = self._session.instance_variables()
+        value = instance_vars.get(self._instance_var)
+        return value
+
+    def __repr__(self):
+        return f'InstanceVarProvider(instance_var={self._instance_var}, session={self._session})'
+
+
+class ScopedConfigProvider(BaseProvider):
+    def __init__(self, config_var_name, session):
+        """Initialize ScopedConfigProvider.
+
+        :type config_var_name: str or tuple
+        :param config_var_name: The name of the config variable to load from
+            the configuration file. If the value is a tuple, it must only
+            consist of two items, where the first item represents the section
+            and the second item represents the config var name in the section.
+
+        :type session: :class:`botocore.session.Session`
+        :param session: The botocore session to get the loaded configuration
+            file variables from.
+        """
+        self._config_var_name = config_var_name
+        self._session = session
+
+    def __deepcopy__(self, memo):
+        return ScopedConfigProvider(
+            copy.deepcopy(self._config_var_name, memo), self._session
+        )
+
+    def provide(self):
+        """Provide a value from a config file property."""
+        scoped_config = self._session.get_scoped_config()
+        if isinstance(self._config_var_name, tuple):
+            section_config = scoped_config.get(self._config_var_name[0])
+            if not isinstance(section_config, dict):
+                return None
+            return section_config.get(self._config_var_name[1])
+        return scoped_config.get(self._config_var_name)
+
+    def __repr__(self):
+        return f'ScopedConfigProvider(config_var_name={self._config_var_name}, session={self._session})'
+
+
+class EnvironmentProvider(BaseProvider):
+    """This class loads config values from environment variables."""
+
+    def __init__(self, name, env):
+        """Initialize with the keys in the dictionary to check.
+
+        :type name: str
+        :param name: The key with that name will be loaded and returned.
+
+        :type env: dict
+        :param env: Environment variables dictionary to get variables from.
+        """
+        self._name = name
+        self._env = env
+
+    def __deepcopy__(self, memo):
+        return EnvironmentProvider(
+            copy.deepcopy(self._name, memo), copy.deepcopy(self._env, memo)
+        )
+
+    def provide(self):
+        """Provide a config value from a source dictionary."""
+        if self._name in self._env:
+            return self._env[self._name]
+        return None
+
+    def __repr__(self):
+        return f'EnvironmentProvider(name={self._name}, env={self._env})'
+
+
+class SectionConfigProvider(BaseProvider):
+    """Provides a dictionary from a section in the scoped config
+
+    This is useful for retrieving scoped config variables (i.e. s3) that have
+    their own set of config variables and resolving logic.
+    """
+
+    def __init__(self, section_name, session, override_providers=None):
+        self._section_name = section_name
+        self._session = session
+        self._scoped_config_provider = ScopedConfigProvider(
+            self._section_name, self._session
+        )
+        self._override_providers = override_providers
+        if self._override_providers is None:
+            self._override_providers = {}
+
+    def __deepcopy__(self, memo):
+        return SectionConfigProvider(
+            copy.deepcopy(self._section_name, memo),
+            self._session,
+            copy.deepcopy(self._override_providers, memo),
+        )
+
+    def provide(self):
+        section_config = self._scoped_config_provider.provide()
+        if section_config and not isinstance(section_config, dict):
+            logger.debug(
+                "The %s config key is not a dictionary type, "
+                "ignoring its value of: %s",
+                self._section_name,
+                section_config,
+            )
+            return None
+        for section_config_var, provider in self._override_providers.items():
+            provider_val = provider.provide()
+            if provider_val is not None:
+                if section_config is None:
+                    section_config = {}
+                section_config[section_config_var] = provider_val
+        return section_config
+
+    def set_default_provider(self, key, default_provider):
+        provider = self._override_providers.get(key)
+        if isinstance(provider, ChainProvider):
+            provider.set_default_provider(default_provider)
+            return
+        elif isinstance(provider, BaseProvider):
+            default_provider = ChainProvider(
+                providers=[provider, default_provider]
+            )
+        self._override_providers[key] = default_provider
+
+    def __repr__(self):
+        return (
+            f'SectionConfigProvider(section_name={self._section_name}, '
+            f'session={self._session}, '
+            f'override_providers={self._override_providers})'
+        )
+
+
+class ConstantProvider(BaseProvider):
+    """This provider provides a constant value."""
+
+    def __init__(self, value):
+        self._value = value
+
+    def __deepcopy__(self, memo):
+        return ConstantProvider(copy.deepcopy(self._value, memo))
+
+    def provide(self):
+        """Provide the constant value given during initialization."""
+        return self._value
+
+    def __repr__(self):
+        return f'ConstantProvider(value={self._value})'
+
+
+class ConfiguredEndpointProvider(BaseProvider):
+    """Lookup an endpoint URL from environment variable or shared config file.
+
+    NOTE: This class is considered private and is subject to abrupt breaking
+    changes or removal without prior announcement. Please do not use it
+    directly.
+    """
+
+    _ENDPOINT_URL_LOOKUP_ORDER = [
+        'environment_service',
+        'environment_global',
+        'config_service',
+        'config_global',
+    ]
+
+    def __init__(
+        self,
+        full_config,
+        scoped_config,
+        client_name,
+        environ=None,
+    ):
+        """Initialize a ConfiguredEndpointProviderChain.
+
+        :type full_config: dict
+        :param full_config: This is the dict representing the full
+            configuration file.
+
+        :type scoped_config: dict
+        :param scoped_config: This is the dict representing the configuration
+            for the current profile for the session.
+
+        :type client_name: str
+        :param client_name: The name used to instantiate a client using
+            botocore.session.Session.create_client.
+
+        :type environ: dict
+        :param environ: A mapping to use for environment variables. If this
+            is not provided it will default to use os.environ.
+        """
+        self._full_config = full_config
+        self._scoped_config = scoped_config
+        self._client_name = client_name
+        self._transformed_service_id = self._get_snake_case_service_id(
+            self._client_name
+        )
+        if environ is None:
+            environ = os.environ
+        self._environ = environ
+
+    def provide(self):
+        """Lookup the configured endpoint URL.
+
+        The order is:
+
+        1. The value provided by a service-specific environment variable.
+        2. The value provided by the global endpoint environment variable
+           (AWS_ENDPOINT_URL).
+        3. The value provided by a service-specific parameter from a services
+           definition section in the shared configuration file.
+        4. The value provided by the global parameter from a services
+           definition section in the shared configuration file.
+        """
+        for location in self._ENDPOINT_URL_LOOKUP_ORDER:
+            logger.debug(
+                'Looking for endpoint for %s via: %s',
+                self._client_name,
+                location,
+            )
+
+            endpoint_url = getattr(self, f'_get_endpoint_url_{location}')()
+
+            if endpoint_url:
+                logger.info(
+                    'Found endpoint for %s via: %s.',
+                    self._client_name,
+                    location,
+                )
+                return endpoint_url
+
+        logger.debug('No configured endpoint found.')
+        return None
+
+    def _get_snake_case_service_id(self, client_name):
+        # Get the service ID without loading the service data file, accounting
+        # for any aliases and standardizing the names with hyphens.
+        client_name = utils.SERVICE_NAME_ALIASES.get(client_name, client_name)
+        hyphenized_service_id = (
+            utils.CLIENT_NAME_TO_HYPHENIZED_SERVICE_ID_OVERRIDES.get(
+                client_name, client_name
+            )
+        )
+        return hyphenized_service_id.replace('-', '_')
+
+    def _get_service_env_var_name(self):
+        transformed_service_id_env = self._transformed_service_id.upper()
+        return f'AWS_ENDPOINT_URL_{transformed_service_id_env}'
+
+    def _get_services_config(self):
+        if 'services' not in self._scoped_config:
+            return {}
+
+        section_name = self._scoped_config['services']
+        services_section = self._full_config.get('services', {}).get(
+            section_name
+        )
+
+        if not services_section:
+            error_msg = (
+                f'The profile is configured to use the services '
+                f'section but the "{section_name}" services '
+                f'configuration does not exist.'
+            )
+            raise InvalidConfigError(error_msg=error_msg)
+
+        return services_section
+
+    def _get_endpoint_url_config_service(self):
+        snakecase_service_id = self._transformed_service_id.lower()
+        return (
+            self._get_services_config()
+            .get(snakecase_service_id, {})
+            .get('endpoint_url')
+        )
+
+    def _get_endpoint_url_config_global(self):
+        return self._scoped_config.get('endpoint_url')
+
+    def _get_endpoint_url_environment_service(self):
+        return EnvironmentProvider(
+            name=self._get_service_env_var_name(), env=self._environ
+        ).provide()
+
+    def _get_endpoint_url_environment_global(self):
+        return EnvironmentProvider(
+            name='AWS_ENDPOINT_URL', env=self._environ
+        ).provide()