Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/botocore/configprovider.py: 51%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

333 statements  

1# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"). You 

4# may not use this file except in compliance with the License. A copy of 

5# the License is located at 

6# 

7# http://aws.amazon.com/apache2.0/ 

8# 

9# or in the "license" file accompanying this file. This file is 

10# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 

11# ANY KIND, either express or implied. See the License for the specific 

12# language governing permissions and limitations under the License. 

13"""This module contains the interface for controlling how configuration 

14is loaded. 

15""" 

16 

17import copy 

18import logging 

19import os 

20 

21from botocore import utils 

22from botocore.exceptions import InvalidConfigError 

23 

24logger = logging.getLogger(__name__) 

25 

26 

27#: A default dictionary that maps the logical names for session variables 

28#: to the specific environment variables and configuration file names 

29#: that contain the values for these variables. 

30#: When creating a new Session object, you can pass in your own dictionary 

31#: to remap the logical names or to add new logical names. You can then 

32#: get the current value for these variables by using the 

33#: ``get_config_variable`` method of the :class:`botocore.session.Session` 

34#: class. 

35#: These form the keys of the dictionary. The values in the dictionary 

36#: are tuples of (<config_name>, <environment variable>, <default value>, 

37#: <conversion func>). 

38#: The conversion func is a function that takes the configuration value 

39#: as an argument and returns the converted value. If this value is 

40#: None, then the configuration value is returned unmodified. This 

41#: conversion function can be used to type convert config values to 

42#: values other than the default values of strings. 

43#: The ``profile`` and ``config_file`` variables should always have a 

44#: None value for the first entry in the tuple because it doesn't make 

45#: sense to look inside the config file for the location of the config 

46#: file or for the default profile to use. 

47#: The ``config_name`` is the name to look for in the configuration file, 

48#: the ``env var`` is the OS environment variable (``os.environ``) to 

49#: use, and ``default_value`` is the value to use if no value is otherwise 

50#: found. 

51#: NOTE: Fixing the spelling of this variable would be a breaking change. 

52#: Please leave as is. 

