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