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