1# Copyright (c) 2012-2013 Mitch Garnaat http://garnaat.org/
2# Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"). You
5# may not use this file except in compliance with the License. A copy of
6# the License is located at
7#
8# http://aws.amazon.com/apache2.0/
9#
10# or in the "license" file accompanying this file. This file is
11# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12# ANY KIND, either express or implied. See the License for the specific
13# language governing permissions and limitations under the License.
14"""
15This module contains the main interface to the botocore package, the
16Session object.
17"""
18
19import copy
20import logging
21import os
22import platform
23import socket
24import warnings
25
26import botocore.client
27import botocore.configloader
28import botocore.credentials
29import botocore.tokens
30from botocore import (
31 UNSIGNED,
32 __version__,
33 handlers,
34 invoke_initializers,
35 monitoring,
36 paginate,
37 retryhandler,
38 translate,
39 waiter,
40)
41from botocore.compat import (
42 HAS_CRT, # noqa: F401
43 MutableMapping,
44)
45from botocore.configprovider import (
46 BOTOCORE_DEFAUT_SESSION_VARIABLES,
47 ConfigChainFactory,
48 ConfiguredEndpointProvider,
49 ConfigValueStore,
50 DefaultConfigResolver,
51 SmartDefaultsConfigStoreFactory,
52 create_botocore_default_config_mapping,
53)
54from botocore.context import get_context, with_current_context
55from botocore.errorfactory import ClientExceptionsFactory
56from botocore.exceptions import (
57 ConfigNotFound,
58 InvalidDefaultsMode,
59 PartialCredentialsError,
60 ProfileNotFound,
61 UnknownServiceError,
62)
63from botocore.hooks import (
64 EventAliaser,
65 HierarchicalEmitter,
66 first_non_none_response,
67)
68from botocore.loaders import create_loader
69from botocore.model import ServiceModel
70from botocore.parsers import ResponseParserFactory
71from botocore.plugin import get_botocore_plugins, load_client_plugins
72from botocore.regions import EndpointResolver
73from botocore.useragent import UserAgentString, register_feature_id
74from botocore.utils import (
75 EVENT_ALIASES,
76 IMDSRegionProvider,
77 validate_region_name,
78)
79
80logger = logging.getLogger(__name__)
81
82
83class Session:
84 """
85 The Session object collects together useful functionality
86 from `botocore` as well as important data such as configuration
87 information and credentials into a single, easy-to-use object.
88
89 :ivar available_profiles: A list of profiles defined in the config
90 file associated with this session.
91 :ivar profile: The current profile.
92 """
93
94 SESSION_VARIABLES = copy.copy(BOTOCORE_DEFAUT_SESSION_VARIABLES)
95
96 #: The default format string to use when configuring the botocore logger.
97 LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
98
99 def __init__(
100 self,
101 session_vars=None,
102 event_hooks=None,
103 include_builtin_handlers=True,
104 profile=None,
105 ):
106 """
107 Create a new Session object.
108
109 :type session_vars: dict
110 :param session_vars: A dictionary that is used to override some or all
111 of the environment variables associated with this session. The
112 key/value pairs defined in this dictionary will override the
113 corresponding variables defined in ``SESSION_VARIABLES``.
114
115 :type event_hooks: BaseEventHooks
116 :param event_hooks: The event hooks object to use. If one is not
117 provided, an event hooks object will be automatically created
118 for you.
119
120 :type include_builtin_handlers: bool
121 :param include_builtin_handlers: Indicates whether or not to
122 automatically register builtin handlers.
123
124 :type profile: str
125 :param profile: The name of the profile to use for this
126 session. Note that the profile can only be set when
127 the session is created.
128
129 """
130 if event_hooks is None:
131 self._original_handler = HierarchicalEmitter()
132 else:
133 self._original_handler = event_hooks
134 self._events = EventAliaser(self._original_handler)
135 if include_builtin_handlers:
136 self._register_builtin_handlers(self._events)
137 self.user_agent_name = 'Botocore'
138 self.user_agent_version = __version__
139 self.user_agent_extra = ''
140 # The _profile attribute is just used to cache the value
141 # of the current profile to avoid going through the normal
142 # config lookup process each access time.
143 self._profile = None
144 self._config = None
145 self._credentials = None
146 self._auth_token = None
147 self._profile_map = None
148 # This is a dict that stores per session specific config variable
149 # overrides via set_config_variable().
150 self._session_instance_vars = {}
151 if profile is not None:
152 self._session_instance_vars['profile'] = profile
153 self._client_config = None
154 self._last_client_region_used = None
155 self._components = ComponentLocator()
156 self._internal_components = ComponentLocator()
157 self._register_components()
158 self.session_var_map = SessionVarDict(self, self.SESSION_VARIABLES)
159 if session_vars is not None:
160 self.session_var_map.update(session_vars)
161 invoke_initializers(self)
162
163 def _register_components(self):
164 self._register_credential_provider()
165 self._register_token_provider()
166 self._register_data_loader()
167 self._register_endpoint_resolver()
168 self._register_event_emitter()
169 self._register_response_parser_factory()
170 self._register_exceptions_factory()
171 self._register_config_store()
172 self._register_monitor()
173 self._register_default_config_resolver()
174 self._register_smart_defaults_factory()
175 self._register_user_agent_creator()
176
177 def _register_event_emitter(self):
178 self._components.register_component('event_emitter', self._events)
179
180 def _register_token_provider(self):
181 self._components.lazy_register_component(
182 'token_provider', self._create_token_resolver
183 )
184
185 def _create_token_resolver(self):
186 return botocore.tokens.create_token_resolver(self)
187
188 def _register_credential_provider(self):
189 self._components.lazy_register_component(
190 'credential_provider', self._create_credential_resolver
191 )
192
193 def _create_credential_resolver(self):
194 return botocore.credentials.create_credential_resolver(
195 self, region_name=self._last_client_region_used
196 )
197
198 def _register_data_loader(self):
199 self._components.lazy_register_component(
200 'data_loader',
201 lambda: create_loader(self.get_config_variable('data_path')),
202 )
203
204 def _register_endpoint_resolver(self):
205 def create_default_resolver():
206 loader = self.get_component('data_loader')
207 endpoints, path = loader.load_data_with_path('endpoints')
208 uses_builtin = loader.is_builtin_path(path)
209 return EndpointResolver(endpoints, uses_builtin_data=uses_builtin)
210
211 self._internal_components.lazy_register_component(
212 'endpoint_resolver', create_default_resolver
213 )
214
215 def _register_default_config_resolver(self):
216 def create_default_config_resolver():
217 loader = self.get_component('data_loader')
218 defaults = loader.load_data('sdk-default-configuration')
219 return DefaultConfigResolver(defaults)
220
221 self._internal_components.lazy_register_component(
222 'default_config_resolver', create_default_config_resolver
223 )
224
225 def _register_smart_defaults_factory(self):
226 def create_smart_defaults_factory():
227 default_config_resolver = self._get_internal_component(
228 'default_config_resolver'
229 )
230 imds_region_provider = IMDSRegionProvider(session=self)
231 return SmartDefaultsConfigStoreFactory(
232 default_config_resolver, imds_region_provider
233 )
234
235 self._internal_components.lazy_register_component(
236 'smart_defaults_factory', create_smart_defaults_factory
237 )
238
239 def _register_response_parser_factory(self):
240 self._components.register_component(
241 'response_parser_factory', ResponseParserFactory()
242 )
243
244 def _register_exceptions_factory(self):
245 self._internal_components.register_component(
246 'exceptions_factory', ClientExceptionsFactory()
247 )
248
249 def _register_builtin_handlers(self, events):
250 for spec in handlers.BUILTIN_HANDLERS:
251 if len(spec) == 2:
252 event_name, handler = spec
253 self.register(event_name, handler)
254 else:
255 event_name, handler, register_type = spec
256 if register_type is handlers.REGISTER_FIRST:
257 self._events.register_first(event_name, handler)
258 elif register_type is handlers.REGISTER_LAST:
259 self._events.register_last(event_name, handler)
260
261 def _register_config_store(self):
262 config_store_component = ConfigValueStore(
263 mapping=create_botocore_default_config_mapping(self)
264 )
265 self._components.register_component(
266 'config_store', config_store_component
267 )
268
269 def _register_monitor(self):
270 self._internal_components.lazy_register_component(
271 'monitor', self._create_csm_monitor
272 )
273
274 def _register_user_agent_creator(self):
275 uas = UserAgentString.from_environment()
276 self._components.register_component('user_agent_creator', uas)
277
278 def _create_csm_monitor(self):
279 if self.get_config_variable('csm_enabled'):
280 client_id = self.get_config_variable('csm_client_id')
281 host = self.get_config_variable('csm_host')
282 port = self.get_config_variable('csm_port')
283 handler = monitoring.Monitor(
284 adapter=monitoring.MonitorEventAdapter(),
285 publisher=monitoring.SocketPublisher(
286 socket=socket.socket(socket.AF_INET, socket.SOCK_DGRAM),
287 host=host,
288 port=port,
289 serializer=monitoring.CSMSerializer(
290 csm_client_id=client_id
291 ),
292 ),
293 )
294 return handler
295 return None
296
297 def _get_crt_version(self):
298 user_agent_creator = self.get_component('user_agent_creator')
299 return user_agent_creator._crt_version or 'Unknown'
300
301 @property
302 def available_profiles(self):
303 return list(self._build_profile_map().keys())
304
305 def _build_profile_map(self):
306 # This will build the profile map if it has not been created,
307 # otherwise it will return the cached value. The profile map
308 # is a list of profile names, to the config values for the profile.
309 if self._profile_map is None:
310 self._profile_map = self.full_config['profiles']
311 return self._profile_map
312
313 @property
314 def profile(self):
315 if self._profile is None:
316 profile = self.get_config_variable('profile')
317 self._profile = profile
318 return self._profile
319
320 def get_config_variable(self, logical_name, methods=None):
321 if methods is not None:
322 return self._get_config_variable_with_custom_methods(
323 logical_name, methods
324 )
325 return self.get_component('config_store').get_config_variable(
326 logical_name
327 )
328
329 def _get_config_variable_with_custom_methods(self, logical_name, methods):
330 # If a custom list of methods was supplied we need to perserve the
331 # behavior with the new system. To do so a new chain that is a copy of
332 # the old one will be constructed, but only with the supplied methods
333 # being added to the chain. This chain will be consulted for a value
334 # and then thrown out. This is not efficient, nor is the methods arg
335 # used in botocore, this is just for backwards compatibility.
336 chain_builder = SubsetChainConfigFactory(session=self, methods=methods)
337 mapping = create_botocore_default_config_mapping(self)
338 for name, config_options in self.session_var_map.items():
339 config_name, env_vars, default, typecast = config_options
340 build_chain_config_args = {
341 'conversion_func': typecast,
342 'default': default,
343 }
344 if 'instance' in methods:
345 build_chain_config_args['instance_name'] = name
346 if 'env' in methods:
347 build_chain_config_args['env_var_names'] = env_vars
348 if 'config' in methods:
349 build_chain_config_args['config_property_name'] = config_name
350 mapping[name] = chain_builder.create_config_chain(
351 **build_chain_config_args
352 )
353 config_store_component = ConfigValueStore(mapping=mapping)
354 value = config_store_component.get_config_variable(logical_name)
355 return value
356
357 def set_config_variable(self, logical_name, value):
358 """Set a configuration variable to a specific value.
359
360 By using this method, you can override the normal lookup
361 process used in ``get_config_variable`` by explicitly setting
362 a value. Subsequent calls to ``get_config_variable`` will
363 use the ``value``. This gives you per-session specific
364 configuration values.
365
366 ::
367 >>> # Assume logical name 'foo' maps to env var 'FOO'
368 >>> os.environ['FOO'] = 'myvalue'
369 >>> s.get_config_variable('foo')
370 'myvalue'
371 >>> s.set_config_variable('foo', 'othervalue')
372 >>> s.get_config_variable('foo')
373 'othervalue'
374
375 :type logical_name: str
376 :param logical_name: The logical name of the session variable
377 you want to set. These are the keys in ``SESSION_VARIABLES``.
378 :param value: The value to associate with the config variable.
379
380 """
381 logger.debug(
382 "Setting config variable for %s to %r",
383 logical_name,
384 value,
385 )
386 self._session_instance_vars[logical_name] = value
387
388 def instance_variables(self):
389 return copy.copy(self._session_instance_vars)
390
391 def get_scoped_config(self):
392 """
393 Returns the config values from the config file scoped to the current
394 profile.
395
396 The configuration data is loaded **only** from the config file.
397 It does not resolve variables based on different locations
398 (e.g. first from the session instance, then from environment
399 variables, then from the config file). If you want this lookup
400 behavior, use the ``get_config_variable`` method instead.
401
402 Note that this configuration is specific to a single profile (the
403 ``profile`` session variable).
404
405 If the ``profile`` session variable is set and the profile does
406 not exist in the config file, a ``ProfileNotFound`` exception
407 will be raised.
408
409 :raises: ConfigNotFound, ConfigParseError, ProfileNotFound
410 :rtype: dict
411
412 """
413 profile_name = self.get_config_variable('profile')
414 profile_map = self._build_profile_map()
415 # If a profile is not explicitly set return the default
416 # profile config or an empty config dict if we don't have
417 # a default profile.
418 if profile_name is None:
419 return profile_map.get('default', {})
420 elif profile_name not in profile_map:
421 # Otherwise if they specified a profile, it has to
422 # exist (even if it's the default profile) otherwise
423 # we complain.
424 raise ProfileNotFound(profile=profile_name)
425 else:
426 return profile_map[profile_name]
427
428 @property
429 def full_config(self):
430 """Return the parsed config file.
431
432 The ``get_config`` method returns the config associated with the
433 specified profile. This property returns the contents of the
434 **entire** config file.
435
436 :rtype: dict
437 """
438 if self._config is None:
439 try:
440 config_file = self.get_config_variable('config_file')
441 self._config = botocore.configloader.load_config(config_file)
442 except ConfigNotFound:
443 self._config = {'profiles': {}}
444 try:
445 # Now we need to inject the profiles from the
446 # credentials file. We don't actually need the values
447 # in the creds file, only the profile names so that we
448 # can validate the user is not referring to a nonexistent
449 # profile.
450 cred_file = self.get_config_variable('credentials_file')
451 cred_profiles = botocore.configloader.raw_config_parse(
452 cred_file
453 )
454 for profile in cred_profiles:
455 cred_vars = cred_profiles[profile]
456 if profile not in self._config['profiles']:
457 self._config['profiles'][profile] = cred_vars
458 else:
459 self._config['profiles'][profile].update(cred_vars)
460 except ConfigNotFound:
461 pass
462 return self._config
463
464 def get_default_client_config(self):
465 """Retrieves the default config for creating clients
466
467 :rtype: botocore.client.Config
468 :returns: The default client config object when creating clients. If
469 the value is ``None`` then there is no default config object
470 attached to the session.
471 """
472 return self._client_config
473
474 def set_default_client_config(self, client_config):
475 """Sets the default config for creating clients
476
477 :type client_config: botocore.client.Config
478 :param client_config: The default client config object when creating
479 clients. If the value is ``None`` then there is no default config
480 object attached to the session.
481 """
482 self._client_config = client_config
483
484 def set_credentials(
485 self, access_key, secret_key, token=None, account_id=None
486 ):
487 """
488 Manually create credentials for this session. If you would
489 prefer to use botocore without a config file, environment variables,
490 or IAM roles, you can pass explicit credentials into this
491 method to establish credentials for this session.
492
493 :type access_key: str
494 :param access_key: The access key part of the credentials.
495
496 :type secret_key: str
497 :param secret_key: The secret key part of the credentials.
498
499 :type token: str
500 :param token: An option session token used by STS session
501 credentials.
502
503 :type account_id: str
504 :param account_id: An optional account ID part of the credentials.
505 """
506 self._credentials = botocore.credentials.Credentials(
507 access_key, secret_key, token, account_id=account_id
508 )
509
510 def get_credentials(self):
511 """
512 Return the :class:`botocore.credential.Credential` object
513 associated with this session. If the credentials have not
514 yet been loaded, this will attempt to load them. If they
515 have already been loaded, this will return the cached
516 credentials.
517
518 """
519 if self._credentials is None:
520 self._credentials = self._components.get_component(
521 'credential_provider'
522 ).load_credentials()
523 return self._credentials
524
525 def get_auth_token(self, **kwargs):
526 """
527 Return the :class:`botocore.tokens.AuthToken` object associated with
528 this session. If the authorization token has not yet been loaded, this
529 will attempt to load it. If it has already been loaded, this will
530 return the cached authorization token.
531
532 """
533 provider = self._components.get_component('token_provider')
534
535 signing_name = kwargs.get('signing_name')
536 if signing_name is not None:
537 auth_token = provider.load_token(signing_name=signing_name)
538 if auth_token is not None:
539 return auth_token
540
541 if self._auth_token is None:
542 self._auth_token = provider.load_token()
543 return self._auth_token
544
545 def user_agent(self):
546 """
547 Return a string suitable for use as a User-Agent header.
548 The string will be of the form:
549
550 <agent_name>/<agent_version> Python/<py_ver> <plat_name>/<plat_ver> <exec_env>
551
552 Where:
553
554 - agent_name is the value of the `user_agent_name` attribute
555 of the session object (`Botocore` by default).
556 - agent_version is the value of the `user_agent_version`
557 attribute of the session object (the botocore version by default).
558 by default.
559 - py_ver is the version of the Python interpreter beng used.
560 - plat_name is the name of the platform (e.g. Darwin)
561 - plat_ver is the version of the platform
562 - exec_env is exec-env/$AWS_EXECUTION_ENV
563
564 If ``user_agent_extra`` is not empty, then this value will be
565 appended to the end of the user agent string.
566
567 """
568 base = (
569 f'{self.user_agent_name}/{self.user_agent_version} '
570 f'Python/{platform.python_version()} '
571 f'{platform.system()}/{platform.release()}'
572 )
573 if HAS_CRT:
574 base += f' awscrt/{self._get_crt_version()}'
575 if os.environ.get('AWS_EXECUTION_ENV') is not None:
576 base += ' exec-env/{}'.format(os.environ.get('AWS_EXECUTION_ENV'))
577 if self.user_agent_extra:
578 base += f' {self.user_agent_extra}'
579
580 return base
581
582 def get_data(self, data_path):
583 """
584 Retrieve the data associated with `data_path`.
585
586 :type data_path: str
587 :param data_path: The path to the data you wish to retrieve.
588 """
589 return self.get_component('data_loader').load_data(data_path)
590
591 def get_service_model(self, service_name, api_version=None):
592 """Get the service model object.
593
594 :type service_name: string
595 :param service_name: The service name
596
597 :type api_version: string
598 :param api_version: The API version of the service. If none is
599 provided, then the latest API version will be used.
600
601 :rtype: L{botocore.model.ServiceModel}
602 :return: The botocore service model for the service.
603
604 """
605 service_description = self.get_service_data(service_name, api_version)
606 return ServiceModel(service_description, service_name=service_name)
607
608 def get_waiter_model(self, service_name, api_version=None):
609 loader = self.get_component('data_loader')
610 waiter_config = loader.load_service_model(
611 service_name, 'waiters-2', api_version
612 )
613 return waiter.WaiterModel(waiter_config)
614
615 def get_paginator_model(self, service_name, api_version=None):
616 loader = self.get_component('data_loader')
617 paginator_config = loader.load_service_model(
618 service_name, 'paginators-1', api_version
619 )
620 return paginate.PaginatorModel(paginator_config)
621
622 def get_service_data(self, service_name, api_version=None):
623 """
624 Retrieve the fully merged data associated with a service.
625 """
626 data_path = service_name
627 service_data = self.get_component('data_loader').load_service_model(
628 data_path, type_name='service-2', api_version=api_version
629 )
630 service_id = EVENT_ALIASES.get(service_name, service_name)
631 self._events.emit(
632 f'service-data-loaded.{service_id}',
633 service_data=service_data,
634 service_name=service_name,
635 session=self,
636 )
637 return service_data
638
639 def get_available_services(self):
640 """
641 Return a list of names of available services.
642 """
643 return self.get_component('data_loader').list_available_services(
644 type_name='service-2'
645 )
646
647 def set_debug_logger(self, logger_name='botocore'):
648 """
649 Convenience function to quickly configure full debug output
650 to go to the console.
651 """
652 self.set_stream_logger(logger_name, logging.DEBUG)
653
654 def set_stream_logger(
655 self, logger_name, log_level, stream=None, format_string=None
656 ):
657 """
658 Convenience method to configure a stream logger.
659
660 :type logger_name: str
661 :param logger_name: The name of the logger to configure
662
663 :type log_level: str
664 :param log_level: The log level to set for the logger. This
665 is any param supported by the ``.setLevel()`` method of
666 a ``Log`` object.
667
668 :type stream: file
669 :param stream: A file like object to log to. If none is provided
670 then sys.stderr will be used.
671
672 :type format_string: str
673 :param format_string: The format string to use for the log
674 formatter. If none is provided this will default to
675 ``self.LOG_FORMAT``.
676
677 """
678 log = logging.getLogger(logger_name)
679 log.setLevel(logging.DEBUG)
680
681 ch = logging.StreamHandler(stream)
682 ch.setLevel(log_level)
683
684 # create formatter
685 if format_string is None:
686 format_string = self.LOG_FORMAT
687 formatter = logging.Formatter(format_string)
688
689 # add formatter to ch
690 ch.setFormatter(formatter)
691
692 # add ch to logger
693 log.addHandler(ch)
694
695 def set_file_logger(self, log_level, path, logger_name='botocore'):
696 """
697 Convenience function to quickly configure any level of logging
698 to a file.
699
700 :type log_level: int
701 :param log_level: A log level as specified in the `logging` module
702
703 :type path: string
704 :param path: Path to the log file. The file will be created
705 if it doesn't already exist.
706 """
707 log = logging.getLogger(logger_name)
708 log.setLevel(logging.DEBUG)
709
710 # create console handler and set level to debug
711 ch = logging.FileHandler(path)
712 ch.setLevel(log_level)
713
714 # create formatter
715 formatter = logging.Formatter(self.LOG_FORMAT)
716
717 # add formatter to ch
718 ch.setFormatter(formatter)
719
720 # add ch to logger
721 log.addHandler(ch)
722
723 def register(
724 self, event_name, handler, unique_id=None, unique_id_uses_count=False
725 ):
726 """Register a handler with an event.
727
728 :type event_name: str
729 :param event_name: The name of the event.
730
731 :type handler: callable
732 :param handler: The callback to invoke when the event
733 is emitted. This object must be callable, and must
734 accept ``**kwargs``. If either of these preconditions are
735 not met, a ``ValueError`` will be raised.
736
737 :type unique_id: str
738 :param unique_id: An optional identifier to associate with the
739 registration. A unique_id can only be used once for
740 the entire session registration (unless it is unregistered).
741 This can be used to prevent an event handler from being
742 registered twice.
743
744 :param unique_id_uses_count: boolean
745 :param unique_id_uses_count: Specifies if the event should maintain
746 a count when a ``unique_id`` is registered and unregisted. The
747 event can only be completely unregistered once every register call
748 using the unique id has been matched by an ``unregister`` call.
749 If ``unique_id`` is specified, subsequent ``register``
750 calls must use the same value for ``unique_id_uses_count``
751 as the ``register`` call that first registered the event.
752
753 :raises ValueError: If the call to ``register`` uses ``unique_id``
754 but the value for ``unique_id_uses_count`` differs from the
755 ``unique_id_uses_count`` value declared by the very first
756 ``register`` call for that ``unique_id``.
757 """
758 self._events.register(
759 event_name,
760 handler,
761 unique_id,
762 unique_id_uses_count=unique_id_uses_count,
763 )
764
765 def unregister(
766 self,
767 event_name,
768 handler=None,
769 unique_id=None,
770 unique_id_uses_count=False,
771 ):
772 """Unregister a handler with an event.
773
774 :type event_name: str
775 :param event_name: The name of the event.
776
777 :type handler: callable
778 :param handler: The callback to unregister.
779
780 :type unique_id: str
781 :param unique_id: A unique identifier identifying the callback
782 to unregister. You can provide either the handler or the
783 unique_id, you do not have to provide both.
784
785 :param unique_id_uses_count: boolean
786 :param unique_id_uses_count: Specifies if the event should maintain
787 a count when a ``unique_id`` is registered and unregisted. The
788 event can only be completely unregistered once every ``register``
789 call using the ``unique_id`` has been matched by an ``unregister``
790 call. If the ``unique_id`` is specified, subsequent
791 ``unregister`` calls must use the same value for
792 ``unique_id_uses_count`` as the ``register`` call that first
793 registered the event.
794
795 :raises ValueError: If the call to ``unregister`` uses ``unique_id``
796 but the value for ``unique_id_uses_count`` differs from the
797 ``unique_id_uses_count`` value declared by the very first
798 ``register`` call for that ``unique_id``.
799 """
800 self._events.unregister(
801 event_name,
802 handler=handler,
803 unique_id=unique_id,
804 unique_id_uses_count=unique_id_uses_count,
805 )
806
807 def emit(self, event_name, **kwargs):
808 return self._events.emit(event_name, **kwargs)
809
810 def emit_first_non_none_response(self, event_name, **kwargs):
811 responses = self._events.emit(event_name, **kwargs)
812 return first_non_none_response(responses)
813
814 def get_component(self, name):
815 try:
816 return self._components.get_component(name)
817 except ValueError:
818 if name in ['endpoint_resolver', 'exceptions_factory']:
819 warnings.warn(
820 f'Fetching the {name} component with the get_component() '
821 'method is deprecated as the component has always been '
822 'considered an internal interface of botocore',
823 DeprecationWarning,
824 )
825 return self._internal_components.get_component(name)
826 raise
827
828 def _get_internal_component(self, name):
829 # While this method may be called by botocore classes outside of the
830 # Session, this method should **never** be used by a class that lives
831 # outside of botocore.
832 return self._internal_components.get_component(name)
833
834 def _register_internal_component(self, name, component):
835 # While this method may be called by botocore classes outside of the
836 # Session, this method should **never** be used by a class that lives
837 # outside of botocore.
838 return self._internal_components.register_component(name, component)
839
840 def register_component(self, name, component):
841 self._components.register_component(name, component)
842
843 def lazy_register_component(self, name, component):
844 self._components.lazy_register_component(name, component)
845
846 @with_current_context()
847 def create_client(
848 self,
849 service_name,
850 region_name=None,
851 api_version=None,
852 use_ssl=True,
853 verify=None,
854 endpoint_url=None,
855 aws_access_key_id=None,
856 aws_secret_access_key=None,
857 aws_session_token=None,
858 config=None,
859 aws_account_id=None,
860 ):
861 """Create a botocore client.
862
863 :type service_name: string
864 :param service_name: The name of the service for which a client will
865 be created. You can use the ``Session.get_available_services()``
866 method to get a list of all available service names.
867
868 :type region_name: string
869 :param region_name: The name of the region associated with the client.
870 A client is associated with a single region.
871
872 :type api_version: string
873 :param api_version: The API version to use. By default, botocore will
874 use the latest API version when creating a client. You only need
875 to specify this parameter if you want to use a previous API version
876 of the client.
877
878 :type use_ssl: boolean
879 :param use_ssl: Whether or not to use SSL. By default, SSL is used.
880 Note that not all services support non-ssl connections.
881
882 :type verify: boolean/string
883 :param verify: Whether or not to verify SSL certificates.
884 By default SSL certificates are verified. You can provide the
885 following values:
886
887 * False - do not validate SSL certificates. SSL will still be
888 used (unless use_ssl is False), but SSL certificates
889 will not be verified.
890 * path/to/cert/bundle.pem - A filename of the CA cert bundle to
891 uses. You can specify this argument if you want to use a
892 different CA cert bundle than the one used by botocore.
893
894 :type endpoint_url: string
895 :param endpoint_url: The complete URL to use for the constructed
896 client. Normally, botocore will automatically construct the
897 appropriate URL to use when communicating with a service. You can
898 specify a complete URL (including the "http/https" scheme) to
899 override this behavior. If this value is provided, then
900 ``use_ssl`` is ignored.
901
902 :type aws_access_key_id: string
903 :param aws_access_key_id: The access key to use when creating
904 the client. This is entirely optional, and if not provided,
905 the credentials configured for the session will automatically
906 be used. You only need to provide this argument if you want
907 to override the credentials used for this specific client.
908
909 :type aws_secret_access_key: string
910 :param aws_secret_access_key: The secret key to use when creating
911 the client. Same semantics as aws_access_key_id above.
912
913 :type aws_session_token: string
914 :param aws_session_token: The session token to use when creating
915 the client. Same semantics as aws_access_key_id above.
916
917 :type config: botocore.client.Config
918 :param config: Advanced client configuration options. If a value
919 is specified in the client config, its value will take precedence
920 over environment variables and configuration values, but not over
921 a value passed explicitly to the method. If a default config
922 object is set on the session, the config object used when creating
923 the client will be the result of calling ``merge()`` on the
924 default config with the config provided to this call.
925
926 :type aws_account_id: string
927 :param aws_account_id: The account id to use when creating
928 the client. Same semantics as aws_access_key_id above.
929
930 :rtype: botocore.client.BaseClient
931 :return: A botocore client instance
932
933 """
934 default_client_config = self.get_default_client_config()
935 # If a config is provided and a default config is set, then
936 # use the config resulting from merging the two.
937 if config is not None and default_client_config is not None:
938 config = default_client_config.merge(config)
939 # If a config was not provided then use the default
940 # client config from the session
941 elif default_client_config is not None:
942 config = default_client_config
943
944 region_name = self._resolve_region_name(region_name, config)
945
946 # Figure out the verify value base on the various
947 # configuration options.
948 if verify is None:
949 verify = self.get_config_variable('ca_bundle')
950
951 if api_version is None:
952 api_version = self.get_config_variable('api_versions').get(
953 service_name, None
954 )
955
956 loader = self.get_component('data_loader')
957 event_emitter = self.get_component('event_emitter')
958 response_parser_factory = self.get_component('response_parser_factory')
959 if config is not None and config.signature_version is UNSIGNED:
960 credentials = None
961 elif (
962 aws_access_key_id is not None and aws_secret_access_key is not None
963 ):
964 credentials = botocore.credentials.Credentials(
965 access_key=aws_access_key_id,
966 secret_key=aws_secret_access_key,
967 token=aws_session_token,
968 account_id=aws_account_id,
969 )
970 elif self._missing_cred_vars(aws_access_key_id, aws_secret_access_key):
971 raise PartialCredentialsError(
972 provider='explicit',
973 cred_var=self._missing_cred_vars(
974 aws_access_key_id, aws_secret_access_key
975 ),
976 )
977 else:
978 if ignored_credentials := self._get_ignored_credentials(
979 aws_session_token, aws_account_id
980 ):
981 logger.debug(
982 "Ignoring the following credential-related values which were set without "
983 "an access key id and secret key on the session or client: %s",
984 ignored_credentials,
985 )
986 credentials = self.get_credentials()
987 if getattr(credentials, 'method', None) == 'explicit':
988 register_feature_id('CREDENTIALS_CODE')
989 auth_token = self.get_auth_token()
990 endpoint_resolver = self._get_internal_component('endpoint_resolver')
991 exceptions_factory = self._get_internal_component('exceptions_factory')
992 config_store = copy.copy(self.get_component('config_store'))
993 user_agent_creator = self.get_component('user_agent_creator')
994 # Session configuration values for the user agent string are applied
995 # just before each client creation because they may have been modified
996 # at any time between session creation and client creation.
997 user_agent_creator.set_session_config(
998 session_user_agent_name=self.user_agent_name,
999 session_user_agent_version=self.user_agent_version,
1000 session_user_agent_extra=self.user_agent_extra,
1001 )
1002 defaults_mode = self._resolve_defaults_mode(config, config_store)
1003 if defaults_mode != 'legacy':
1004 smart_defaults_factory = self._get_internal_component(
1005 'smart_defaults_factory'
1006 )
1007 smart_defaults_factory.merge_smart_defaults(
1008 config_store, defaults_mode, region_name
1009 )
1010
1011 self._add_configured_endpoint_provider(
1012 client_name=service_name,
1013 config_store=config_store,
1014 )
1015
1016 user_agent_creator.set_client_features(get_context().features)
1017
1018 client_creator = botocore.client.ClientCreator(
1019 loader,
1020 endpoint_resolver,
1021 self.user_agent(),
1022 event_emitter,
1023 retryhandler,
1024 translate,
1025 response_parser_factory,
1026 exceptions_factory,
1027 config_store,
1028 user_agent_creator=user_agent_creator,
1029 auth_token_resolver=self.get_auth_token,
1030 )
1031 client = client_creator.create_client(
1032 service_name=service_name,
1033 region_name=region_name,
1034 is_secure=use_ssl,
1035 endpoint_url=endpoint_url,
1036 verify=verify,
1037 credentials=credentials,
1038 scoped_config=self.get_scoped_config(),
1039 client_config=config,
1040 api_version=api_version,
1041 auth_token=auth_token,
1042 )
1043 monitor = self._get_internal_component('monitor')
1044 if monitor is not None:
1045 monitor.register(client.meta.events)
1046 self._register_client_plugins(client)
1047 return client
1048
1049 def _resolve_region_name(self, region_name, config):
1050 # Figure out the user-provided region based on the various
1051 # configuration options.
1052 if region_name is None:
1053 if config and config.region_name is not None:
1054 region_name = config.region_name
1055 else:
1056 region_name = self.get_config_variable('region')
1057
1058 validate_region_name(region_name)
1059 # For any client that we create in retrieving credentials
1060 # we want to create it using the same region as specified in
1061 # creating this client. It is important to note though that the
1062 # credentials client is only created once per session. So if a new
1063 # client is created with a different region, its credential resolver
1064 # will use the region of the first client. However, that is not an
1065 # issue as of now because the credential resolver uses only STS and
1066 # the credentials returned at regional endpoints are valid across
1067 # all regions in the partition.
1068 self._last_client_region_used = region_name
1069 return region_name
1070
1071 def _resolve_defaults_mode(self, client_config, config_store):
1072 mode = config_store.get_config_variable('defaults_mode')
1073
1074 if client_config and client_config.defaults_mode:
1075 mode = client_config.defaults_mode
1076
1077 default_config_resolver = self._get_internal_component(
1078 'default_config_resolver'
1079 )
1080 default_modes = default_config_resolver.get_default_modes()
1081 lmode = mode.lower()
1082 if lmode not in default_modes:
1083 raise InvalidDefaultsMode(
1084 mode=mode, valid_modes=', '.join(default_modes)
1085 )
1086
1087 return lmode
1088
1089 def _add_configured_endpoint_provider(self, client_name, config_store):
1090 chain = ConfiguredEndpointProvider(
1091 full_config=self.full_config,
1092 scoped_config=self.get_scoped_config(),
1093 client_name=client_name,
1094 )
1095 config_store.set_config_provider(
1096 logical_name='endpoint_url',
1097 provider=chain,
1098 )
1099
1100 def _missing_cred_vars(self, access_key, secret_key):
1101 if access_key is not None and secret_key is None:
1102 return 'aws_secret_access_key'
1103 if secret_key is not None and access_key is None:
1104 return 'aws_access_key_id'
1105 return None
1106
1107 def get_available_partitions(self):
1108 """Lists the available partitions found on disk
1109
1110 :rtype: list
1111 :return: Returns a list of partition names (e.g., ["aws", "aws-cn"])
1112 """
1113 resolver = self._get_internal_component('endpoint_resolver')
1114 return resolver.get_available_partitions()
1115
1116 def get_partition_for_region(self, region_name):
1117 """Lists the partition name of a particular region.
1118
1119 :type region_name: string
1120 :param region_name: Name of the region to list partition for (e.g.,
1121 us-east-1).
1122
1123 :rtype: string
1124 :return: Returns the respective partition name (e.g., aws).
1125 """
1126 resolver = self._get_internal_component('endpoint_resolver')
1127 return resolver.get_partition_for_region(region_name)
1128
1129 def get_available_regions(
1130 self, service_name, partition_name='aws', allow_non_regional=False
1131 ):
1132 """Lists the region and endpoint names of a particular partition.
1133
1134 :type service_name: string
1135 :param service_name: Name of a service to list endpoint for (e.g., s3).
1136 This parameter accepts a service name (e.g., "elb") or endpoint
1137 prefix (e.g., "elasticloadbalancing").
1138
1139 :type partition_name: string
1140 :param partition_name: Name of the partition to limit endpoints to.
1141 (e.g., aws for the public AWS endpoints, aws-cn for AWS China
1142 endpoints, aws-us-gov for AWS GovCloud (US) Endpoints, etc.
1143
1144 :type allow_non_regional: bool
1145 :param allow_non_regional: Set to True to include endpoints that are
1146 not regional endpoints (e.g., s3-external-1,
1147 fips-us-gov-west-1, etc).
1148 :return: Returns a list of endpoint names (e.g., ["us-east-1"]).
1149 """
1150 resolver = self._get_internal_component('endpoint_resolver')
1151 results = []
1152 try:
1153 service_data = self.get_service_data(service_name)
1154 endpoint_prefix = service_data['metadata'].get(
1155 'endpointPrefix', service_name
1156 )
1157 results = resolver.get_available_endpoints(
1158 endpoint_prefix, partition_name, allow_non_regional
1159 )
1160 except UnknownServiceError:
1161 pass
1162 return results
1163
1164 def _get_ignored_credentials(self, aws_session_token, aws_account_id):
1165 credential_inputs = []
1166 if aws_session_token:
1167 credential_inputs.append('aws_session_token')
1168 if aws_account_id:
1169 credential_inputs.append('aws_account_id')
1170 return ', '.join(credential_inputs) if credential_inputs else None
1171
1172 def _register_client_plugins(self, client):
1173 plugins_list = get_botocore_plugins()
1174 if plugins_list == "DISABLED" or not plugins_list:
1175 return
1176
1177 client_plugins = {}
1178 for plugin in plugins_list.split(','):
1179 try:
1180 name, module = [part.strip() for part in plugin.split('=')]
1181 client_plugins[name] = module
1182 except ValueError:
1183 logger.warning(
1184 "Invalid plugin format: %s. Expected 'name=module'", plugin
1185 )
1186
1187 if client_plugins:
1188 load_client_plugins(client, client_plugins)
1189
1190
1191class ComponentLocator:
1192 """Service locator for session components."""
1193
1194 def __init__(self):
1195 self._components = {}
1196 self._deferred = {}
1197
1198 def get_component(self, name):
1199 if name in self._deferred:
1200 factory = self._deferred[name]
1201 self._components[name] = factory()
1202 # Only delete the component from the deferred dict after
1203 # successfully creating the object from the factory as well as
1204 # injecting the instantiated value into the _components dict.
1205 try:
1206 del self._deferred[name]
1207 except KeyError:
1208 # If we get here, it's likely that get_component was called
1209 # concurrently from multiple threads, and another thread
1210 # already deleted the entry. This means the factory was
1211 # probably called twice, but cleaning up the deferred entry
1212 # should not crash outright.
1213 pass
1214 try:
1215 return self._components[name]
1216 except KeyError:
1217 raise ValueError(f"Unknown component: {name}")
1218
1219 def register_component(self, name, component):
1220 self._components[name] = component
1221 try:
1222 del self._deferred[name]
1223 except KeyError:
1224 pass
1225
1226 def lazy_register_component(self, name, no_arg_factory):
1227 self._deferred[name] = no_arg_factory
1228 try:
1229 del self._components[name]
1230 except KeyError:
1231 pass
1232
1233
1234class SessionVarDict(MutableMapping):
1235 def __init__(self, session, session_vars):
1236 self._session = session
1237 self._store = copy.copy(session_vars)
1238
1239 def __getitem__(self, key):
1240 return self._store[key]
1241
1242 def __setitem__(self, key, value):
1243 self._store[key] = value
1244 self._update_config_store_from_session_vars(key, value)
1245
1246 def __delitem__(self, key):
1247 del self._store[key]
1248
1249 def __iter__(self):
1250 return iter(self._store)
1251
1252 def __len__(self):
1253 return len(self._store)
1254
1255 def _update_config_store_from_session_vars(
1256 self, logical_name, config_options
1257 ):
1258 # This is for backwards compatibility. The new preferred way to
1259 # modify configuration logic is to use the component system to get
1260 # the config_store component from the session, and then update
1261 # a key with a custom config provider(s).
1262 # This backwards compatibility method takes the old session_vars
1263 # list of tuples and and transforms that into a set of updates to
1264 # the config_store component.
1265 config_chain_builder = ConfigChainFactory(session=self._session)
1266 config_name, env_vars, default, typecast = config_options
1267 config_store = self._session.get_component('config_store')
1268 config_store.set_config_provider(
1269 logical_name,
1270 config_chain_builder.create_config_chain(
1271 instance_name=logical_name,
1272 env_var_names=env_vars,
1273 config_property_names=config_name,
1274 default=default,
1275 conversion_func=typecast,
1276 ),
1277 )
1278
1279
1280class SubsetChainConfigFactory:
1281 """A class for creating backwards compatible configuration chains.
1282
1283 This class can be used instead of
1284 :class:`botocore.configprovider.ConfigChainFactory` to make it honor the
1285 methods argument to get_config_variable. This class can be used to filter
1286 out providers that are not in the methods tuple when creating a new config
1287 chain.
1288 """
1289
1290 def __init__(self, session, methods, environ=None):
1291 self._factory = ConfigChainFactory(session, environ)
1292 self._supported_methods = methods
1293
1294 def create_config_chain(
1295 self,
1296 instance_name=None,
1297 env_var_names=None,
1298 config_property_name=None,
1299 default=None,
1300 conversion_func=None,
1301 ):
1302 """Build a config chain following the standard botocore pattern.
1303
1304 This config chain factory will omit any providers not in the methods
1305 tuple provided at initialization. For example if given the tuple
1306 ('instance', 'config',) it will not inject the environment provider
1307 into the standard config chain. This lets the botocore session support
1308 the custom ``methods`` argument for all the default botocore config
1309 variables when calling ``get_config_variable``.
1310 """
1311 if 'instance' not in self._supported_methods:
1312 instance_name = None
1313 if 'env' not in self._supported_methods:
1314 env_var_names = None
1315 if 'config' not in self._supported_methods:
1316 config_property_name = None
1317 return self._factory.create_config_chain(
1318 instance_name=instance_name,
1319 env_var_names=env_var_names,
1320 config_property_names=config_property_name,
1321 default=default,
1322 conversion_func=conversion_func,
1323 )
1324
1325
1326def get_session(env_vars=None):
1327 """
1328 Return a new session object.
1329 """
1330 return Session(env_vars)