diff options
author | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
---|---|---|
committer | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
commit | 4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch) | |
tree | ee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/botocore/configprovider.py | |
parent | cc961e04ba734dd72309fb548a2f97d67d578813 (diff) | |
download | gn-ai-master.tar.gz |
Diffstat (limited to '.venv/lib/python3.12/site-packages/botocore/configprovider.py')
-rw-r--r-- | .venv/lib/python3.12/site-packages/botocore/configprovider.py | 1061 |
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() |