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 # Emit event to allow service-specific or customer customization of serializer kwargs
174 event_name = f'creating-serializer.{service_name}'
175 serializer_kwargs = {
176 'timestamp_precision': botocore.serialize.TIMESTAMP_PRECISION_DEFAULT
177 }
178 event_emitter.emit(
179 event_name,
180 protocol_name=protocol,
181 service_model=service_model,
182 serializer_kwargs=serializer_kwargs,
183 )
185 serializer = botocore.serialize.create_serializer(
186 protocol,
187 parameter_validation,
188 timestamp_precision=serializer_kwargs['timestamp_precision'],
189 )
190 response_parser = botocore.parsers.create_parser(protocol)
192 ruleset_resolver = self._build_endpoint_resolver(
193 endpoints_ruleset_data,
194 partition_data,
195 client_config,
196 service_model,
197 endpoint_region_name,
198 region_name,
199 configured_endpoint_url,
200 endpoint,
201 is_secure,
202 endpoint_bridge,
203 event_emitter,
204 credentials,
205 account_id_endpoint_mode,
206 )
208 # Copy the session's user agent factory and adds client configuration.
209 client_ua_creator = self._session_ua_creator.with_client_config(
210 new_config
211 )
212 supplied_ua = client_config.user_agent if client_config else None
213 new_config._supplied_user_agent = supplied_ua
215 return {
216 'serializer': serializer,
217 'endpoint': endpoint,
218 'response_parser': response_parser,
219 'event_emitter': event_emitter,
220 'request_signer': signer,
221 'service_model': service_model,
222 'loader': self._loader,
223 'client_config': new_config,
224 'partition': partition,
225 'exceptions_factory': self._exceptions_factory,
226 'endpoint_ruleset_resolver': ruleset_resolver,
227 'user_agent_creator': client_ua_creator,
228 }
230 def compute_client_args(
231 self,
232 service_model,
233 client_config,
234 endpoint_bridge,
235 region_name,
236 endpoint_url,
237 is_secure,
238 scoped_config,
239 ):
240 service_name = service_model.endpoint_prefix
241 protocol = service_model.resolved_protocol
242 parameter_validation = True
243 if client_config and not client_config.parameter_validation:
244 parameter_validation = False
245 elif scoped_config:
246 raw_value = scoped_config.get('parameter_validation')
247 if raw_value is not None:
248 parameter_validation = ensure_boolean(raw_value)
250 s3_config = self.compute_s3_config(client_config)
252 configured_endpoint_url = self._compute_configured_endpoint_url(
253 client_config=client_config,
254 endpoint_url=endpoint_url,
255 )
256 if configured_endpoint_url is not None:
257 register_feature_id('ENDPOINT_OVERRIDE')
259 endpoint_config = self._compute_endpoint_config(
260 service_name=service_name,
261 region_name=region_name,
262 endpoint_url=configured_endpoint_url,
263 is_secure=is_secure,
264 endpoint_bridge=endpoint_bridge,
265 s3_config=s3_config,
266 )
267 endpoint_variant_tags = endpoint_config['metadata'].get('tags', [])
269 # Some third-party libraries expect the final user-agent string in
270 # ``client.meta.config.user_agent``. To maintain backwards
271 # compatibility, the preliminary user-agent string (before any Config
272 # object modifications and without request-specific user-agent
273 # components) is stored in the new Config object's ``user_agent``
274 # property but not used by Botocore itself.
275 preliminary_ua_string = self._session_ua_creator.with_client_config(
276 client_config
277 ).to_string()
278 # Create a new client config to be passed to the client based
279 # on the final values. We do not want the user to be able
280 # to try to modify an existing client with a client config.
281 config_kwargs = dict(
282 region_name=endpoint_config['region_name'],
283 signature_version=endpoint_config['signature_version'],
284 user_agent=preliminary_ua_string,
285 )
286 if 'dualstack' in endpoint_variant_tags:
287 config_kwargs.update(use_dualstack_endpoint=True)
288 if 'fips' in endpoint_variant_tags:
289 config_kwargs.update(use_fips_endpoint=True)
290 if client_config is not None:
291 config_kwargs.update(
292 connect_timeout=client_config.connect_timeout,
293 read_timeout=client_config.read_timeout,
294 max_pool_connections=client_config.max_pool_connections,
295 proxies=client_config.proxies,
296 proxies_config=client_config.proxies_config,
297 retries=client_config.retries,
298 client_cert=client_config.client_cert,
299 inject_host_prefix=client_config.inject_host_prefix,
300 tcp_keepalive=client_config.tcp_keepalive,
301 user_agent_extra=client_config.user_agent_extra,
302 user_agent_appid=client_config.user_agent_appid,
303 request_min_compression_size_bytes=(
304 client_config.request_min_compression_size_bytes
305 ),
306 disable_request_compression=(
307 client_config.disable_request_compression
308 ),
309 client_context_params=client_config.client_context_params,
310 sigv4a_signing_region_set=(
311 client_config.sigv4a_signing_region_set
312 ),
313 request_checksum_calculation=(
314 client_config.request_checksum_calculation
315 ),
316 response_checksum_validation=(
317 client_config.response_checksum_validation
318 ),
319 account_id_endpoint_mode=client_config.account_id_endpoint_mode,
320 auth_scheme_preference=client_config.auth_scheme_preference,
321 )
322 self._compute_retry_config(config_kwargs)
323 self._compute_connect_timeout(config_kwargs)
324 self._compute_user_agent_appid_config(config_kwargs)
325 self._compute_request_compression_config(config_kwargs)
326 self._compute_sigv4a_signing_region_set_config(config_kwargs)
327 self._compute_checksum_config(config_kwargs)
328 self._compute_account_id_endpoint_mode_config(config_kwargs)
329 self._compute_inject_host_prefix(client_config, config_kwargs)
330 self._compute_auth_scheme_preference_config(
331 client_config, config_kwargs
332 )
333 self._compute_signature_version_config(client_config, config_kwargs)
334 s3_config = self.compute_s3_config(client_config)
336 is_s3_service = self._is_s3_service(service_name)
338 if is_s3_service and 'dualstack' in endpoint_variant_tags:
339 if s3_config is None:
340 s3_config = {}
341 s3_config['use_dualstack_endpoint'] = True
343 return {
344 'service_name': service_name,
345 'parameter_validation': parameter_validation,
346 'configured_endpoint_url': configured_endpoint_url,
347 'endpoint_config': endpoint_config,
348 'protocol': protocol,
349 'config_kwargs': config_kwargs,
350 's3_config': s3_config,
351 'socket_options': self._compute_socket_options(
352 scoped_config, client_config
353 ),
354 }
356 def _compute_inject_host_prefix(self, client_config, config_kwargs):
357 # In the cases that a Config object was not provided, or the private value
358 # remained UNSET, we should resolve the value from the config store.
359 if (
360 client_config is None
361 or client_config._inject_host_prefix == 'UNSET'
362 ):
363 configured_disable_host_prefix_injection = (
364 self._config_store.get_config_variable(
365 'disable_host_prefix_injection'
366 )
367 )
368 if configured_disable_host_prefix_injection is not None:
369 config_kwargs[
370 'inject_host_prefix'
371 ] = not configured_disable_host_prefix_injection
372 else:
373 config_kwargs['inject_host_prefix'] = True
375 def _compute_configured_endpoint_url(self, client_config, endpoint_url):
376 if endpoint_url is not None:
377 return endpoint_url
379 if self._ignore_configured_endpoint_urls(client_config):
380 logger.debug("Ignoring configured endpoint URLs.")
381 return endpoint_url
383 return self._config_store.get_config_variable('endpoint_url')
385 def _ignore_configured_endpoint_urls(self, client_config):
386 if (
387 client_config
388 and client_config.ignore_configured_endpoint_urls is not None
389 ):
390 return client_config.ignore_configured_endpoint_urls
392 return self._config_store.get_config_variable(
393 'ignore_configured_endpoint_urls'
394 )
396 def compute_s3_config(self, client_config):
397 s3_configuration = self._config_store.get_config_variable('s3')
399 # Next specific client config values takes precedence over
400 # specific values in the scoped config.
401 if client_config is not None:
402 if client_config.s3 is not None:
403 if s3_configuration is None:
404 s3_configuration = client_config.s3
405 else:
406 # The current s3_configuration dictionary may be
407 # from a source that only should be read from so
408 # we want to be safe and just make a copy of it to modify
409 # before it actually gets updated.
410 s3_configuration = s3_configuration.copy()
411 s3_configuration.update(client_config.s3)
413 return s3_configuration
415 def _is_s3_service(self, service_name):
416 """Whether the service is S3 or S3 Control.
418 Note that throughout this class, service_name refers to the endpoint
419 prefix, not the folder name of the service in botocore/data. For
420 S3 Control, the folder name is 's3control' but the endpoint prefix is
421 's3-control'.
422 """
423 return service_name in ['s3', 's3-control']
425 def _compute_endpoint_config(
426 self,
427 service_name,
428 region_name,
429 endpoint_url,
430 is_secure,
431 endpoint_bridge,
432 s3_config,
433 ):
434 resolve_endpoint_kwargs = {
435 'service_name': service_name,
436 'region_name': region_name,
437 'endpoint_url': endpoint_url,
438 'is_secure': is_secure,
439 'endpoint_bridge': endpoint_bridge,
440 }
441 if service_name == 's3':
442 return self._compute_s3_endpoint_config(
443 s3_config=s3_config, **resolve_endpoint_kwargs
444 )
445 if service_name == 'sts':
446 return self._compute_sts_endpoint_config(**resolve_endpoint_kwargs)
447 return self._resolve_endpoint(**resolve_endpoint_kwargs)
449 def _compute_s3_endpoint_config(
450 self, s3_config, **resolve_endpoint_kwargs
451 ):
452 force_s3_global = self._should_force_s3_global(
453 resolve_endpoint_kwargs['region_name'], s3_config
454 )
455 if force_s3_global:
456 resolve_endpoint_kwargs['region_name'] = None
457 endpoint_config = self._resolve_endpoint(**resolve_endpoint_kwargs)
458 self._set_region_if_custom_s3_endpoint(
459 endpoint_config, resolve_endpoint_kwargs['endpoint_bridge']
460 )
461 # For backwards compatibility reasons, we want to make sure the
462 # client.meta.region_name will remain us-east-1 if we forced the
463 # endpoint to be the global region. Specifically, if this value
464 # changes to aws-global, it breaks logic where a user is checking
465 # for us-east-1 as the global endpoint such as in creating buckets.
466 if force_s3_global and endpoint_config['region_name'] == 'aws-global':
467 endpoint_config['region_name'] = 'us-east-1'
468 return endpoint_config
470 def _should_force_s3_global(self, region_name, s3_config):
471 s3_regional_config = 'legacy'
472 if s3_config and 'us_east_1_regional_endpoint' in s3_config:
473 s3_regional_config = s3_config['us_east_1_regional_endpoint']
474 self._validate_s3_regional_config(s3_regional_config)
476 is_global_region = region_name in ('us-east-1', None)
477 return s3_regional_config == 'legacy' and is_global_region
479 def _validate_s3_regional_config(self, config_val):
480 if config_val not in VALID_REGIONAL_ENDPOINTS_CONFIG:
481 raise botocore.exceptions.InvalidS3UsEast1RegionalEndpointConfigError(
482 s3_us_east_1_regional_endpoint_config=config_val
483 )
485 def _set_region_if_custom_s3_endpoint(
486 self, endpoint_config, endpoint_bridge
487 ):
488 # If a user is providing a custom URL, the endpoint resolver will
489 # refuse to infer a signing region. If we want to default to s3v4,
490 # we have to account for this.
491 if (
492 endpoint_config['signing_region'] is None
493 and endpoint_config['region_name'] is None
494 ):
495 endpoint = endpoint_bridge.resolve('s3')
496 endpoint_config['signing_region'] = endpoint['signing_region']
497 endpoint_config['region_name'] = endpoint['region_name']
499 def _compute_sts_endpoint_config(self, **resolve_endpoint_kwargs):
500 endpoint_config = self._resolve_endpoint(**resolve_endpoint_kwargs)
501 if self._should_set_global_sts_endpoint(
502 resolve_endpoint_kwargs['region_name'],
503 resolve_endpoint_kwargs['endpoint_url'],
504 endpoint_config,
505 ):
506 self._set_global_sts_endpoint(
507 endpoint_config, resolve_endpoint_kwargs['is_secure']
508 )
509 return endpoint_config
511 def _should_set_global_sts_endpoint(
512 self, region_name, endpoint_url, endpoint_config
513 ):
514 has_variant_tags = endpoint_config and endpoint_config.get(
515 'metadata', {}
516 ).get('tags')
517 if endpoint_url or has_variant_tags:
518 return False
519 return (
520 self._get_sts_regional_endpoints_config() == 'legacy'
521 and region_name in LEGACY_GLOBAL_STS_REGIONS
522 )
524 def _get_sts_regional_endpoints_config(self):
525 sts_regional_endpoints_config = self._config_store.get_config_variable(
526 'sts_regional_endpoints'
527 )
528 if not sts_regional_endpoints_config:
529 sts_regional_endpoints_config = 'regional'
530 if (
531 sts_regional_endpoints_config
532 not in VALID_REGIONAL_ENDPOINTS_CONFIG
533 ):
534 raise botocore.exceptions.InvalidSTSRegionalEndpointsConfigError(
535 sts_regional_endpoints_config=sts_regional_endpoints_config
536 )
537 return sts_regional_endpoints_config
539 def _set_global_sts_endpoint(self, endpoint_config, is_secure):
540 scheme = 'https' if is_secure else 'http'
541 endpoint_config['endpoint_url'] = f'{scheme}://sts.amazonaws.com'
542 endpoint_config['signing_region'] = 'us-east-1'
544 def _resolve_endpoint(
545 self,
546 service_name,
547 region_name,
548 endpoint_url,
549 is_secure,
550 endpoint_bridge,
551 ):
552 return endpoint_bridge.resolve(
553 service_name, region_name, endpoint_url, is_secure
554 )
556 def _compute_socket_options(self, scoped_config, client_config=None):
557 # This disables Nagle's algorithm and is the default socket options
558 # in urllib3.
559 socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]
560 client_keepalive = client_config and client_config.tcp_keepalive
561 scoped_keepalive = scoped_config and self._ensure_boolean(
562 scoped_config.get("tcp_keepalive", False)
563 )
564 # Enables TCP Keepalive if specified in client config object or shared config file.
565 if client_keepalive or scoped_keepalive:
566 socket_options.append((socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1))
567 return socket_options
569 def _compute_retry_config(self, config_kwargs):
570 self._compute_retry_max_attempts(config_kwargs)
571 self._compute_retry_mode(config_kwargs)
573 def _compute_retry_max_attempts(self, config_kwargs):
574 # There's a pre-existing max_attempts client config value that actually
575 # means max *retry* attempts. There's also a `max_attempts` we pull
576 # from the config store that means *total attempts*, which includes the
577 # intitial request. We can't change what `max_attempts` means in
578 # client config so we try to normalize everything to a new
579 # "total_max_attempts" variable. We ensure that after this, the only
580 # configuration for "max attempts" is the 'total_max_attempts' key.
581 # An explicitly provided max_attempts in the client config
582 # overrides everything.
583 retries = config_kwargs.get('retries')
584 if retries is not None:
585 if 'total_max_attempts' in retries:
586 retries.pop('max_attempts', None)
587 return
588 if 'max_attempts' in retries:
589 value = retries.pop('max_attempts')
590 # client config max_attempts means total retries so we
591 # have to add one for 'total_max_attempts' to account
592 # for the initial request.
593 retries['total_max_attempts'] = value + 1
594 return
595 # Otherwise we'll check the config store which checks env vars,
596 # config files, etc. There is no default value for max_attempts
597 # so if this returns None and we don't set a default value here.
598 max_attempts = self._config_store.get_config_variable('max_attempts')
599 if max_attempts is not None:
600 if retries is None:
601 retries = {}
602 config_kwargs['retries'] = retries
603 retries['total_max_attempts'] = max_attempts
605 def _compute_retry_mode(self, config_kwargs):
606 retries = config_kwargs.get('retries')
607 if retries is None:
608 retries = {}
609 config_kwargs['retries'] = retries
610 elif 'mode' in retries:
611 # If there's a retry mode explicitly set in the client config
612 # that overrides everything.
613 return
614 retry_mode = self._config_store.get_config_variable('retry_mode')
615 if retry_mode is None:
616 retry_mode = 'legacy'
617 retries['mode'] = retry_mode
619 def _compute_connect_timeout(self, config_kwargs):
620 # Checking if connect_timeout is set on the client config.
621 # If it is not, we check the config_store in case a
622 # non legacy default mode has been configured.
623 connect_timeout = config_kwargs.get('connect_timeout')
624 if connect_timeout is not None:
625 return
626 connect_timeout = self._config_store.get_config_variable(
627 'connect_timeout'
628 )
629 if connect_timeout:
630 config_kwargs['connect_timeout'] = connect_timeout
632 def _compute_request_compression_config(self, config_kwargs):
633 min_size = config_kwargs.get('request_min_compression_size_bytes')
634 disabled = config_kwargs.get('disable_request_compression')
635 if min_size is None:
636 min_size = self._config_store.get_config_variable(
637 'request_min_compression_size_bytes'
638 )
639 # conversion func is skipped so input validation must be done here
640 # regardless if the value is coming from the config store or the
641 # config object
642 min_size = self._validate_min_compression_size(min_size)
643 config_kwargs['request_min_compression_size_bytes'] = min_size
645 if disabled is None:
646 disabled = self._config_store.get_config_variable(
647 'disable_request_compression'
648 )
649 else:
650 # if the user provided a value we must check if it's a boolean
651 disabled = ensure_boolean(disabled)
652 config_kwargs['disable_request_compression'] = disabled
654 def _validate_min_compression_size(self, min_size):
655 min_allowed_min_size = 1
656 max_allowed_min_size = 1048576
657 error_msg_base = (
658 f'Invalid value "{min_size}" for '
659 'request_min_compression_size_bytes.'
660 )
661 try:
662 min_size = int(min_size)
663 except (ValueError, TypeError):
664 msg = (
665 f'{error_msg_base} Value must be an integer. '
666 f'Received {type(min_size)} instead.'
667 )
668 raise botocore.exceptions.InvalidConfigError(error_msg=msg)
669 if not min_allowed_min_size <= min_size <= max_allowed_min_size:
670 msg = (
671 f'{error_msg_base} Value must be between '
672 f'{min_allowed_min_size} and {max_allowed_min_size}.'
673 )
674 raise botocore.exceptions.InvalidConfigError(error_msg=msg)
676 return min_size
678 def _ensure_boolean(self, val):
679 if isinstance(val, bool):
680 return val
681 else:
682 return val.lower() == 'true'
684 def _build_endpoint_resolver(
685 self,
686 endpoints_ruleset_data,
687 partition_data,
688 client_config,
689 service_model,
690 endpoint_region_name,
691 region_name,
692 endpoint_url,
693 endpoint,
694 is_secure,
695 endpoint_bridge,
696 event_emitter,
697 credentials,
698 account_id_endpoint_mode,
699 ):
700 if endpoints_ruleset_data is None:
701 return None
703 # The legacy EndpointResolver is global to the session, but
704 # EndpointRulesetResolver is service-specific. Builtins for
705 # EndpointRulesetResolver must not be derived from the legacy
706 # endpoint resolver's output, including final_args, s3_config,
707 # etc.
708 s3_config_raw = self.compute_s3_config(client_config) or {}
709 service_name_raw = service_model.endpoint_prefix
710 # Maintain complex logic for s3 and sts endpoints for backwards
711 # compatibility.
712 if service_name_raw in ['s3', 'sts'] or region_name is None:
713 eprv2_region_name = endpoint_region_name
714 else:
715 eprv2_region_name = region_name
716 resolver_builtins = self.compute_endpoint_resolver_builtin_defaults(
717 region_name=eprv2_region_name,
718 service_name=service_name_raw,
719 s3_config=s3_config_raw,
720 endpoint_bridge=endpoint_bridge,
721 client_endpoint_url=endpoint_url,
722 legacy_endpoint_url=endpoint.host,
723 credentials=credentials,
724 account_id_endpoint_mode=account_id_endpoint_mode,
725 )
726 # Client context params for s3 conflict with the available settings
727 # in the `s3` parameter on the `Config` object. If the same parameter
728 # is set in both places, the value in the `s3` parameter takes priority.
729 if client_config is not None:
730 client_context = client_config.client_context_params or {}
731 else:
732 client_context = {}
733 if self._is_s3_service(service_name_raw):
734 client_context.update(s3_config_raw)
736 sig_version = (
737 client_config.signature_version
738 if client_config is not None
739 else None
740 )
741 return EndpointRulesetResolver(
742 endpoint_ruleset_data=endpoints_ruleset_data,
743 partition_data=partition_data,
744 service_model=service_model,
745 builtins=resolver_builtins,
746 client_context=client_context,
747 event_emitter=event_emitter,
748 use_ssl=is_secure,
749 requested_auth_scheme=sig_version,
750 )
752 def compute_endpoint_resolver_builtin_defaults(
753 self,
754 region_name,
755 service_name,
756 s3_config,
757 endpoint_bridge,
758 client_endpoint_url,
759 legacy_endpoint_url,
760 credentials,
761 account_id_endpoint_mode,
762 ):
763 # EndpointRulesetResolver rulesets may accept an "SDK::Endpoint" as
764 # input. If the endpoint_url argument of create_client() is set, it
765 # always takes priority.
766 if client_endpoint_url:
767 given_endpoint = client_endpoint_url
768 # If an endpoints.json data file other than the one bundled within
769 # the botocore/data directory is used, the output of legacy
770 # endpoint resolution is provided to EndpointRulesetResolver.
771 elif not endpoint_bridge.resolver_uses_builtin_data():
772 given_endpoint = legacy_endpoint_url
773 else:
774 given_endpoint = None
776 # The endpoint rulesets differ from legacy botocore behavior in whether
777 # forcing path style addressing in incompatible situations raises an
778 # exception or silently ignores the config setting. The
779 # AWS_S3_FORCE_PATH_STYLE parameter is adjusted both here and for each
780 # operation so that the ruleset behavior is backwards compatible.
781 if s3_config.get('use_accelerate_endpoint', False):
782 force_path_style = False
783 elif client_endpoint_url is not None and not is_s3_accelerate_url(
784 client_endpoint_url
785 ):
786 force_path_style = s3_config.get('addressing_style') != 'virtual'
787 else:
788 force_path_style = s3_config.get('addressing_style') == 'path'
790 return {
791 EPRBuiltins.AWS_REGION: region_name,
792 EPRBuiltins.AWS_USE_FIPS: (
793 # SDK_ENDPOINT cannot be combined with AWS_USE_FIPS
794 given_endpoint is None
795 # use legacy resolver's _resolve_endpoint_variant_config_var()
796 # or default to False if it returns None
797 and endpoint_bridge._resolve_endpoint_variant_config_var(
798 'use_fips_endpoint'
799 )
800 or False
801 ),
802 EPRBuiltins.AWS_USE_DUALSTACK: (
803 # SDK_ENDPOINT cannot be combined with AWS_USE_DUALSTACK
804 given_endpoint is None
805 # use legacy resolver's _resolve_use_dualstack_endpoint() and
806 # or default to False if it returns None
807 and endpoint_bridge._resolve_use_dualstack_endpoint(
808 service_name
809 )
810 or False
811 ),
812 EPRBuiltins.AWS_STS_USE_GLOBAL_ENDPOINT: (
813 self._should_set_global_sts_endpoint(
814 region_name=region_name,
815 endpoint_url=None,
816 endpoint_config=None,
817 )
818 ),
819 EPRBuiltins.AWS_S3_USE_GLOBAL_ENDPOINT: (
820 self._should_force_s3_global(region_name, s3_config)
821 ),
822 EPRBuiltins.AWS_S3_ACCELERATE: s3_config.get(
823 'use_accelerate_endpoint', False
824 ),
825 EPRBuiltins.AWS_S3_FORCE_PATH_STYLE: force_path_style,
826 EPRBuiltins.AWS_S3_USE_ARN_REGION: s3_config.get(
827 'use_arn_region', True
828 ),
829 EPRBuiltins.AWS_S3CONTROL_USE_ARN_REGION: s3_config.get(
830 'use_arn_region', False
831 ),
832 EPRBuiltins.AWS_S3_DISABLE_MRAP: s3_config.get(
833 's3_disable_multiregion_access_points', False
834 ),
835 EPRBuiltins.SDK_ENDPOINT: given_endpoint,
836 EPRBuiltins.ACCOUNT_ID: credentials.get_deferred_property(
837 'account_id'
838 )
839 if credentials
840 else None,
841 EPRBuiltins.ACCOUNT_ID_ENDPOINT_MODE: account_id_endpoint_mode,
842 }
844 def _compute_user_agent_appid_config(self, config_kwargs):
845 user_agent_appid = config_kwargs.get('user_agent_appid')
846 if user_agent_appid is None:
847 user_agent_appid = self._config_store.get_config_variable(
848 'user_agent_appid'
849 )
850 if (
851 user_agent_appid is not None
852 and len(user_agent_appid) > USERAGENT_APPID_MAXLEN
853 ):
854 logger.warning(
855 'The configured value for user_agent_appid exceeds the '
856 'maximum length of %d characters.',
857 USERAGENT_APPID_MAXLEN,
858 )
859 config_kwargs['user_agent_appid'] = user_agent_appid
861 def _compute_sigv4a_signing_region_set_config(self, config_kwargs):
862 sigv4a_signing_region_set = config_kwargs.get(
863 'sigv4a_signing_region_set'
864 )
865 if sigv4a_signing_region_set is None:
866 sigv4a_signing_region_set = self._config_store.get_config_variable(
867 'sigv4a_signing_region_set'
868 )
869 config_kwargs['sigv4a_signing_region_set'] = sigv4a_signing_region_set
871 def _compute_checksum_config(self, config_kwargs):
872 self._handle_checksum_config(
873 config_kwargs,
874 config_key="request_checksum_calculation",
875 valid_options=VALID_REQUEST_CHECKSUM_CALCULATION_CONFIG,
876 )
877 self._handle_checksum_config(
878 config_kwargs,
879 config_key="response_checksum_validation",
880 valid_options=VALID_RESPONSE_CHECKSUM_VALIDATION_CONFIG,
881 )
883 def _handle_checksum_config(
884 self,
885 config_kwargs,
886 config_key,
887 valid_options,
888 ):
889 value = config_kwargs.get(config_key)
890 if value is None:
891 value = self._config_store.get_config_variable(config_key)
893 if isinstance(value, str):
894 value = value.lower()
896 if value not in valid_options:
897 raise botocore.exceptions.InvalidChecksumConfigError(
898 config_key=config_key,
899 config_value=value,
900 valid_options=valid_options,
901 )
902 self._register_checksum_config_feature_ids(value, config_key)
903 config_kwargs[config_key] = value
905 def _register_checksum_config_feature_ids(self, value, config_key):
906 checksum_config_feature_id = None
907 if config_key == "request_checksum_calculation":
908 checksum_config_feature_id = (
909 f"FLEXIBLE_CHECKSUMS_REQ_{value.upper()}"
910 )
911 elif config_key == "response_checksum_validation":
912 checksum_config_feature_id = (
913 f"FLEXIBLE_CHECKSUMS_RES_{value.upper()}"
914 )
915 if checksum_config_feature_id is not None:
916 register_feature_id(checksum_config_feature_id)
918 def _compute_account_id_endpoint_mode_config(self, config_kwargs):
919 config_key = 'account_id_endpoint_mode'
921 # Disable account id based endpoint routing for unsigned requests
922 # since there are no credentials to resolve.
923 signature_version = config_kwargs.get('signature_version')
924 if signature_version is botocore.UNSIGNED:
925 config_kwargs[config_key] = 'disabled'
926 return
928 account_id_endpoint_mode = config_kwargs.get(config_key)
929 if account_id_endpoint_mode is None:
930 account_id_endpoint_mode = self._config_store.get_config_variable(
931 config_key
932 )
934 if isinstance(account_id_endpoint_mode, str):
935 account_id_endpoint_mode = account_id_endpoint_mode.lower()
937 if (
938 account_id_endpoint_mode
939 not in VALID_ACCOUNT_ID_ENDPOINT_MODE_CONFIG
940 ):
941 raise botocore.exceptions.InvalidConfigError(
942 error_msg=f"The configured value '{account_id_endpoint_mode}' for '{config_key}' is "
943 f"invalid. Valid values are: {VALID_ACCOUNT_ID_ENDPOINT_MODE_CONFIG}."
944 )
946 config_kwargs[config_key] = account_id_endpoint_mode
948 def _compute_auth_scheme_preference_config(
949 self, client_config, config_kwargs
950 ):
951 config_key = 'auth_scheme_preference'
952 set_in_config_object = False
954 if client_config and client_config.auth_scheme_preference:
955 value = client_config.auth_scheme_preference
956 set_in_config_object = True
957 else:
958 value = self._config_store.get_config_variable(config_key)
960 if value is None:
961 config_kwargs[config_key] = None
962 return
964 if not isinstance(value, str):
965 raise botocore.exceptions.InvalidConfigError(
966 error_msg=(
967 f"{config_key} must be a comma-delimited string. "
968 f"Received {type(value)} instead: {value}."
969 )
970 )
972 value = ','.join(
973 item.replace(' ', '').replace('\t', '')
974 for item in value.split(',')
975 if item.strip()
976 )
978 if set_in_config_object:
979 value = ClientConfigString(value)
981 config_kwargs[config_key] = value
983 def _compute_signature_version_config(self, client_config, config_kwargs):
984 if client_config and client_config.signature_version:
985 value = client_config.signature_version
986 if isinstance(value, str):
987 config_kwargs['signature_version'] = ClientConfigString(value)
990class ConfigObjectWrapper:
991 """Base class to mark values set via in-code Config object."""
993 pass
996class ClientConfigString(str, ConfigObjectWrapper):
997 def __new__(cls, value=None):
998 return super().__new__(cls, value)