53BOTOCORE_DEFAUT_SESSION_VARIABLES = { 

54 # logical: config_file, env_var, default_value, conversion_func 

55 'profile': (None, ['AWS_DEFAULT_PROFILE', 'AWS_PROFILE'], None, None), 

56 'region': ('region', 'AWS_DEFAULT_REGION', None, None), 

57 'data_path': ('data_path', 'AWS_DATA_PATH', None, None), 

58 'config_file': (None, 'AWS_CONFIG_FILE', '~/.aws/config', None), 

59 'ca_bundle': ('ca_bundle', 'AWS_CA_BUNDLE', None, None), 

60 'api_versions': ('api_versions', None, {}, None), 

61 # This is the shared credentials file amongst sdks. 

62 'credentials_file': ( 

63 None, 

64 'AWS_SHARED_CREDENTIALS_FILE', 

65 '~/.aws/credentials', 

66 None, 

67 ), 

68 # These variables only exist in the config file. 

69 # This is the number of seconds until we time out a request to 

70 # the instance metadata service. 

71 'metadata_service_timeout': ( 

72 'metadata_service_timeout', 

73 'AWS_METADATA_SERVICE_TIMEOUT', 

74 1, 

75 int, 

76 ), 

77 # This is the number of request attempts we make until we give 

78 # up trying to retrieve data from the instance metadata service. 

79 'metadata_service_num_attempts': ( 

80 'metadata_service_num_attempts', 

81 'AWS_METADATA_SERVICE_NUM_ATTEMPTS', 

82 1, 

83 int, 

84 ), 

85 'ec2_metadata_service_endpoint': ( 

86 'ec2_metadata_service_endpoint', 

87 'AWS_EC2_METADATA_SERVICE_ENDPOINT', 

88 None, 

89 None, 

90 ), 

91 'ec2_metadata_service_endpoint_mode': ( 

92 'ec2_metadata_service_endpoint_mode', 

93 'AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE', 

94 None, 

95 None, 

96 ), 

97 'ec2_metadata_v1_disabled': ( 

98 'ec2_metadata_v1_disabled', 

99 'AWS_EC2_METADATA_V1_DISABLED', 

100 False, 

101 utils.ensure_boolean, 

102 ), 

103 'imds_use_ipv6': ( 

104 'imds_use_ipv6', 

105 'AWS_IMDS_USE_IPV6', 

106 False, 

107 utils.ensure_boolean, 

108 ), 

109 'use_dualstack_endpoint': ( 

110 'use_dualstack_endpoint', 

111 'AWS_USE_DUALSTACK_ENDPOINT', 

112 None, 

113 utils.ensure_boolean, 

114 ), 

115 'use_fips_endpoint': ( 

116 'use_fips_endpoint', 

117 'AWS_USE_FIPS_ENDPOINT', 

118 None, 

119 utils.ensure_boolean, 

120 ), 

121 'ignore_configured_endpoint_urls': ( 

122 'ignore_configured_endpoint_urls', 

123 'AWS_IGNORE_CONFIGURED_ENDPOINT_URLS', 

124 None, 

125 utils.ensure_boolean, 

126 ), 

127 'parameter_validation': ('parameter_validation', None, True, None), 

128 # Client side monitoring configurations. 

129 # Note: These configurations are considered internal to botocore. 

130 # Do not use them until publicly documented. 

131 'csm_enabled': ( 

132 'csm_enabled', 

133 'AWS_CSM_ENABLED', 

134 False, 

135 utils.ensure_boolean, 

136 ), 

137 'csm_host': ('csm_host', 'AWS_CSM_HOST', '127.0.0.1', None), 

138 'csm_port': ('csm_port', 'AWS_CSM_PORT', 31000, int), 

139 'csm_client_id': ('csm_client_id', 'AWS_CSM_CLIENT_ID', '', None), 

140 # Endpoint discovery configuration 

141 'endpoint_discovery_enabled': ( 

142 'endpoint_discovery_enabled', 

143 'AWS_ENDPOINT_DISCOVERY_ENABLED', 

144 'auto', 

145 None, 

146 ), 

147 'retry_mode': ('retry_mode', 'AWS_RETRY_MODE', 'legacy', None), 

148 'defaults_mode': ('defaults_mode', 'AWS_DEFAULTS_MODE', 'legacy', None), 

149 # We can't have a default here for v1 because we need to defer to 

150 # whatever the defaults are in _retry.json. 

151 'max_attempts': ('max_attempts', 'AWS_MAX_ATTEMPTS', None, int), 

152 'user_agent_appid': ('sdk_ua_app_id', 'AWS_SDK_UA_APP_ID', None, None), 

153 'request_min_compression_size_bytes': ( 

154 'request_min_compression_size_bytes', 

155 'AWS_REQUEST_MIN_COMPRESSION_SIZE_BYTES', 

156 10240, 

157 None, 

158 ), 

159 'disable_request_compression': ( 

160 'disable_request_compression', 

161 'AWS_DISABLE_REQUEST_COMPRESSION', 

162 False, 

163 utils.ensure_boolean, 

164 ), 

165 'sigv4a_signing_region_set': ( 

166 'sigv4a_signing_region_set', 

167 'AWS_SIGV4A_SIGNING_REGION_SET', 

168 None, 

169 None, 

170 ), 

171 'request_checksum_calculation': ( 

172 'request_checksum_calculation', 

173 'AWS_REQUEST_CHECKSUM_CALCULATION', 

174 "when_supported", 

175 None, 

176 ), 

177 'response_checksum_validation': ( 

178 'response_checksum_validation', 

179 'AWS_RESPONSE_CHECKSUM_VALIDATION', 

180 "when_supported", 

181 None, 

182 ), 

183 'account_id_endpoint_mode': ( 

184 'account_id_endpoint_mode', 

185 'AWS_ACCOUNT_ID_ENDPOINT_MODE', 

186 'preferred', 

187 None, 

188 ), 

189 'disable_host_prefix_injection': ( 

190 'disable_host_prefix_injection', 

191 'AWS_DISABLE_HOST_PREFIX_INJECTION', 

192 None, 

193 utils.ensure_boolean, 

194 ), 

195 'auth_scheme_preference': ( 

196 'auth_scheme_preference', 

197 'AWS_AUTH_SCHEME_PREFERENCE', 

198 None, 

199 None, 

200 ), 

201} 

202 

203# Evaluate AWS_STS_REGIONAL_ENDPOINTS settings 

204try: 

205 # This is not a public interface and is subject to abrupt breaking changes. 

206 # Any usage is not advised or supported in external code bases. 

207 from botocore.customizations.sts import ( 

208 sts_default_setting as _sts_default_setting, 

209 ) 

210except ImportError: 

211 _sts_default_setting = 'legacy' 

212 

213_STS_DEFAULT_SETTINGS = { 

214 'sts_regional_endpoints': ( 

215 'sts_regional_endpoints', 

216 'AWS_STS_REGIONAL_ENDPOINTS', 

217 _sts_default_setting, 

218 None, 

219 ), 

220} 

221BOTOCORE_DEFAUT_SESSION_VARIABLES.update(_STS_DEFAULT_SETTINGS) 

222 

223 

224# A mapping for the s3 specific configuration vars. These are the configuration 

225# vars that typically go in the s3 section of the config file. This mapping 

226# follows the same schema as the previous session variable mapping. 

