Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/botocore/args.py: 17%
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
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
1# Copyright 2016 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"""Internal module to help with normalizing botocore client args.
15This module (and all function/classes within this module) should be
16considered internal, and *not* a public API.
18"""
20import copy
21import logging
22import socket
24import botocore.exceptions
25import botocore.parsers
26import botocore.serialize
27from botocore.config import Config
28from botocore.endpoint import EndpointCreator
29from botocore.regions import EndpointResolverBuiltins as EPRBuiltins
30from botocore.regions import EndpointRulesetResolver
31from botocore.signers import RequestSigner
32from botocore.useragent import UserAgentString, register_feature_id
33from botocore.utils import (
34 PRIORITY_ORDERED_SUPPORTED_PROTOCOLS, # noqa: F401
35 ensure_boolean,
36 is_s3_accelerate_url,
37)
39logger = logging.getLogger(__name__)
42VALID_REGIONAL_ENDPOINTS_CONFIG = [
43 'legacy',
44 'regional',
45]
46LEGACY_GLOBAL_STS_REGIONS = [
47 'ap-northeast-1',
48 'ap-south-1',
49 'ap-southeast-1',
50 'ap-southeast-2',
51 'aws-global',
52 'ca-central-1',
53 'eu-central-1',
54 'eu-north-1',
55 'eu-west-1',
56 'eu-west-2',
57 'eu-west-3',
58 'sa-east-1',
59 'us-east-1',
60 'us-east-2',
61 'us-west-1',
62 'us-west-2',
63]
64# Maximum allowed length of the ``user_agent_appid`` config field. Longer
65# values result in a warning-level log message.
66USERAGENT_APPID_MAXLEN = 50
68VALID_REQUEST_CHECKSUM_CALCULATION_CONFIG = (
69 "when_supported",
70 "when_required",
71)
72VALID_RESPONSE_CHECKSUM_VALIDATION_CONFIG = (
73 "when_supported",
74 "when_required",
75)
78VALID_ACCOUNT_ID_ENDPOINT_MODE_CONFIG = (
79 'preferred',
80 'disabled',
81 'required',
82)
85class ClientArgsCreator:
86 def __init__(
87 self,
88 event_emitter,
89 user_agent,
90 response_parser_factory,
91 loader,
92 exceptions_factory,
93 config_store,
94 user_agent_creator=None,
95 ):
96 self._event_emitter = event_emitter
97 self._response_parser_factory = response_parser_factory
98 self._loader = loader
99 self._exceptions_factory = exceptions_factory
100 self._config_store = config_store
101 if user_agent_creator is None:
102 self._session_ua_creator = UserAgentString.from_environment()
103 else:
104 self._session_ua_creator = user_agent_creator
106 def get_client_args(
107 self,
108 service_model,
109 region_name,
110 is_secure,
111 endpoint_url,
112 verify,
113 credentials,
114 scoped_config,
115 client_config,
116 endpoint_bridge,
117 auth_token=None,
118 endpoints_ruleset_data=None,
119 partition_data=None,
120 ):
121 final_args = self.compute_client_args(
122 service_model,
123 client_config,
124 endpoint_bridge,
125 region_name,
126 endpoint_url,
127 is_secure,
128 scoped_config,
129 )
131 service_name = final_args['service_name'] # noqa
132 parameter_validation = final_args['parameter_validation']
133 endpoint_config = final_args['endpoint_config']
134 protocol = final_args['protocol']
135 config_kwargs = final_args['config_kwargs']
136 s3_config = final_args['s3_config']
137 partition = endpoint_config['metadata'].get('partition', None)
138 socket_options = final_args['socket_options']
139 configured_endpoint_url = final_args['configured_endpoint_url']
140 signing_region = endpoint_config['signing_region']
141 endpoint_region_name = endpoint_config['region_name']
142 account_id_endpoint_mode = config_kwargs['account_id_endpoint_mode']
144 event_emitter = copy.copy(self._event_emitter)
145 signer = RequestSigner(
146 service_model.service_id,
147 signing_region,
148 endpoint_config['signing_name'],
149 endpoint_config['signature_version'],
150 credentials,
151 event_emitter,
152 auth_token,
153 )
155 config_kwargs['s3'] = s3_config
156 new_config = Config(**config_kwargs)
157 endpoint_creator = EndpointCreator(event_emitter)
159 endpoint = endpoint_creator.create_endpoint(
160 service_model,
161 region_name=endpoint_region_name,
162 endpoint_url=endpoint_config['endpoint_url'],
163 verify=verify,
164 response_parser_factory=self._response_parser_factory,
165 max_pool_connections=new_config.max_pool_connections,
166 proxies=new_config.proxies,
167 timeout=(new_config.connect_timeout, new_config.read_timeout),
168 socket_options=socket_options,
169 client_cert=new_config.client_cert,
170 proxies_config=new_config.proxies_config,
171 )
173 serializer = botocore.serialize.create_serializer(
174 protocol, parameter_validation
175 )
176 response_parser = botocore.parsers.create_parser(protocol)
178 ruleset_resolver = self._build_endpoint_resolver(
179 endpoints_ruleset_data,
180 partition_data,
181 client_config,
182 service_model,
183 endpoint_region_name,
184 region_name,
185 configured_endpoint_url,
186 endpoint,
187 is_secure,
188 endpoint_bridge,
189 event_emitter,
190 credentials,
191 account_id_endpoint_mode,
192 )
194 # Copy the session's user agent factory and adds client configuration.
195 client_ua_creator = self._session_ua_creator.with_client_config(
196 new_config
197 )
198 supplied_ua = client_config.user_agent if client_config else None
199 new_config._supplied_user_agent = supplied_ua
201 return {
202 'serializer': serializer,
203 'endpoint': endpoint,
204 'response_parser': response_parser,
205 'event_emitter': event_emitter,
206 'request_signer': signer,
207 'service_model': service_model,
208 'loader': self._loader,
209 'client_config': new_config,
210 'partition': partition,
211 'exceptions_factory': self._exceptions_factory,
212 'endpoint_ruleset_resolver': ruleset_resolver,
213 'user_agent_creator': client_ua_creator,
214 }
216 def compute_client_args(
217 self,
218 service_model,
219 client_config,
220 endpoint_bridge,
221 region_name,
222 endpoint_url,
223 is_secure,
224 scoped_config,
225 ):
226 service_name = service_model.endpoint_prefix
227 protocol = service_model.resolved_protocol
228 parameter_validation = True
229 if client_config and not client_config.parameter_validation:
230 parameter_validation = False
231 elif scoped_config:
232 raw_value = scoped_config.get('parameter_validation')
233 if raw_value is not None:
234 parameter_validation = ensure_boolean(raw_value)
236 s3_config = self.compute_s3_config(client_config)
238 configured_endpoint_url = self._compute_configured_endpoint_url(
239 client_config=client_config,
240 endpoint_url=endpoint_url,
241 )
242 if configured_endpoint_url is not None:
243 register_feature_id('ENDPOINT_OVERRIDE')
245 endpoint_config = self._compute_endpoint_config(
246 service_name=service_name,
247 region_name=region_name,
248 endpoint_url=configured_endpoint_url,
249 is_secure=is_secure,
250 endpoint_bridge=endpoint_bridge,
251 s3_config=s3_config,
252 )
253 endpoint_variant_tags = endpoint_config['metadata'].get('tags', [])
255 # Some third-party libraries expect the final user-agent string in
256 # ``client.meta.config.user_agent``. To maintain backwards
257 # compatibility, the preliminary user-agent string (before any Config
258 # object modifications and without request-specific user-agent
259 # components) is stored in the new Config object's ``user_agent``
260 # property but not used by Botocore itself.
261 preliminary_ua_string = self._session_ua_creator.with_client_config(
262 client_config
263 ).to_string()
264 # Create a new client config to be passed to the client based
265 # on the final values. We do not want the user to be able
266 # to try to modify an existing client with a client config.
267 config_kwargs = dict(
268 region_name=endpoint_config['region_name'],
269 signature_version=endpoint_config['signature_version'],
270 user_agent=preliminary_ua_string,
271 )
272 if 'dualstack' in endpoint_variant_tags:
273 config_kwargs.update(use_dualstack_endpoint=True)
274 if 'fips' in endpoint_variant_tags:
275 config_kwargs.update(use_fips_endpoint=True)
276 if client_config is not None:
277 config_kwargs.update(
278 connect_timeout=client_config.connect_timeout,
279 read_timeout=client_config.read_timeout,
280 max_pool_connections=client_config.max_pool_connections,
281 proxies=client_config.proxies,
282 proxies_config=client_config.proxies_config,
283 retries=client_config.retries,
284 client_cert=client_config.client_cert,
285 inject_host_prefix=client_config.inject_host_prefix,
286 tcp_keepalive=client_config.tcp_keepalive,
287 user_agent_extra=client_config.user_agent_extra,
288 user_agent_appid=client_config.user_agent_appid,
289 request_min_compression_size_bytes=(
290 client_config.request_min_compression_size_bytes
291 ),
292 disable_request_compression=(
293 client_config.disable_request_compression
294 ),
295 client_context_params=client_config.client_context_params,
296 sigv4a_signing_region_set=(
297 client_config.sigv4a_signing_region_set
298 ),
299 request_checksum_calculation=(
300 client_config.request_checksum_calculation
301 ),
302 response_checksum_validation=(
303 client_config.response_checksum_validation
304 ),
305 account_id_endpoint_mode=client_config.account_id_endpoint_mode,
306 auth_scheme_preference=client_config.auth_scheme_preference,
307 )
308 self._compute_retry_config(config_kwargs)
309 self._compute_connect_timeout(config_kwargs)
310 self._compute_user_agent_appid_config(config_kwargs)
311 self._compute_request_compression_config(config_kwargs)
312 self._compute_sigv4a_signing_region_set_config(config_kwargs)
313 self._compute_checksum_config(config_kwargs)
314 self._compute_account_id_endpoint_mode_config(config_kwargs)
315 self._compute_inject_host_prefix(client_config, config_kwargs)
316 self._compute_auth_scheme_preference_config(
317 client_config, config_kwargs
318 )
319 self._compute_signature_version_config(client_config, config_kwargs)
320 s3_config = self.compute_s3_config(client_config)
322 is_s3_service = self._is_s3_service(service_name)
324 if is_s3_service and 'dualstack' in endpoint_variant_tags:
325 if s3_config is None:
326 s3_config = {}
327 s3_config['use_dualstack_endpoint'] = True
329 return {
330 'service_name': service_name,
331 'parameter_validation': parameter_validation,
332 'configured_endpoint_url': configured_endpoint_url,
333 'endpoint_config': endpoint_config,
334 'protocol': protocol,
335 'config_kwargs': config_kwargs,
336 's3_config': s3_config,
337 'socket_options': self._compute_socket_options(
338 scoped_config, client_config
339 ),
340 }
342 def _compute_inject_host_prefix(self, client_config, config_kwargs):
343 # In the cases that a Config object was not provided, or the private value
344 # remained UNSET, we should resolve the value from the config store.
345 if (
346 client_config is None
347 or client_config._inject_host_prefix == 'UNSET'
348 ):
349 configured_disable_host_prefix_injection = (
350 self._config_store.get_config_variable(
351 'disable_host_prefix_injection'
352 )
353 )
354 if configured_disable_host_prefix_injection is not None:
355 config_kwargs[
356 'inject_host_prefix'
357 ] = not configured_disable_host_prefix_injection
358 else:
359 config_kwargs['inject_host_prefix'] = True
361 def _compute_configured_endpoint_url(self, client_config, endpoint_url):
362 if endpoint_url is not None:
363 return endpoint_url
365 if self._ignore_configured_endpoint_urls(client_config):
366 logger.debug("Ignoring configured endpoint URLs.")
367 return endpoint_url
369 return self._config_store.get_config_variable('endpoint_url')
371 def _ignore_configured_endpoint_urls(self, client_config):
372 if (
373 client_config
374 and client_config.ignore_configured_endpoint_urls is not None
375 ):
376 return client_config.ignore_configured_endpoint_urls
378 return self._config_store.get_config_variable(
379 'ignore_configured_endpoint_urls'
380 )
382 def compute_s3_config(self, client_config):
383 s3_configuration = self._config_store.get_config_variable('s3')
385 # Next specific client config values takes precedence over
386 # specific values in the scoped config.
387 if client_config is not None:
388 if client_config.s3 is not None:
389 if s3_configuration is None:
390 s3_configuration = client_config.s3
391 else:
392 # The current s3_configuration dictionary may be
393 # from a source that only should be read from so
394 # we want to be safe and just make a copy of it to modify
395 # before it actually gets updated.
396 s3_configuration = s3_configuration.copy()
397 s3_configuration.update(client_config.s3)
399 return s3_configuration
401 def _is_s3_service(self, service_name):
402 """Whether the service is S3 or S3 Control.
404 Note that throughout this class, service_name refers to the endpoint
405 prefix, not the folder name of the service in botocore/data. For
406 S3 Control, the folder name is 's3control' but the endpoint prefix is
407 's3-control'.
408 """
409 return service_name in ['s3', 's3-control']
411 def _compute_endpoint_config(
412 self,
413 service_name,
414 region_name,
415 endpoint_url,
416 is_secure,
417 endpoint_bridge,
418 s3_config,
419 ):
420 resolve_endpoint_kwargs = {
421 'service_name': service_name,
422 'region_name': region_name,
423 'endpoint_url': endpoint_url,
424 'is_secure': is_secure,
425 'endpoint_bridge': endpoint_bridge,
426 }
427 if service_name == 's3':
428 return self._compute_s3_endpoint_config(
429 s3_config=s3_config, **resolve_endpoint_kwargs
430 )
431 if service_name == 'sts':
432 return self._compute_sts_endpoint_config(**resolve_endpoint_kwargs)
433 return self._resolve_endpoint(**resolve_endpoint_kwargs)
435 def _compute_s3_endpoint_config(
436 self, s3_config, **resolve_endpoint_kwargs
437 ):
438 force_s3_global = self._should_force_s3_global(
439 resolve_endpoint_kwargs['region_name'], s3_config
440 )
441 if force_s3_global:
442 resolve_endpoint_kwargs['region_name'] = None
443 endpoint_config = self._resolve_endpoint(**resolve_endpoint_kwargs)
444 self._set_region_if_custom_s3_endpoint(
445 endpoint_config, resolve_endpoint_kwargs['endpoint_bridge']
446 )
447 # For backwards compatibility reasons, we want to make sure the
448 # client.meta.region_name will remain us-east-1 if we forced the
449 # endpoint to be the global region. Specifically, if this value
450 # changes to aws-global, it breaks logic where a user is checking
451 # for us-east-1 as the global endpoint such as in creating buckets.
452 if force_s3_global and endpoint_config['region_name'] == 'aws-global':
453 endpoint_config['region_name'] = 'us-east-1'
454 return endpoint_config
456 def _should_force_s3_global(self, region_name, s3_config):
457 s3_regional_config = 'legacy'
458 if s3_config and 'us_east_1_regional_endpoint' in s3_config:
459 s3_regional_config = s3_config['us_east_1_regional_endpoint']
460 self._validate_s3_regional_config(s3_regional_config)
462 is_global_region = region_name in ('us-east-1', None)
463 return s3_regional_config == 'legacy' and is_global_region
465 def _validate_s3_regional_config(self, config_val):
466 if config_val not in VALID_REGIONAL_ENDPOINTS_CONFIG:
467 raise botocore.exceptions.InvalidS3UsEast1RegionalEndpointConfigError(
468 s3_us_east_1_regional_endpoint_config=config_val
469 )
471 def _set_region_if_custom_s3_endpoint(
472 self, endpoint_config, endpoint_bridge
473 ):
474 # If a user is providing a custom URL, the endpoint resolver will
475 # refuse to infer a signing region. If we want to default to s3v4,
476 # we have to account for this.
477 if (
478 endpoint_config['signing_region'] is None
479 and endpoint_config['region_name'] is None
480 ):
481 endpoint = endpoint_bridge.resolve('s3')
482 endpoint_config['signing_region'] = endpoint['signing_region']
483 endpoint_config['region_name'] = endpoint['region_name']
485 def _compute_sts_endpoint_config(self, **resolve_endpoint_kwargs):
486 endpoint_config = self._resolve_endpoint(**resolve_endpoint_kwargs)
487 if self._should_set_global_sts_endpoint(
488 resolve_endpoint_kwargs['region_name'],
489 resolve_endpoint_kwargs['endpoint_url'],
490 endpoint_config,
491 ):
492 self._set_global_sts_endpoint(
493 endpoint_config, resolve_endpoint_kwargs['is_secure']
494 )
495 return endpoint_config
497 def _should_set_global_sts_endpoint(
498 self, region_name, endpoint_url, endpoint_config
499 ):
500 has_variant_tags = endpoint_config and endpoint_config.get(
501 'metadata', {}
502 ).get('tags')
503 if endpoint_url or has_variant_tags:
504 return False
505 return (
506 self._get_sts_regional_endpoints_config() == 'legacy'
507 and region_name in LEGACY_GLOBAL_STS_REGIONS
508 )
510 def _get_sts_regional_endpoints_config(self):
511 sts_regional_endpoints_config = self._config_store.get_config_variable(
512 'sts_regional_endpoints'
513 )
514 if not sts_regional_endpoints_config:
515 sts_regional_endpoints_config = 'legacy'
516 if (
517 sts_regional_endpoints_config
518 not in VALID_REGIONAL_ENDPOINTS_CONFIG
519 ):
520 raise botocore.exceptions.InvalidSTSRegionalEndpointsConfigError(
521 sts_regional_endpoints_config=sts_regional_endpoints_config
522 )
523 return sts_regional_endpoints_config
525 def _set_global_sts_endpoint(self, endpoint_config, is_secure):
526 scheme = 'https' if is_secure else 'http'
527 endpoint_config['endpoint_url'] = f'{scheme}://sts.amazonaws.com'
528 endpoint_config['signing_region'] = 'us-east-1'
530 def _resolve_endpoint(
531 self,
532 service_name,
533 region_name,
534 endpoint_url,
535 is_secure,
536 endpoint_bridge,
537 ):
538 return endpoint_bridge.resolve(
539 service_name, region_name, endpoint_url, is_secure
540 )
542 def _compute_socket_options(self, scoped_config, client_config=None):
543 # This disables Nagle's algorithm and is the default socket options
544 # in urllib3.
545 socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]
546 client_keepalive = client_config and client_config.tcp_keepalive
547 scoped_keepalive = scoped_config and self._ensure_boolean(
548 scoped_config.get("tcp_keepalive", False)
549 )
550 # Enables TCP Keepalive if specified in client config object or shared config file.
551 if client_keepalive or scoped_keepalive:
552 socket_options.append((socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1))
553 return socket_options
555 def _compute_retry_config(self, config_kwargs):
556 self._compute_retry_max_attempts(config_kwargs)
557 self._compute_retry_mode(config_kwargs)
559 def _compute_retry_max_attempts(self, config_kwargs):
560 # There's a pre-existing max_attempts client config value that actually
561 # means max *retry* attempts. There's also a `max_attempts` we pull
562 # from the config store that means *total attempts*, which includes the
563 # intitial request. We can't change what `max_attempts` means in
564 # client config so we try to normalize everything to a new
565 # "total_max_attempts" variable. We ensure that after this, the only
566 # configuration for "max attempts" is the 'total_max_attempts' key.
567 # An explicitly provided max_attempts in the client config
568 # overrides everything.
569 retries = config_kwargs.get('retries')
570 if retries is not None:
571 if 'total_max_attempts' in retries:
572 retries.pop('max_attempts', None)
573 return
574 if 'max_attempts' in retries:
575 value = retries.pop('max_attempts')
576 # client config max_attempts means total retries so we
577 # have to add one for 'total_max_attempts' to account
578 # for the initial request.
579 retries['total_max_attempts'] = value + 1
580 return
581 # Otherwise we'll check the config store which checks env vars,
582 # config files, etc. There is no default value for max_attempts
583 # so if this returns None and we don't set a default value here.
584 max_attempts = self._config_store.get_config_variable('max_attempts')
585 if max_attempts is not None:
586 if retries is None:
587 retries = {}
588 config_kwargs['retries'] = retries
589 retries['total_max_attempts'] = max_attempts
591 def _compute_retry_mode(self, config_kwargs):
592 retries = config_kwargs.get('retries')
593 if retries is None:
594 retries = {}
595 config_kwargs['retries'] = retries
596 elif 'mode' in retries:
597 # If there's a retry mode explicitly set in the client config
598 # that overrides everything.
599 return
600 retry_mode = self._config_store.get_config_variable('retry_mode')
601 if retry_mode is None:
602 retry_mode = 'legacy'
603 retries['mode'] = retry_mode
605 def _compute_connect_timeout(self, config_kwargs):
606 # Checking if connect_timeout is set on the client config.
607 # If it is not, we check the config_store in case a
608 # non legacy default mode has been configured.
609 connect_timeout = config_kwargs.get('connect_timeout')
610 if connect_timeout is not None:
611 return
612 connect_timeout = self._config_store.get_config_variable(
613 'connect_timeout'
614 )
615 if connect_timeout:
616 config_kwargs['connect_timeout'] = connect_timeout
618 def _compute_request_compression_config(self, config_kwargs):
619 min_size = config_kwargs.get('request_min_compression_size_bytes')
620 disabled = config_kwargs.get('disable_request_compression')
621 if min_size is None:
622 min_size = self._config_store.get_config_variable(
623 'request_min_compression_size_bytes'
624 )
625 # conversion func is skipped so input validation must be done here
626 # regardless if the value is coming from the config store or the
627 # config object
628 min_size = self._validate_min_compression_size(min_size)
629 config_kwargs['request_min_compression_size_bytes'] = min_size
631 if disabled is None:
632 disabled = self._config_store.get_config_variable(
633 'disable_request_compression'
634 )
635 else:
636 # if the user provided a value we must check if it's a boolean
637 disabled = ensure_boolean(disabled)
638 config_kwargs['disable_request_compression'] = disabled
640 def _validate_min_compression_size(self, min_size):
641 min_allowed_min_size = 1
642 max_allowed_min_size = 1048576
643 error_msg_base = (
644 f'Invalid value "{min_size}" for '
645 'request_min_compression_size_bytes.'
646 )
647 try:
648 min_size = int(min_size)
649 except (ValueError, TypeError):
650 msg = (
651 f'{error_msg_base} Value must be an integer. '
652 f'Received {type(min_size)} instead.'
653 )
654 raise botocore.exceptions.InvalidConfigError(error_msg=msg)
655 if not min_allowed_min_size <= min_size <= max_allowed_min_size:
656 msg = (
657 f'{error_msg_base} Value must be between '
658 f'{min_allowed_min_size} and {max_allowed_min_size}.'
659 )
660 raise botocore.exceptions.InvalidConfigError(error_msg=msg)
662 return min_size
664 def _ensure_boolean(self, val):
665 if isinstance(val, bool):
666 return val
667 else:
668 return val.lower() == 'true'
670 def _build_endpoint_resolver(
671 self,
672 endpoints_ruleset_data,
673 partition_data,
674 client_config,
675 service_model,
676 endpoint_region_name,
677 region_name,
678 endpoint_url,
679 endpoint,
680 is_secure,
681 endpoint_bridge,
682 event_emitter,
683 credentials,
684 account_id_endpoint_mode,
685 ):
686 if endpoints_ruleset_data is None:
687 return None
689 # The legacy EndpointResolver is global to the session, but
690 # EndpointRulesetResolver is service-specific. Builtins for
691 # EndpointRulesetResolver must not be derived from the legacy
692 # endpoint resolver's output, including final_args, s3_config,
693 # etc.
694 s3_config_raw = self.compute_s3_config(client_config) or {}
695 service_name_raw = service_model.endpoint_prefix
696 # Maintain complex logic for s3 and sts endpoints for backwards
697 # compatibility.
698 if service_name_raw in ['s3', 'sts'] or region_name is None:
699 eprv2_region_name = endpoint_region_name
700 else:
701 eprv2_region_name = region_name
702 resolver_builtins = self.compute_endpoint_resolver_builtin_defaults(
703 region_name=eprv2_region_name,
704 service_name=service_name_raw,
705 s3_config=s3_config_raw,
706 endpoint_bridge=endpoint_bridge,
707 client_endpoint_url=endpoint_url,
708 legacy_endpoint_url=endpoint.host,
709 credentials=credentials,
710 account_id_endpoint_mode=account_id_endpoint_mode,
711 )
712 # Client context params for s3 conflict with the available settings
713 # in the `s3` parameter on the `Config` object. If the same parameter
714 # is set in both places, the value in the `s3` parameter takes priority.
715 if client_config is not None:
716 client_context = client_config.client_context_params or {}
717 else:
718 client_context = {}
719 if self._is_s3_service(service_name_raw):
720 client_context.update(s3_config_raw)
722 sig_version = (
723 client_config.signature_version
724 if client_config is not None
725 else None
726 )
727 return EndpointRulesetResolver(
728 endpoint_ruleset_data=endpoints_ruleset_data,
729 partition_data=partition_data,
730 service_model=service_model,
731 builtins=resolver_builtins,
732 client_context=client_context,
733 event_emitter=event_emitter,
734 use_ssl=is_secure,
735 requested_auth_scheme=sig_version,
736 )
738 def compute_endpoint_resolver_builtin_defaults(
739 self,
740 region_name,
741 service_name,
742 s3_config,
743 endpoint_bridge,
744 client_endpoint_url,
745 legacy_endpoint_url,
746 credentials,
747 account_id_endpoint_mode,
748 ):
749 # EndpointRulesetResolver rulesets may accept an "SDK::Endpoint" as
750 # input. If the endpoint_url argument of create_client() is set, it
751 # always takes priority.
752 if client_endpoint_url:
753 given_endpoint = client_endpoint_url
754 # If an endpoints.json data file other than the one bundled within
755 # the botocore/data directory is used, the output of legacy
756 # endpoint resolution is provided to EndpointRulesetResolver.
757 elif not endpoint_bridge.resolver_uses_builtin_data():
758 given_endpoint = legacy_endpoint_url
759 else:
760 given_endpoint = None
762 # The endpoint rulesets differ from legacy botocore behavior in whether
763 # forcing path style addressing in incompatible situations raises an
764 # exception or silently ignores the config setting. The
765 # AWS_S3_FORCE_PATH_STYLE parameter is adjusted both here and for each
766 # operation so that the ruleset behavior is backwards compatible.
767 if s3_config.get('use_accelerate_endpoint', False):
768 force_path_style = False
769 elif client_endpoint_url is not None and not is_s3_accelerate_url(
770 client_endpoint_url
771 ):
772 force_path_style = s3_config.get('addressing_style') != 'virtual'
773 else:
774 force_path_style = s3_config.get('addressing_style') == 'path'
776 return {
777 EPRBuiltins.AWS_REGION: region_name,
778 EPRBuiltins.AWS_USE_FIPS: (
779 # SDK_ENDPOINT cannot be combined with AWS_USE_FIPS
780 given_endpoint is None
781 # use legacy resolver's _resolve_endpoint_variant_config_var()
782 # or default to False if it returns None
783 and endpoint_bridge._resolve_endpoint_variant_config_var(
784 'use_fips_endpoint'
785 )
786 or False
787 ),
788 EPRBuiltins.AWS_USE_DUALSTACK: (
789 # SDK_ENDPOINT cannot be combined with AWS_USE_DUALSTACK
790 given_endpoint is None
791 # use legacy resolver's _resolve_use_dualstack_endpoint() and
792 # or default to False if it returns None
793 and endpoint_bridge._resolve_use_dualstack_endpoint(
794 service_name
795 )
796 or False
797 ),
798 EPRBuiltins.AWS_STS_USE_GLOBAL_ENDPOINT: (
799 self._should_set_global_sts_endpoint(
800 region_name=region_name,
801 endpoint_url=None,
802 endpoint_config=None,
803 )
804 ),
805 EPRBuiltins.AWS_S3_USE_GLOBAL_ENDPOINT: (
806 self._should_force_s3_global(region_name, s3_config)
807 ),
808 EPRBuiltins.AWS_S3_ACCELERATE: s3_config.get(
809 'use_accelerate_endpoint', False
810 ),
811 EPRBuiltins.AWS_S3_FORCE_PATH_STYLE: force_path_style,
812 EPRBuiltins.AWS_S3_USE_ARN_REGION: s3_config.get(
813 'use_arn_region', True
814 ),
815 EPRBuiltins.AWS_S3CONTROL_USE_ARN_REGION: s3_config.get(
816 'use_arn_region', False
817 ),
818 EPRBuiltins.AWS_S3_DISABLE_MRAP: s3_config.get(
819 's3_disable_multiregion_access_points', False
820 ),
821 EPRBuiltins.SDK_ENDPOINT: given_endpoint,
822 EPRBuiltins.ACCOUNT_ID: credentials.get_deferred_property(
823 'account_id'
824 )
825 if credentials
826 else None,
827 EPRBuiltins.ACCOUNT_ID_ENDPOINT_MODE: account_id_endpoint_mode,
828 }
830 def _compute_user_agent_appid_config(self, config_kwargs):
831 user_agent_appid = config_kwargs.get('user_agent_appid')
832 if user_agent_appid is None:
833 user_agent_appid = self._config_store.get_config_variable(
834 'user_agent_appid'
835 )
836 if (
837 user_agent_appid is not None
838 and len(user_agent_appid) > USERAGENT_APPID_MAXLEN
839 ):
840 logger.warning(
841 'The configured value for user_agent_appid exceeds the '
842 f'maximum length of {USERAGENT_APPID_MAXLEN} characters.'
843 )
844 config_kwargs['user_agent_appid'] = user_agent_appid
846 def _compute_sigv4a_signing_region_set_config(self, config_kwargs):
847 sigv4a_signing_region_set = config_kwargs.get(
848 'sigv4a_signing_region_set'
849 )
850 if sigv4a_signing_region_set is None:
851 sigv4a_signing_region_set = self._config_store.get_config_variable(
852 'sigv4a_signing_region_set'
853 )
854 config_kwargs['sigv4a_signing_region_set'] = sigv4a_signing_region_set
856 def _compute_checksum_config(self, config_kwargs):
857 self._handle_checksum_config(
858 config_kwargs,
859 config_key="request_checksum_calculation",
860 valid_options=VALID_REQUEST_CHECKSUM_CALCULATION_CONFIG,
861 )
862 self._handle_checksum_config(
863 config_kwargs,
864 config_key="response_checksum_validation",
865 valid_options=VALID_RESPONSE_CHECKSUM_VALIDATION_CONFIG,
866 )
868 def _handle_checksum_config(
869 self,
870 config_kwargs,
871 config_key,
872 valid_options,
873 ):
874 value = config_kwargs.get(config_key)
875 if value is None:
876 value = self._config_store.get_config_variable(config_key)
878 if isinstance(value, str):
879 value = value.lower()
881 if value not in valid_options:
882 raise botocore.exceptions.InvalidChecksumConfigError(
883 config_key=config_key,
884 config_value=value,
885 valid_options=valid_options,
886 )
887 self._register_checksum_config_feature_ids(value, config_key)
888 config_kwargs[config_key] = value
890 def _register_checksum_config_feature_ids(self, value, config_key):
891 checksum_config_feature_id = None
892 if config_key == "request_checksum_calculation":
893 checksum_config_feature_id = (
894 f"FLEXIBLE_CHECKSUMS_REQ_{value.upper()}"
895 )
896 elif config_key == "response_checksum_validation":
897 checksum_config_feature_id = (
898 f"FLEXIBLE_CHECKSUMS_RES_{value.upper()}"
899 )
900 if checksum_config_feature_id is not None:
901 register_feature_id(checksum_config_feature_id)
903 def _compute_account_id_endpoint_mode_config(self, config_kwargs):
904 config_key = 'account_id_endpoint_mode'
906 # Disable account id based endpoint routing for unsigned requests
907 # since there are no credentials to resolve.
908 signature_version = config_kwargs.get('signature_version')
909 if signature_version is botocore.UNSIGNED:
910 config_kwargs[config_key] = 'disabled'
911 return
913 account_id_endpoint_mode = config_kwargs.get(config_key)
914 if account_id_endpoint_mode is None:
915 account_id_endpoint_mode = self._config_store.get_config_variable(
916 config_key
917 )
919 if isinstance(account_id_endpoint_mode, str):
920 account_id_endpoint_mode = account_id_endpoint_mode.lower()
922 if (
923 account_id_endpoint_mode
924 not in VALID_ACCOUNT_ID_ENDPOINT_MODE_CONFIG
925 ):
926 raise botocore.exceptions.InvalidConfigError(
927 error_msg=f"The configured value '{account_id_endpoint_mode}' for '{config_key}' is "
928 f"invalid. Valid values are: {VALID_ACCOUNT_ID_ENDPOINT_MODE_CONFIG}."
929 )
931 config_kwargs[config_key] = account_id_endpoint_mode
933 def _compute_auth_scheme_preference_config(
934 self, client_config, config_kwargs
935 ):
936 config_key = 'auth_scheme_preference'
937 set_in_config_object = False
939 if client_config and client_config.auth_scheme_preference:
940 value = client_config.auth_scheme_preference
941 set_in_config_object = True
942 else:
943 value = self._config_store.get_config_variable(config_key)
945 if value is None:
946 config_kwargs[config_key] = None
947 return
949 if not isinstance(value, str):
950 raise botocore.exceptions.InvalidConfigError(
951 error_msg=(
952 f"{config_key} must be a comma-delimited string. "
953 f"Received {type(value)} instead: {value}."
954 )
955 )
957 value = ','.join(
958 item.replace(' ', '').replace('\t', '')
959 for item in value.split(',')
960 if item.strip()
961 )
963 if set_in_config_object:
964 value = ClientConfigString(value)
966 config_kwargs[config_key] = value
968 def _compute_signature_version_config(self, client_config, config_kwargs):
969 if client_config and client_config.signature_version:
970 value = client_config.signature_version
971 if isinstance(value, str):
972 config_kwargs['signature_version'] = ClientConfigString(value)
975class ConfigObjectWrapper:
976 """Base class to mark values set via in-code Config object."""
978 pass
981class ClientConfigString(str, ConfigObjectWrapper):
982 def __new__(cls, value=None):
983 return super().__new__(cls, value)