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
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 auth_token = self.get_auth_token()
988 endpoint_resolver = self._get_internal_component('endpoint_resolver')
989 exceptions_factory = self._get_internal_component('exceptions_factory')
990 config_store = copy.copy(self.get_component('config_store'))
991 user_agent_creator = self.get_component('user_agent_creator')
992 # Session configuration values for the user agent string are applied
993 # just before each client creation because they may have been modified
994 # at any time between session creation and client creation.
995 user_agent_creator.set_session_config(
996 session_user_agent_name=self.user_agent_name,
997 session_user_agent_version=self.user_agent_version,
998 session_user_agent_extra=self.user_agent_extra,
999 )
1000 defaults_mode = self._resolve_defaults_mode(config, config_store)
1001 if defaults_mode != 'legacy':
1002 smart_defaults_factory = self._get_internal_component(
1003 'smart_defaults_factory'
1004 )
1005 smart_defaults_factory.merge_smart_defaults(
1006 config_store, defaults_mode, region_name
1007 )
1008
1009 self._add_configured_endpoint_provider(
1010 client_name=service_name,
1011 config_store=config_store,
1012 )
1013
1014 user_agent_creator.set_client_features(get_context().features)
1015
1016 client_creator = botocore.client.ClientCreator(
1017 loader,
1018 endpoint_resolver,
1019 self.user_agent(),
1020 event_emitter,
1021 retryhandler,
1022 translate,
1023 response_parser_factory,
1024 exceptions_factory,
1025 config_store,
1026 user_agent_creator=user_agent_creator,
1027 auth_token_resolver=self.get_auth_token,
1028 )
1029 client = client_creator.create_client(
1030 service_name=service_name,
1031 region_name=region_name,
1032 is_secure=use_ssl,
1033 endpoint_url=endpoint_url,
1034 verify=verify,
1035 credentials=credentials,
1036 scoped_config=self.get_scoped_config(),
1037 client_config=config,
1038 api_version=api_version,
1039 auth_token=auth_token,
1040 )
1041 monitor = self._get_internal_component('monitor')
1042 if monitor is not None:
1043 monitor.register(client.meta.events)
1044 self._register_client_plugins(client)
1045 return client
1046
1047 def _resolve_region_name(self, region_name, config):
1048 # Figure out the user-provided region based on the various
1049 # configuration options.
1050 if region_name is None:
1051 if config and config.region_name is not None:
1052 region_name = config.region_name
1053 else:
1054 region_name = self.get_config_variable('region')
1055
1056 validate_region_name(region_name)
1057 # For any client that we create in retrieving credentials
1058 # we want to create it using the same region as specified in
1059 # creating this client. It is important to note though that the
1060 # credentials client is only created once per session. So if a new
1061 # client is created with a different region, its credential resolver
1062 # will use the region of the first client. However, that is not an
1063 # issue as of now because the credential resolver uses only STS and
1064 # the credentials returned at regional endpoints are valid across
1065 # all regions in the partition.
1066 self._last_client_region_used = region_name
1067 return region_name
1068
1069 def _resolve_defaults_mode(self, client_config, config_store):
1070 mode = config_store.get_config_variable('defaults_mode')
1071
1072 if client_config and client_config.defaults_mode:
1073 mode = client_config.defaults_mode
1074
1075 default_config_resolver = self._get_internal_component(
1076 'default_config_resolver'
1077 )
1078 default_modes = default_config_resolver.get_default_modes()
1079 lmode = mode.lower()
1080 if lmode not in default_modes:
1081 raise InvalidDefaultsMode(
1082 mode=mode, valid_modes=', '.join(default_modes)
1083 )
1084
1085 return lmode
1086
1087 def _add_configured_endpoint_provider(self, client_name, config_store):
1088 chain = ConfiguredEndpointProvider(
1089 full_config=self.full_config,
1090 scoped_config=self.get_scoped_config(),
1091 client_name=client_name,
1092 )
1093 config_store.set_config_provider(
1094 logical_name='endpoint_url',
1095 provider=chain,
1096 )
1097
1098 def _missing_cred_vars(self, access_key, secret_key):
1099 if access_key is not None and secret_key is None:
1100 return 'aws_secret_access_key'
1101 if secret_key is not None and access_key is None:
1102 return 'aws_access_key_id'
1103 return None
1104
1105 def get_available_partitions(self):
1106 """Lists the available partitions found on disk
1107
1108 :rtype: list
1109 :return: Returns a list of partition names (e.g., ["aws", "aws-cn"])
1110 """
1111 resolver = self._get_internal_component('endpoint_resolver')
1112 return resolver.get_available_partitions()
1113
1114 def get_partition_for_region(self, region_name):
1115 """Lists the partition name of a particular region.
1116
1117 :type region_name: string
1118 :param region_name: Name of the region to list partition for (e.g.,
1119 us-east-1).
1120
1121 :rtype: string
1122 :return: Returns the respective partition name (e.g., aws).
1123 """
1124 resolver = self._get_internal_component('endpoint_resolver')
1125 return resolver.get_partition_for_region(region_name)
1126
1127 def get_available_regions(
1128 self, service_name, partition_name='aws', allow_non_regional=False
1129 ):
1130 """Lists the region and endpoint names of a particular partition.
1131
1132 :type service_name: string
1133 :param service_name: Name of a service to list endpoint for (e.g., s3).
1134 This parameter accepts a service name (e.g., "elb") or endpoint
1135 prefix (e.g., "elasticloadbalancing").
1136
1137 :type partition_name: string
1138 :param partition_name: Name of the partition to limit endpoints to.
1139 (e.g., aws for the public AWS endpoints, aws-cn for AWS China
1140 endpoints, aws-us-gov for AWS GovCloud (US) Endpoints, etc.
1141
1142 :type allow_non_regional: bool
1143 :param allow_non_regional: Set to True to include endpoints that are
1144 not regional endpoints (e.g., s3-external-1,
1145 fips-us-gov-west-1, etc).
1146 :return: Returns a list of endpoint names (e.g., ["us-east-1"]).
1147 """
1148 resolver = self._get_internal_component('endpoint_resolver')
1149 results = []
1150 try:
1151 service_data = self.get_service_data(service_name)
1152 endpoint_prefix = service_data['metadata'].get(
1153 'endpointPrefix', service_name
1154 )
1155 results = resolver.get_available_endpoints(
1156 endpoint_prefix, partition_name, allow_non_regional
1157 )
1158 except UnknownServiceError:
1159 pass
1160 return results
1161
1162 def _get_ignored_credentials(self, aws_session_token, aws_account_id):
1163 credential_inputs = []
1164 if aws_session_token:
1165 credential_inputs.append('aws_session_token')
1166 if aws_account_id:
1167 credential_inputs.append('aws_account_id')
1168 return ', '.join(credential_inputs) if credential_inputs else None
1169
1170 def _register_client_plugins(self, client):
1171 plugins_list = get_botocore_plugins()
1172 if plugins_list == "DISABLED" or not plugins_list:
1173 return
1174
1175 client_plugins = {}
1176 for plugin in plugins_list.split(','):
1177 try:
1178 name, module = [part.strip() for part in plugin.split('=')]
1179 client_plugins[name] = module
1180 except ValueError:
1181 logger.warning(
1182 "Invalid plugin format: %s. Expected 'name=module'", plugin
1183 )
1184
1185 if client_plugins:
1186 load_client_plugins(client, client_plugins)
1187
1188
1189class ComponentLocator:
1190 """Service locator for session components."""
1191
1192 def __init__(self):
1193 self._components = {}
1194 self._deferred = {}
1195
1196 def get_component(self, name):
1197 if name in self._deferred:
1198 factory = self._deferred[name]
1199 self._components[name] = factory()
1200 # Only delete the component from the deferred dict after
1201 # successfully creating the object from the factory as well as
1202 # injecting the instantiated value into the _components dict.
1203 try:
1204 del self._deferred[name]
1205 except KeyError:
1206 # If we get here, it's likely that get_component was called
1207 # concurrently from multiple threads, and another thread
1208 # already deleted the entry. This means the factory was
1209 # probably called twice, but cleaning up the deferred entry
1210 # should not crash outright.
1211 pass
1212 try:
1213 return self._components[name]
1214 except KeyError:
1215 raise ValueError(f"Unknown component: {name}")
1216
1217 def register_component(self, name, component):
1218 self._components[name] = component
1219 try:
1220 del self._deferred[name]
1221 except KeyError:
1222 pass
1223
1224 def lazy_register_component(self, name, no_arg_factory):
1225 self._deferred[name] = no_arg_factory
1226 try:
1227 del self._components[name]
1228 except KeyError:
1229 pass
1230
1231
1232class SessionVarDict(MutableMapping):
1233 def __init__(self, session, session_vars):
1234 self._session = session
1235 self._store = copy.copy(session_vars)
1236
1237 def __getitem__(self, key):
1238 return self._store[key]
1239
1240 def __setitem__(self, key, value):
1241 self._store[key] = value
1242 self._update_config_store_from_session_vars(key, value)
1243
1244 def __delitem__(self, key):
1245 del self._store[key]
1246
1247 def __iter__(self):
1248 return iter(self._store)
1249
1250 def __len__(self):
1251 return len(self._store)
1252
1253 def _update_config_store_from_session_vars(
1254 self, logical_name, config_options
1255 ):
1256 # This is for backwards compatibility. The new preferred way to
1257 # modify configuration logic is to use the component system to get
1258 # the config_store component from the session, and then update
1259 # a key with a custom config provider(s).
1260 # This backwards compatibility method takes the old session_vars
1261 # list of tuples and and transforms that into a set of updates to
1262 # the config_store component.
1263 config_chain_builder = ConfigChainFactory(session=self._session)
1264 config_name, env_vars, default, typecast = config_options
1265 config_store = self._session.get_component('config_store')
1266 config_store.set_config_provider(
1267 logical_name,
1268 config_chain_builder.create_config_chain(
1269 instance_name=logical_name,
1270 env_var_names=env_vars,
1271 config_property_names=config_name,
1272 default=default,
1273 conversion_func=typecast,
1274 ),
1275 )
1276
1277
1278class SubsetChainConfigFactory:
1279 """A class for creating backwards compatible configuration chains.
1280
1281 This class can be used instead of
1282 :class:`botocore.configprovider.ConfigChainFactory` to make it honor the
1283 methods argument to get_config_variable. This class can be used to filter
1284 out providers that are not in the methods tuple when creating a new config
1285 chain.
1286 """
1287
1288 def __init__(self, session, methods, environ=None):
1289 self._factory = ConfigChainFactory(session, environ)
1290 self._supported_methods = methods
1291
1292 def create_config_chain(
1293 self,
1294 instance_name=None,
1295 env_var_names=None,
1296 config_property_name=None,
1297 default=None,
1298 conversion_func=None,
1299 ):
1300 """Build a config chain following the standard botocore pattern.
1301
1302 This config chain factory will omit any providers not in the methods
1303 tuple provided at initialization. For example if given the tuple
1304 ('instance', 'config',) it will not inject the environment provider
1305 into the standard config chain. This lets the botocore session support
1306 the custom ``methods`` argument for all the default botocore config
1307 variables when calling ``get_config_variable``.
1308 """
1309 if 'instance' not in self._supported_methods:
1310 instance_name = None
1311 if 'env' not in self._supported_methods:
1312 env_var_names = None
1313 if 'config' not in self._supported_methods:
1314 config_property_name = None
1315 return self._factory.create_config_chain(
1316 instance_name=instance_name,
1317 env_var_names=env_var_names,
1318 config_property_names=config_property_name,
1319 default=default,
1320 conversion_func=conversion_func,
1321 )
1322
1323
1324def get_session(env_vars=None):
1325 """
1326 Return a new session object.
1327 """
1328 return Session(env_vars)