227DEFAULT_S3_CONFIG_VARS = { 

228 'addressing_style': (('s3', 'addressing_style'), None, None, None), 

229 'use_accelerate_endpoint': ( 

230 ('s3', 'use_accelerate_endpoint'), 

231 None, 

232 None, 

233 utils.ensure_boolean, 

234 ), 

235 'use_dualstack_endpoint': ( 

236 ('s3', 'use_dualstack_endpoint'), 

237 None, 

238 None, 

239 utils.ensure_boolean, 

240 ), 

241 'payload_signing_enabled': ( 

242 ('s3', 'payload_signing_enabled'), 

243 None, 

244 None, 

245 utils.ensure_boolean, 

246 ), 

247 'use_arn_region': ( 

248 ['s3_use_arn_region', ('s3', 'use_arn_region')], 

249 'AWS_S3_USE_ARN_REGION', 

250 None, 

251 utils.ensure_boolean, 

252 ), 

253 'us_east_1_regional_endpoint': ( 

254 [ 

255 's3_us_east_1_regional_endpoint', 

256 ('s3', 'us_east_1_regional_endpoint'), 

257 ], 

258 'AWS_S3_US_EAST_1_REGIONAL_ENDPOINT', 

259 None, 

260 None, 

261 ), 

262 's3_disable_multiregion_access_points': ( 

263 ('s3', 's3_disable_multiregion_access_points'), 

264 'AWS_S3_DISABLE_MULTIREGION_ACCESS_POINTS', 

265 None, 

266 utils.ensure_boolean, 

267 ), 

268} 

269# A mapping for the proxy specific configuration vars. These are 

270# used to configure how botocore interacts with proxy setups while 

271# sending requests. 

272DEFAULT_PROXIES_CONFIG_VARS = { 

273 'proxy_ca_bundle': ('proxy_ca_bundle', None, None, None), 

274 'proxy_client_cert': ('proxy_client_cert', None, None, None), 

275 'proxy_use_forwarding_for_https': ( 

276 'proxy_use_forwarding_for_https', 

277 None, 

278 None, 

279 utils.normalize_boolean, 

280 ), 

281} 

282 

283 

284def create_botocore_default_config_mapping(session): 

285 chain_builder = ConfigChainFactory(session=session) 

286 config_mapping = _create_config_chain_mapping( 

287 chain_builder, BOTOCORE_DEFAUT_SESSION_VARIABLES 

288 ) 

289 config_mapping['s3'] = SectionConfigProvider( 

290 's3', 

291 session, 

292 _create_config_chain_mapping(chain_builder, DEFAULT_S3_CONFIG_VARS), 

293 ) 

294 config_mapping['proxies_config'] = SectionConfigProvider( 

295 'proxies_config', 

296 session, 

297 _create_config_chain_mapping( 

298 chain_builder, DEFAULT_PROXIES_CONFIG_VARS 

299 ), 

300 ) 

301 return config_mapping 

302 

303 

304def _create_config_chain_mapping(chain_builder, config_variables): 

305 mapping = {} 

306 for logical_name, config in config_variables.items(): 

307 mapping[logical_name] = chain_builder.create_config_chain( 

308 instance_name=logical_name, 

309 env_var_names=config[1], 

310 config_property_names=config[0], 

311 default=config[2], 

312 conversion_func=config[3], 

313 ) 

314 return mapping 

315 

316 

317class DefaultConfigResolver: 

318 def __init__(self, default_config_data): 

319 self._base_default_config = default_config_data['base'] 

320 self._modes = default_config_data['modes'] 

321 self._resolved_default_configurations = {} 

322 

323 def _resolve_default_values_by_mode(self, mode): 

324 default_config = self._base_default_config.copy() 

325 modifications = self._modes.get(mode) 

326 

327 for config_var in modifications: 

328 default_value = default_config[config_var] 

329 modification_dict = modifications[config_var] 

330 modification = list(modification_dict.keys())[0] 

331 modification_value = modification_dict[modification] 

332 if modification == 'multiply': 

333 default_value *= modification_value 

334 elif modification == 'add': 

335 default_value += modification_value 

336 elif modification == 'override': 

337 default_value = modification_value 

338 default_config[config_var] = default_value 

339 return default_config 

340 

341 def get_default_modes(self): 

342 default_modes = ['legacy', 'auto'] 

343 default_modes.extend(self._modes.keys()) 

344 return default_modes 

345 

346 def get_default_config_values(self, mode): 

347 if mode not in self._resolved_default_configurations: 

348 defaults = self._resolve_default_values_by_mode(mode) 

349 self._resolved_default_configurations[mode] = defaults 

350 return self._resolved_default_configurations[mode] 

351 

352 

353class ConfigChainFactory: 

354 """Factory class to create our most common configuration chain case. 

355 

356 This is a convenience class to construct configuration chains that follow 

357 our most common pattern. This is to prevent ordering them incorrectly, 

358 and to make the config chain construction more readable. 

359 """ 

360 

361 def __init__(self, session, environ=None): 

362 """Initialize a ConfigChainFactory. 

363 

364 :type session: :class:`botocore.session.Session` 

365 :param session: This is the session that should be used to look up 

366 values from the config file. 

367 

368 :type environ: dict 

369 :param environ: A mapping to use for environment variables. If this 

370 is not provided it will default to use os.environ. 

371 """ 

372 self._session = session 

373 if environ is None: 

374 environ = os.environ 

375 self._environ = environ 

376 

377 def create_config_chain( 

378 self, 

379 instance_name=None, 

380 env_var_names=None, 

381 config_property_names=None, 

382 default=None, 

383 conversion_func=None, 

384 ): 

385 """Build a config chain following the standard botocore pattern. 

386 

387 In botocore most of our config chains follow the the precendence: 

388 session_instance_variables, environment, config_file, default_value. 

389 

390 This is a convenience function for creating a chain that follow 

391 that precendence. 

392 

393 :type instance_name: str 

394 :param instance_name: This indicates what session instance variable 

395 corresponds to this config value. If it is None it will not be 

396 added to the chain. 

397 

398 :type env_var_names: str or list of str or None 

399 :param env_var_names: One or more environment variable names to 

400 search for this value. They are searched in order. If it is None 

401 it will not be added to the chain. 

402 

403 :type config_property_names: str/tuple or list of str/tuple or None 

404 :param config_property_names: One of more strings or tuples 

405 representing the name of the key in the config file for this 

406 config option. They are searched in order. If it is None it will 

407 not be added to the chain. 

408 

409 :type default: Any 

410 :param default: Any constant value to be returned. 

411 

412 :type conversion_func: None or callable 

413 :param conversion_func: If this value is None then it has no effect on 

414 the return type. Otherwise, it is treated as a function that will 

415 conversion_func our provided type. 

416 

417 :rvalue: ConfigChain 

418 :returns: A ConfigChain that resolves in the order env_var_names -> 

419 config_property_name -> default. Any values that were none are 

420 omitted form the chain. 

421 """ 

422 providers = [] 

423 if instance_name is not None: 

424 providers.append( 

425 InstanceVarProvider( 

426 instance_var=instance_name, session=self._session 

427 ) 

428 ) 

429 if env_var_names is not None: 

430 providers.extend(self._get_env_providers(env_var_names)) 

431 if config_property_names is not None: 

432 providers.extend( 

433 self._get_scoped_config_providers(config_property_names) 

434 ) 

435 if default is not None: 

436 providers.append(ConstantProvider(value=default)) 

437 

438 return ChainProvider( 

439 providers=providers, 

440 conversion_func=conversion_func, 

441 ) 

442 

443 def _get_env_providers(self, env_var_names): 

444 env_var_providers = [] 

445 if not isinstance(env_var_names, list): 

446 env_var_names = [env_var_names] 

447 for env_var_name in env_var_names: 

448 env_var_providers.append( 

449 EnvironmentProvider(name=env_var_name, env=self._environ) 

450 ) 

451 return env_var_providers 

452 

453 def _get_scoped_config_providers(self, config_property_names): 

454 scoped_config_providers = [] 

455 if not isinstance(config_property_names, list): 

456 config_property_names = [config_property_names] 

457 for config_property_name in config_property_names: 

458 scoped_config_providers.append( 

459 ScopedConfigProvider( 

460 config_var_name=config_property_name, 

461 session=self._session, 

462 ) 

463 ) 

464 return scoped_config_providers 

465 

466 

467class ConfigValueStore: 

468 """The ConfigValueStore object stores configuration values.""" 

469 

470 def __init__(self, mapping=None): 

471 """Initialize a ConfigValueStore. 

472 

473 :type mapping: dict 

474 :param mapping: The mapping parameter is a map of string to a subclass 

475 of BaseProvider. When a config variable is asked for via the 

476 get_config_variable method, the corresponding provider will be 

477 invoked to load the value. 

478 """ 

479 self._overrides = {} 

480 self._mapping = {} 

481 if mapping is not None: 

482 for logical_name, provider in mapping.items(): 

483 self.set_config_provider(logical_name, provider) 

484 

485 def __deepcopy__(self, memo): 

486 config_store = ConfigValueStore(copy.deepcopy(self._mapping, memo)) 

487 for logical_name, override_value in self._overrides.items(): 

488 config_store.set_config_variable(logical_name, override_value) 

489 

490 return config_store 

491 

492 def __copy__(self): 

493 config_store = ConfigValueStore(copy.copy(self._mapping)) 

494 for logical_name, override_value in self._overrides.items(): 

495 config_store.set_config_variable(logical_name, override_value) 

496 

497 return config_store 

498 

499 def get_config_variable(self, logical_name): 

500 """ 

501 Retrieve the value associated with the specified logical_name 

502 from the corresponding provider. If no value is found None will 

503 be returned. 

504 

505 :type logical_name: str 

506 :param logical_name: The logical name of the session variable 

507 you want to retrieve. This name will be mapped to the 

508 appropriate environment variable name for this session as 

509 well as the appropriate config file entry. 

510 

511 :returns: value of variable or None if not defined. 

512 """ 

513 if logical_name in self._overrides: 

514 return self._overrides[logical_name] 

515 if logical_name not in self._mapping: 

516 return None 

517 provider = self._mapping[logical_name] 

518 return provider.provide() 

519 

520 def get_config_provider(self, logical_name): 

521 """ 

522 Retrieve the provider associated with the specified logical_name. 

523 If no provider is found None will be returned. 

524 

525 :type logical_name: str 

526 :param logical_name: The logical name of the session variable 

527 you want to retrieve. This name will be mapped to the 

528 appropriate environment variable name for this session as 

529 well as the appropriate config file entry. 

530 

531 :returns: configuration provider or None if not defined. 

532 """ 

533 if ( 

534 logical_name in self._overrides 

535 or logical_name not in self._mapping 

536 ): 

537 return None 

538 provider = self._mapping[logical_name] 

539 return provider 

540 

541 def set_config_variable(self, logical_name, value): 

542 """Set a configuration variable to a specific value. 

543 

544 By using this method, you can override the normal lookup 

545 process used in ``get_config_variable`` by explicitly setting 

546 a value. Subsequent calls to ``get_config_variable`` will 

547 use the ``value``. This gives you per-session specific 

548 configuration values. 

549 

550 :: 

551 >>> # Assume logical name 'foo' maps to env var 'FOO' 

552 >>> os.environ['FOO'] = 'myvalue' 

553 >>> s.get_config_variable('foo') 

554 'myvalue' 

555 >>> s.set_config_variable('foo', 'othervalue') 

556 >>> s.get_config_variable('foo') 

557 'othervalue' 

558 

559 :type logical_name: str 

560 :param logical_name: The logical name of the session variable 

561 you want to set. These are the keys in ``SESSION_VARIABLES``. 

562 

563 :param value: The value to associate with the config variable. 

564 """ 

565 self._overrides[logical_name] = value 

566 

567 def clear_config_variable(self, logical_name): 

568 """Remove an override config variable from the session. 

569 

570 :type logical_name: str 

571 :param logical_name: The name of the parameter to clear the override 

572 value from. 

573 """ 

574 self._overrides.pop(logical_name, None) 

575 

576 def set_config_provider(self, logical_name, provider): 

577 """Set the provider for a config value. 

578 

579 This provides control over how a particular configuration value is 

580 loaded. This replaces the provider for ``logical_name`` with the new 

581 ``provider``. 

582 

583 :type logical_name: str 

584 :param logical_name: The name of the config value to change the config 

585 provider for. 

586 

587 :type provider: :class:`botocore.configprovider.BaseProvider` 

588 :param provider: The new provider that should be responsible for 

589 providing a value for the config named ``logical_name``. 

590 """ 

591 self._mapping[logical_name] = provider 

592 

593 

594class SmartDefaultsConfigStoreFactory: 

595 def __init__(self, default_config_resolver, imds_region_provider): 

596 self._default_config_resolver = default_config_resolver 

597 self._imds_region_provider = imds_region_provider 

598 # Initializing _instance_metadata_region as None so we 

599 # can fetch region in a lazy fashion only when needed. 

600 self._instance_metadata_region = None 

601 

602 def merge_smart_defaults(self, config_store, mode, region_name): 

603 if mode == 'auto': 

604 mode = self.resolve_auto_mode(region_name) 

605 default_configs = ( 

606 self._default_config_resolver.get_default_config_values(mode) 

607 ) 

608 for config_var in default_configs: 

609 config_value = default_configs[config_var] 

610 method = getattr(self, f'_set_{config_var}', None) 

611 if method: 

612 method(config_store, config_value) 

613 

614 def resolve_auto_mode(self, region_name): 

615 current_region = None 

616 if os.environ.get('AWS_EXECUTION_ENV'): 

617 default_region = os.environ.get('AWS_DEFAULT_REGION') 

618 current_region = os.environ.get('AWS_REGION', default_region) 

619 if not current_region: 

620 if self._instance_metadata_region: 

621 current_region = self._instance_metadata_region 

622 else: 

623 try: 

624 current_region = self._imds_region_provider.provide() 

625 self._instance_metadata_region = current_region 

626 except Exception: 

627 pass 

628 

629 if current_region: 

630 if region_name == current_region: 

631 return 'in-region' 

632 else: 

633 return 'cross-region' 

634 return 'standard' 

635 

636 def _update_provider(self, config_store, variable, value): 

637 original_provider = config_store.get_config_provider(variable) 

638 default_provider = ConstantProvider(value) 

639 if isinstance(original_provider, ChainProvider): 

640 chain_provider_copy = copy.deepcopy(original_provider) 

641 chain_provider_copy.set_default_provider(default_provider) 

642 default_provider = chain_provider_copy 

643 elif isinstance(original_provider, BaseProvider): 

644 default_provider = ChainProvider( 

645 providers=[original_provider, default_provider] 

646 ) 

647 config_store.set_config_provider(variable, default_provider) 

648 

649 def _update_section_provider( 

650 self, config_store, section_name, variable, value 

651 ): 

652 section_provider_copy = copy.deepcopy( 

653 config_store.get_config_provider(section_name) 

654 ) 

655 section_provider_copy.set_default_provider( 

656 variable, ConstantProvider(value) 

657 ) 

658 config_store.set_config_provider(section_name, section_provider_copy) 

659 

660 def _set_retryMode(self, config_store, value): 

661 self._update_provider(config_store, 'retry_mode', value) 

662 

663 def _set_stsRegionalEndpoints(self, config_store, value): 

664 self._update_provider(config_store, 'sts_regional_endpoints', value) 

665 

666 def _set_s3UsEast1RegionalEndpoints(self, config_store, value): 

667 self._update_section_provider( 

668 config_store, 's3', 'us_east_1_regional_endpoint', value 

669 ) 

670 

671 def _set_connectTimeoutInMillis(self, config_store, value): 

672 self._update_provider(config_store, 'connect_timeout', value / 1000) 

673 

674 

675class BaseProvider: 

676 """Base class for configuration value providers. 

677 

678 A configuration provider has some method of providing a configuration 

679 value. 

680 """ 

681 

682 def provide(self): 

683 """Provide a config value.""" 

684 raise NotImplementedError('provide') 

685 

686 

687class ChainProvider(BaseProvider): 

688 """This provider wraps one or more other providers. 

689 

690 Each provider in the chain is called, the first one returning a non-None 

691 value is then returned. 

692 """ 

693 

694 def __init__(self, providers=None, conversion_func=None): 

695 """Initalize a ChainProvider. 

696 

697 :type providers: list 

698 :param providers: The initial list of providers to check for values 

699 when invoked. 

700 

701 :type conversion_func: None or callable 

702 :param conversion_func: If this value is None then it has no affect on 

703 the return type. Otherwise, it is treated as a function that will 

704 transform provided value. 

705 """ 

706 if providers is None: 

707 providers = [] 

708 self._providers = providers 

709 self._conversion_func = conversion_func 

710 

711 def __deepcopy__(self, memo): 

712 return ChainProvider( 

713 copy.deepcopy(self._providers, memo), self._conversion_func 

714 ) 

715 

716 def provide(self): 

717 """Provide the value from the first provider to return non-None. 

718 

719 Each provider in the chain has its provide method called. The first 

720 one in the chain to return a non-None value is the returned from the 

721 ChainProvider. When no non-None value is found, None is returned. 

722 """ 

723 for provider in self._providers: 

724 value = provider.provide() 

725 if value is not None: 

726 return self._convert_type(value) 

727 return None 

728 

729 def set_default_provider(self, default_provider): 

730 if self._providers and isinstance( 

731 self._providers[-1], ConstantProvider 

732 ): 

733 self._providers[-1] = default_provider 

734 else: 

735 self._providers.append(default_provider) 

736 

737 num_of_constants = sum( 

738 isinstance(provider, ConstantProvider) 

739 for provider in self._providers 

740 ) 

741 if num_of_constants > 1: 

742 logger.info( 

743 'ChainProvider object contains multiple ' 

744 'instances of ConstantProvider objects' 

745 ) 

746 

747 def _convert_type(self, value): 

748 if self._conversion_func is not None: 

749 return self._conversion_func(value) 

750 return value 

751 

752 def __repr__(self): 

753 return '[{}]'.format(', '.join([str(p) for p in self._providers])) 

754 

755 

756class InstanceVarProvider(BaseProvider): 

757 """This class loads config values from the session instance vars.""" 

758 

759 def __init__(self, instance_var, session): 

760 """Initialize InstanceVarProvider. 

761 

762 :type instance_var: str 

763 :param instance_var: The instance variable to load from the session. 

764 

765 :type session: :class:`botocore.session.Session` 

766 :param session: The botocore session to get the loaded configuration 

767 file variables from. 

768 """ 

769 self._instance_var = instance_var 

770 self._session = session 

771 

772 def __deepcopy__(self, memo): 

773 return InstanceVarProvider( 

774 copy.deepcopy(self._instance_var, memo), self._session 

775 ) 

776 

777 def provide(self): 

778 """Provide a config value from the session instance vars.""" 

779 instance_vars = self._session.instance_variables() 

780 value = instance_vars.get(self._instance_var) 

781 return value 

782 

783 def __repr__(self): 

784 return f'InstanceVarProvider(instance_var={self._instance_var}, session={self._session})' 

785 

786 

787class ScopedConfigProvider(BaseProvider): 

788 def __init__(self, config_var_name, session): 

789 """Initialize ScopedConfigProvider. 

790 

791 :type config_var_name: str or tuple 

792 :param config_var_name: The name of the config variable to load from 

793 the configuration file. If the value is a tuple, it must only 

794 consist of two items, where the first item represents the section 

795 and the second item represents the config var name in the section. 

796 

797 :type session: :class:`botocore.session.Session` 

798 :param session: The botocore session to get the loaded configuration 

799 file variables from. 

800 """ 

801 self._config_var_name = config_var_name 

802 self._session = session 

803 

804 def __deepcopy__(self, memo): 

805 return ScopedConfigProvider( 

806 copy.deepcopy(self._config_var_name, memo), self._session 

807 ) 

808 

809 def provide(self): 

810 """Provide a value from a config file property.""" 

811 scoped_config = self._session.get_scoped_config() 

812 if isinstance(self._config_var_name, tuple): 

813 section_config = scoped_config.get(self._config_var_name[0]) 

814 if not isinstance(section_config, dict): 

815 return None 

816 return section_config.get(self._config_var_name[1]) 

817 return scoped_config.get(self._config_var_name) 

818 

819 def __repr__(self): 

820 return f'ScopedConfigProvider(config_var_name={self._config_var_name}, session={self._session})' 

821 

822 

823class EnvironmentProvider(BaseProvider): 

824 """This class loads config values from environment variables.""" 

825 

826 def __init__(self, name, env): 

827 """Initialize with the keys in the dictionary to check. 

828 

829 :type name: str 

830 :param name: The key with that name will be loaded and returned. 

831 

832 :type env: dict 

833 :param env: Environment variables dictionary to get variables from. 

834 """ 

835 self._name = name 

836 self._env = env 

837 

838 def __deepcopy__(self, memo): 

839 return EnvironmentProvider( 

840 copy.deepcopy(self._name, memo), copy.deepcopy(self._env, memo) 

841 ) 

842 

843 def provide(self): 

844 """Provide a config value from a source dictionary.""" 

845 if self._name in self._env: 

846 return self._env[self._name] 

847 return None 

848 

849 def __repr__(self): 

850 return f'EnvironmentProvider(name={self._name}, env={self._env})' 

851 

852 

853class SectionConfigProvider(BaseProvider): 

854 """Provides a dictionary from a section in the scoped config 

855 

856 This is useful for retrieving scoped config variables (i.e. s3) that have 

857 their own set of config variables and resolving logic. 

858 """ 

859 

860 def __init__(self, section_name, session, override_providers=None): 

861 self._section_name = section_name 

862 self._session = session 

863 self._scoped_config_provider = ScopedConfigProvider( 

864 self._section_name, self._session 

865 ) 

866 self._override_providers = override_providers 

867 if self._override_providers is None: 

868 self._override_providers = {} 

869 

870 def __deepcopy__(self, memo): 

871 return SectionConfigProvider( 

872 copy.deepcopy(self._section_name, memo), 

873 self._session, 

874 copy.deepcopy(self._override_providers, memo), 

875 ) 

876 

877 def provide(self): 

878 section_config = self._scoped_config_provider.provide() 

879 if section_config and not isinstance(section_config, dict): 

880 logger.debug( 

881 "The %s config key is not a dictionary type, " 

882 "ignoring its value of: %s", 

883 self._section_name, 

884 section_config, 

885 ) 

886 return None 

887 for section_config_var, provider in self._override_providers.items(): 

888 provider_val = provider.provide() 

889 if provider_val is not None: 

890 if section_config is None: 

891 section_config = {} 

892 section_config[section_config_var] = provider_val 

893 return section_config 

894 

895 def set_default_provider(self, key, default_provider): 

896 provider = self._override_providers.get(key) 

897 if isinstance(provider, ChainProvider): 

898 provider.set_default_provider(default_provider) 

899 return 

900 elif isinstance(provider, BaseProvider): 

901 default_provider = ChainProvider( 

902 providers=[provider, default_provider] 

903 ) 

904 self._override_providers[key] = default_provider 

905 

906 def __repr__(self): 

907 return ( 

908 f'SectionConfigProvider(section_name={self._section_name}, ' 

909 f'session={self._session}, ' 

910 f'override_providers={self._override_providers})' 

911 ) 

912 

913 

914class ConstantProvider(BaseProvider): 

915 """This provider provides a constant value.""" 

916 

917 def __init__(self, value): 

918 self._value = value 

919 

920 def __deepcopy__(self, memo): 

921 return ConstantProvider(copy.deepcopy(self._value, memo)) 

922 

923 def provide(self): 

924 """Provide the constant value given during initialization.""" 

925 return self._value 

926 

927 def __repr__(self): 

928 return f'ConstantProvider(value={self._value})' 

929 

930 

931class ConfiguredEndpointProvider(BaseProvider): 

932 """Lookup an endpoint URL from environment variable or shared config file. 

933 

934 NOTE: This class is considered private and is subject to abrupt breaking 

935 changes or removal without prior announcement. Please do not use it 

936 directly. 

937 """ 

938 

939 _ENDPOINT_URL_LOOKUP_ORDER = [ 

940 'environment_service', 

941 'environment_global', 

942 'config_service', 

943 'config_global', 

944 ] 

945 

946 def __init__( 

947 self, 

948 full_config, 

949 scoped_config, 

950 client_name, 

951 environ=None, 

952 ): 

953 """Initialize a ConfiguredEndpointProviderChain. 

954 

955 :type full_config: dict 

956 :param full_config: This is the dict representing the full 

957 configuration file. 

958 

959 :type scoped_config: dict 

960 :param scoped_config: This is the dict representing the configuration 

961 for the current profile for the session. 

962 

963 :type client_name: str 

964 :param client_name: The name used to instantiate a client using 

965 botocore.session.Session.create_client. 

966 

967 :type environ: dict 

968 :param environ: A mapping to use for environment variables. If this 

969 is not provided it will default to use os.environ. 

970 """ 

971 self._full_config = full_config 

972 self._scoped_config = scoped_config 

973 self._client_name = client_name 

974 self._transformed_service_id = self._get_snake_case_service_id( 

975 self._client_name 

976 ) 

977 if environ is None: 

978 environ = os.environ 

979 self._environ = environ 

980 

981 def provide(self): 

982 """Lookup the configured endpoint URL. 

983 

984 The order is: 

985 

986 1. The value provided by a service-specific environment variable. 

987 2. The value provided by the global endpoint environment variable 

988 (AWS_ENDPOINT_URL). 

989 3. The value provided by a service-specific parameter from a services 

990 definition section in the shared configuration file. 

991 4. The value provided by the global parameter from a services 

992 definition section in the shared configuration file. 

993 """ 

994 for location in self._ENDPOINT_URL_LOOKUP_ORDER: 

995 logger.debug( 

996 'Looking for endpoint for %s via: %s', 

997 self._client_name, 

998 location, 

999 ) 

1000 

1001 endpoint_url = getattr(self, f'_get_endpoint_url_{location}')() 

1002 

1003 if endpoint_url: 

1004 logger.info( 

1005 'Found endpoint for %s via: %s.', 

1006 self._client_name, 

1007 location, 

1008 ) 

1009 return endpoint_url 

1010 

1011 logger.debug('No configured endpoint found.') 

1012 return None 

1013 

1014 def _get_snake_case_service_id(self, client_name): 

1015 # Get the service ID without loading the service data file, accounting 

1016 # for any aliases and standardizing the names with hyphens. 

1017 client_name = utils.SERVICE_NAME_ALIASES.get(client_name, client_name) 

1018 hyphenized_service_id = ( 

1019 utils.CLIENT_NAME_TO_HYPHENIZED_SERVICE_ID_OVERRIDES.get( 

1020 client_name, client_name 

1021 ) 

1022 ) 

1023 return hyphenized_service_id.replace('-', '_') 

1024 

1025 def _get_service_env_var_name(self): 

1026 transformed_service_id_env = self._transformed_service_id.upper() 

1027 return f'AWS_ENDPOINT_URL_{transformed_service_id_env}' 

1028 

1029 def _get_services_config(self): 

1030 if 'services' not in self._scoped_config: 

1031 return {} 

1032 

1033 section_name = self._scoped_config['services'] 

1034 services_section = self._full_config.get('services', {}).get( 

1035 section_name 

1036 ) 

1037 

1038 if not services_section: 

1039 error_msg = ( 

1040 f'The profile is configured to use the services ' 

1041 f'section but the "{section_name}" services ' 

1042 f'configuration does not exist.' 

1043 ) 

1044 raise InvalidConfigError(error_msg=error_msg) 

1045 

1046 return services_section 

1047 

1048 def _get_endpoint_url_config_service(self): 

1049 snakecase_service_id = self._transformed_service_id.lower() 

1050 return ( 

1051 self._get_services_config() 

1052 .get(snakecase_service_id, {}) 

1053 .get('endpoint_url') 

1054 ) 

1055 

1056 def _get_endpoint_url_config_global(self): 

1057 return self._scoped_config.get('endpoint_url') 

1058 

1059 def _get_endpoint_url_environment_service(self): 

1060 return EnvironmentProvider( 

1061 name=self._get_service_env_var_name(), env=self._environ 

1062 ).provide() 

1063 

1064 def _get_endpoint_url_environment_global(self): 

1065 return EnvironmentProvider( 

1066 name='AWS_ENDPOINT_URL', env=self._environ 

1067 ).provide()