session.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927
  1. # Copyright (c) 2012-2013 LiuYC https://github.com/liuyichen/
  2. # Copyright 2012-2014 ksyun.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://www.apache.org/licenses/LICENSE-2.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. """
  15. This module contains the main interface to the kscore package, the
  16. Session object.
  17. """
  18. import copy
  19. import logging
  20. import os
  21. import platform
  22. from kscore import __version__
  23. import kscore.configloader
  24. import kscore.credentials
  25. import kscore.domain
  26. import kscore.client
  27. from kscore.exceptions import ConfigNotFound, ProfileNotFound
  28. from kscore.exceptions import UnknownServiceError
  29. from kscore import handlers
  30. from kscore.hooks import HierarchicalEmitter, first_non_none_response
  31. from kscore.loaders import create_loader
  32. from kscore.parsers import ResponseParserFactory
  33. from kscore.regions import EndpointResolver
  34. from kscore.model import ServiceModel
  35. from kscore import paginate
  36. from kscore import waiter
  37. from kscore import retryhandler, translate
  38. class Session(object):
  39. """
  40. The Session object collects together useful functionality
  41. from `kscore` as well as important data such as configuration
  42. information and credentials into a single, easy-to-use object.
  43. :ivar available_profiles: A list of profiles defined in the config
  44. file associated with this session.
  45. :ivar profile: The current profile.
  46. """
  47. #: A default dictionary that maps the logical names for session variables
  48. #: to the specific environment variables and configuration file names
  49. #: that contain the values for these variables.
  50. #: When creating a new Session object, you can pass in your own dictionary
  51. #: to remap the logical names or to add new logical names. You can then
  52. #: get the current value for these variables by using the
  53. #: ``get_config_variable`` method of the :class:`kscore.session.Session`
  54. #: class.
  55. #: These form the keys of the dictionary. The values in the dictionary
  56. #: are tuples of (<config_name>, <environment variable>, <default value>,
  57. #: <conversion func>).
  58. #: The conversion func is a function that takes the configuration value
  59. #: as an argument and returns the converted value. If this value is
  60. #: None, then the configuration value is returned unmodified. This
  61. #: conversion function can be used to type convert config values to
  62. #: values other than the default values of strings.
  63. #: The ``profile`` and ``config_file`` variables should always have a
  64. #: None value for the first entry in the tuple because it doesn't make
  65. #: sense to look inside the config file for the location of the config
  66. #: file or for the default profile to use.
  67. #: The ``config_name`` is the name to look for in the configuration file,
  68. #: the ``env var`` is the OS environment variable (``os.environ``) to
  69. #: use, and ``default_value`` is the value to use if no value is otherwise
  70. #: found.
  71. SESSION_VARIABLES = {
  72. # logical: config_file, env_var, default_value, conversion_func
  73. 'profile': (None, ['KS_DEFAULT_PROFILE', 'KS_PROFILE'], None, None),
  74. 'region': ('region', 'KS_DEFAULT_REGION', None, None),
  75. 'data_path': ('data_path', 'KS_DATA_PATH', None, None),
  76. 'config_file': (None, 'KS_CONFIG_FILE', '~/.ks/config', None),
  77. 'ca_bundle': ('ca_bundle', 'KS_CA_BUNDLE', None, None),
  78. 'api_versions': ('api_versions', None, {}, None),
  79. 'dynamic_loader': ("dynamic_loader", "KS_DYNAMIC_LOADER", "YAML", None),
  80. # This is the shared credentials file amongst sdks.
  81. 'credentials_file': (None, 'KS_SHARED_CREDENTIALS_FILE',
  82. '~/.ks/credentials', None),
  83. # These variables only exist in the config file.
  84. # This is the number of seconds until we time out a request to
  85. # the instance metadata service.
  86. 'metadata_service_timeout': ('metadata_service_timeout',
  87. 'KS_METADATA_SERVICE_TIMEOUT', 1, int),
  88. # This is the number of request attempts we make until we give
  89. # up trying to retrieve data from the instance metadata service.
  90. 'metadata_service_num_attempts': ('metadata_service_num_attempts',
  91. 'KS_METADATA_SERVICE_NUM_ATTEMPTS', 1, int),
  92. }
  93. #: The default format string to use when configuring the kscore logger.
  94. LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
  95. def __init__(self, session_vars=None, event_hooks=None,
  96. include_builtin_handlers=True, profile=None):
  97. """
  98. Create a new Session object.
  99. :type session_vars: dict
  100. :param session_vars: A dictionary that is used to override some or all
  101. of the environment variables associated with this session. The
  102. key/value pairs defined in this dictionary will override the
  103. corresponding variables defined in ``SESSION_VARIABLES``.
  104. :type event_hooks: BaseEventHooks
  105. :param event_hooks: The event hooks object to use. If one is not
  106. provided, an event hooks object will be automatically created
  107. for you.
  108. :type include_builtin_handlers: bool
  109. :param include_builtin_handlers: Indicates whether or not to
  110. automatically register builtin handlers.
  111. :type profile: str
  112. :param profile: The name of the profile to use for this
  113. session. Note that the profile can only be set when
  114. the session is created.
  115. """
  116. self.session_var_map = copy.copy(self.SESSION_VARIABLES)
  117. if session_vars:
  118. self.session_var_map.update(session_vars)
  119. if event_hooks is None:
  120. self._events = HierarchicalEmitter()
  121. else:
  122. self._events = event_hooks
  123. if include_builtin_handlers:
  124. self._register_builtin_handlers(self._events)
  125. self.user_agent_name = 'KSCore'
  126. self.user_agent_version = __version__
  127. self.user_agent_extra = ''
  128. # The _profile attribute is just used to cache the value
  129. # of the current profile to avoid going through the normal
  130. # config lookup process each access time.
  131. self._profile = None
  132. self._config = None
  133. self._credentials = None
  134. self._domain = None
  135. self._profile_map = None
  136. # This is a dict that stores per session specific config variable
  137. # overrides via set_config_variable().
  138. self._session_instance_vars = {}
  139. if profile is not None:
  140. self._session_instance_vars['profile'] = profile
  141. self._client_config = None
  142. self._components = ComponentLocator()
  143. self._register_components()
  144. def _register_components(self):
  145. self._register_credential_provider()
  146. self._register_domain_provider()
  147. self._register_data_loader()
  148. self._register_endpoint_resolver()
  149. self._register_event_emitter()
  150. self._register_response_parser_factory()
  151. def _register_event_emitter(self):
  152. self._components.register_component('event_emitter', self._events)
  153. def _register_credential_provider(self):
  154. self._components.lazy_register_component(
  155. 'credential_provider',
  156. lambda: kscore.credentials.create_credential_resolver(self))
  157. def _register_domain_provider(self):
  158. self._components.lazy_register_component(
  159. 'domain_provider',
  160. lambda: kscore.domain.create_domain_resolver(self))
  161. def _register_data_loader(self):
  162. dynamic_loader = self.get_config_variable('dynamic_loader')
  163. self._components.lazy_register_component(
  164. 'data_loader',
  165. lambda: create_loader(self.get_config_variable('data_path'), dynamic_loader))
  166. def _register_endpoint_resolver(self):
  167. def create_default_resolver():
  168. loader = self.get_component('data_loader')
  169. endpoints = loader.load_data('endpoints')
  170. return EndpointResolver(endpoints)
  171. self._components.lazy_register_component(
  172. 'endpoint_resolver', create_default_resolver)
  173. def _register_response_parser_factory(self):
  174. self._components.register_component('response_parser_factory',
  175. ResponseParserFactory())
  176. def _register_builtin_handlers(self, events):
  177. for spec in handlers.BUILTIN_HANDLERS:
  178. if len(spec) == 2:
  179. event_name, handler = spec
  180. self.register(event_name, handler)
  181. else:
  182. event_name, handler, register_type = spec
  183. if register_type is handlers.REGISTER_FIRST:
  184. self._events.register_first(event_name, handler)
  185. elif register_type is handlers.REGISTER_LAST:
  186. self._events.register_last(event_name, handler)
  187. @property
  188. def available_profiles(self):
  189. return list(self._build_profile_map().keys())
  190. def _build_profile_map(self):
  191. # This will build the profile map if it has not been created,
  192. # otherwise it will return the cached value. The profile map
  193. # is a list of profile names, to the config values for the profile.
  194. if self._profile_map is None:
  195. self._profile_map = self.full_config['profiles']
  196. return self._profile_map
  197. @property
  198. def profile(self):
  199. if self._profile is None:
  200. profile = self.get_config_variable('profile')
  201. self._profile = profile
  202. return self._profile
  203. def get_config_variable(self, logical_name,
  204. methods=('instance', 'env', 'config')):
  205. """
  206. Retrieve the value associated with the specified logical_name
  207. from the environment or the config file. Values found in the
  208. environment variable take precedence of values found in the
  209. config file. If no value can be found, a None will be returned.
  210. :type logical_name: str
  211. :param logical_name: The logical name of the session variable
  212. you want to retrieve. This name will be mapped to the
  213. appropriate environment variable name for this session as
  214. well as the appropriate config file entry.
  215. :type method: tuple
  216. :param method: Defines which methods will be used to find
  217. the variable value. By default, all available methods
  218. are tried but you can limit which methods are used
  219. by supplying a different value to this parameter.
  220. Valid choices are: instance|env|config
  221. :returns: value of variable or None if not defined.
  222. """
  223. # Handle all the short circuit special cases first.
  224. if logical_name not in self.session_var_map:
  225. return
  226. # Do the actual lookups. We need to handle
  227. # 'instance', 'env', and 'config' locations, in that order.
  228. value = None
  229. var_config = self.session_var_map[logical_name]
  230. if self._found_in_instance_vars(methods, logical_name):
  231. return self._session_instance_vars[logical_name]
  232. elif self._found_in_env(methods, var_config):
  233. value = self._retrieve_from_env(var_config[1], os.environ)
  234. elif self._found_in_config_file(methods, var_config):
  235. value = self.get_scoped_config()[var_config[0]]
  236. if value is None:
  237. value = var_config[2]
  238. if var_config[3] is not None:
  239. value = var_config[3](value)
  240. return value
  241. def _found_in_instance_vars(self, methods, logical_name):
  242. if 'instance' in methods:
  243. return logical_name in self._session_instance_vars
  244. return False
  245. def _found_in_env(self, methods, var_config):
  246. return (
  247. 'env' in methods and
  248. var_config[1] is not None and
  249. self._retrieve_from_env(var_config[1], os.environ) is not None)
  250. def _found_in_config_file(self, methods, var_config):
  251. if 'config' in methods and var_config[0] is not None:
  252. return var_config[0] in self.get_scoped_config()
  253. return False
  254. def _retrieve_from_env(self, names, environ):
  255. # We need to handle the case where names is either
  256. # a single value or a list of variables.
  257. if not isinstance(names, list):
  258. names = [names]
  259. for name in names:
  260. if name in environ:
  261. return environ[name]
  262. return None
  263. def set_config_variable(self, logical_name, value):
  264. """Set a configuration variable to a specific value.
  265. By using this method, you can override the normal lookup
  266. process used in ``get_config_variable`` by explicitly setting
  267. a value. Subsequent calls to ``get_config_variable`` will
  268. use the ``value``. This gives you per-session specific
  269. configuration values.
  270. ::
  271. >>> # Assume logical name 'foo' maps to env var 'FOO'
  272. >>> os.environ['FOO'] = 'myvalue'
  273. >>> s.get_config_variable('foo')
  274. 'myvalue'
  275. >>> s.set_config_variable('foo', 'othervalue')
  276. >>> s.get_config_variable('foo')
  277. 'othervalue'
  278. :type logical_name: str
  279. :param logical_name: The logical name of the session variable
  280. you want to set. These are the keys in ``SESSION_VARIABLES``.
  281. :param value: The value to associate with the config variable.
  282. """
  283. self._session_instance_vars[logical_name] = value
  284. def get_scoped_config(self):
  285. """
  286. Returns the config values from the config file scoped to the current
  287. profile.
  288. The configuration data is loaded **only** from the config file.
  289. It does not resolve variables based on different locations
  290. (e.g. first from the session instance, then from environment
  291. variables, then from the config file). If you want this lookup
  292. behavior, use the ``get_config_variable`` method instead.
  293. Note that this configuration is specific to a single profile (the
  294. ``profile`` session variable).
  295. If the ``profile`` session variable is set and the profile does
  296. not exist in the config file, a ``ProfileNotFound`` exception
  297. will be raised.
  298. :raises: ConfigNotFound, ConfigParseError, ProfileNotFound
  299. :rtype: dict
  300. """
  301. profile_name = self.get_config_variable('profile')
  302. profile_map = self._build_profile_map()
  303. # If a profile is not explicitly set return the default
  304. # profile config or an empty config dict if we don't have
  305. # a default profile.
  306. if profile_name is None:
  307. return profile_map.get('default', {})
  308. elif profile_name not in profile_map:
  309. # Otherwise if they specified a profile, it has to
  310. # exist (even if it's the default profile) otherwise
  311. # we complain.
  312. raise ProfileNotFound(profile=profile_name)
  313. else:
  314. return profile_map[profile_name]
  315. @property
  316. def full_config(self):
  317. """Return the parsed config file.
  318. The ``get_config`` method returns the config associated with the
  319. specified profile. This property returns the contents of the
  320. **entire** config file.
  321. :rtype: dict
  322. """
  323. if self._config is None:
  324. try:
  325. config_file = self.get_config_variable('config_file')
  326. self._config = kscore.configloader.load_config(config_file)
  327. except ConfigNotFound:
  328. self._config = {'profiles': {}}
  329. try:
  330. # Now we need to inject the profiles from the
  331. # credentials file. We don't actually need the values
  332. # in the creds file, only the profile names so that we
  333. # can validate the user is not referring to a nonexistent
  334. # profile.
  335. cred_file = self.get_config_variable('credentials_file')
  336. cred_profiles = kscore.configloader.raw_config_parse(cred_file)
  337. for profile in cred_profiles:
  338. cred_vars = cred_profiles[profile]
  339. if profile not in self._config['profiles']:
  340. self._config['profiles'][profile] = cred_vars
  341. else:
  342. self._config['profiles'][profile].update(cred_vars)
  343. except ConfigNotFound:
  344. pass
  345. return self._config
  346. def get_default_client_config(self):
  347. """Retrieves the default config for creating clients
  348. :rtype: kscore.client.Config
  349. :returns: The default client config object when creating clients. If
  350. the value is ``None`` then there is no default config object
  351. attached to the session.
  352. """
  353. return self._client_config
  354. def set_default_client_config(self, client_config):
  355. """Sets the default config for creating clients
  356. :type client_config: kscore.client.Config
  357. :param client_config: The default client config object when creating
  358. clients. If the value is ``None`` then there is no default config
  359. object attached to the session.
  360. """
  361. self._client_config = client_config
  362. def set_domain(self, domain):
  363. self._domain = kscore.domain.Domain(domain)
  364. def get_domain(self):
  365. if self._domain is None:
  366. self._domain = self._components.get_component(
  367. 'domain_provider').load_domain()
  368. return self._domain
  369. def set_credentials(self, access_key, secret_key, token=None):
  370. """
  371. Manually create credentials for this session. If you would
  372. prefer to use kscore without a config file, environment variables,
  373. or IAM roles, you can pass explicit credentials into this
  374. method to establish credentials for this session.
  375. :type access_key: str
  376. :param access_key: The access key part of the credentials.
  377. :type secret_key: str
  378. :param secret_key: The secret key part of the credentials.
  379. :type token: str
  380. :param token: An option session token used by STS session
  381. credentials.
  382. """
  383. self._credentials = kscore.credentials.Credentials(access_key,
  384. secret_key,
  385. token)
  386. def get_credentials(self):
  387. """
  388. Return the :class:`kscore.credential.Credential` object
  389. associated with this session. If the credentials have not
  390. yet been loaded, this will attempt to load them. If they
  391. have already been loaded, this will return the cached
  392. credentials.
  393. """
  394. if self._credentials is None:
  395. self._credentials = self._components.get_component(
  396. 'credential_provider').load_credentials()
  397. return self._credentials
  398. def user_agent(self):
  399. """
  400. Return a string suitable for use as a User-Agent header.
  401. The string will be of the form:
  402. <agent_name>/<agent_version> Python/<py_ver> <plat_name>/<plat_ver>
  403. Where:
  404. - agent_name is the value of the `user_agent_name` attribute
  405. of the session object (`kscore` by default).
  406. - agent_version is the value of the `user_agent_version`
  407. attribute of the session object (the kscore version by default).
  408. by default.
  409. - py_ver is the version of the Python interpreter beng used.
  410. - plat_name is the name of the platform (e.g. Darwin)
  411. - plat_ver is the version of the platform
  412. If ``user_agent_extra`` is not empty, then this value will be
  413. appended to the end of the user agent string.
  414. """
  415. base = '%s/%s Python/%s %s/%s' % (self.user_agent_name,
  416. self.user_agent_version,
  417. platform.python_version(),
  418. platform.system(),
  419. platform.release())
  420. if self.user_agent_extra:
  421. base += ' %s' % self.user_agent_extra
  422. return base
  423. def get_data(self, data_path):
  424. """
  425. Retrieve the data associated with `data_path`.
  426. :type data_path: str
  427. :param data_path: The path to the data you wish to retrieve.
  428. """
  429. return self.get_component('data_loader').load_data(data_path)
  430. def get_service_model(self, service_name, api_version=None):
  431. """Get the service model object.
  432. :type service_name: string
  433. :param service_name: The service name
  434. :type api_version: string
  435. :param api_version: The API version of the service. If none is
  436. provided, then the latest API version will be used.
  437. :rtype: L{kscore.model.ServiceModel}
  438. :return: The kscore service model for the service.
  439. """
  440. service_description = self.get_service_data(service_name, api_version)
  441. return ServiceModel(service_description, service_name=service_name)
  442. def get_waiter_model(self, service_name, api_version=None):
  443. loader = self.get_component('data_loader')
  444. waiter_config = loader.load_service_model(
  445. service_name, 'waiters-2', api_version)
  446. return waiter.WaiterModel(waiter_config)
  447. def get_paginator_model(self, service_name, api_version=None):
  448. loader = self.get_component('data_loader')
  449. paginator_config = loader.load_service_model(
  450. service_name, 'paginators-1', api_version)
  451. return paginate.PaginatorModel(paginator_config)
  452. def get_service_data(self, service_name, api_version=None):
  453. """
  454. Retrieve the fully merged data associated with a service.
  455. """
  456. data_path = service_name
  457. service_data = self.get_component('data_loader').load_service_model(
  458. data_path,
  459. type_name='service-2',
  460. api_version=api_version
  461. )
  462. self._events.emit('service-data-loaded.%s' % service_name,
  463. service_data=service_data,
  464. service_name=service_name, session=self)
  465. return service_data
  466. def get_available_services(self):
  467. """
  468. Return a list of names of available services.
  469. """
  470. return self.get_component('data_loader')\
  471. .list_available_services(type_name='service-2')
  472. def set_debug_logger(self, logger_name='kscore'):
  473. """
  474. Convenience function to quickly configure full debug output
  475. to go to the console.
  476. """
  477. self.set_stream_logger(logger_name, logging.DEBUG)
  478. def set_stream_logger(self, logger_name, log_level, stream=None,
  479. format_string=None):
  480. """
  481. Convenience method to configure a stream logger.
  482. :type logger_name: str
  483. :param logger_name: The name of the logger to configure
  484. :type log_level: str
  485. :param log_level: The log level to set for the logger. This
  486. is any param supported by the ``.setLevel()`` method of
  487. a ``Log`` object.
  488. :type stream: file
  489. :param stream: A file like object to log to. If none is provided
  490. then sys.stderr will be used.
  491. :type format_string: str
  492. :param format_string: The format string to use for the log
  493. formatter. If none is provided this will default to
  494. ``self.LOG_FORMAT``.
  495. """
  496. log = logging.getLogger(logger_name)
  497. log.setLevel(logging.DEBUG)
  498. ch = logging.StreamHandler(stream)
  499. ch.setLevel(log_level)
  500. # create formatter
  501. if format_string is None:
  502. format_string = self.LOG_FORMAT
  503. formatter = logging.Formatter(format_string)
  504. # add formatter to ch
  505. ch.setFormatter(formatter)
  506. # add ch to logger
  507. log.addHandler(ch)
  508. def set_file_logger(self, log_level, path, logger_name='kscore'):
  509. """
  510. Convenience function to quickly configure any level of logging
  511. to a file.
  512. :type log_level: int
  513. :param log_level: A log level as specified in the `logging` module
  514. :type path: string
  515. :param path: Path to the log file. The file will be created
  516. if it doesn't already exist.
  517. """
  518. log = logging.getLogger(logger_name)
  519. log.setLevel(logging.DEBUG)
  520. # create console handler and set level to debug
  521. ch = logging.FileHandler(path)
  522. ch.setLevel(log_level)
  523. # create formatter
  524. formatter = logging.Formatter(self.LOG_FORMAT)
  525. # add formatter to ch
  526. ch.setFormatter(formatter)
  527. # add ch to logger
  528. log.addHandler(ch)
  529. def register(self, event_name, handler, unique_id=None,
  530. unique_id_uses_count=False):
  531. """Register a handler with an event.
  532. :type event_name: str
  533. :param event_name: The name of the event.
  534. :type handler: callable
  535. :param handler: The callback to invoke when the event
  536. is emitted. This object must be callable, and must
  537. accept ``**kwargs``. If either of these preconditions are
  538. not met, a ``ValueError`` will be raised.
  539. :type unique_id: str
  540. :param unique_id: An optional identifier to associate with the
  541. registration. A unique_id can only be used once for
  542. the entire session registration (unless it is unregistered).
  543. This can be used to prevent an event handler from being
  544. registered twice.
  545. :param unique_id_uses_count: boolean
  546. :param unique_id_uses_count: Specifies if the event should maintain
  547. a count when a ``unique_id`` is registered and unregisted. The
  548. event can only be completely unregistered once every register call
  549. using the unique id has been matched by an ``unregister`` call.
  550. If ``unique_id`` is specified, subsequent ``register``
  551. calls must use the same value for ``unique_id_uses_count``
  552. as the ``register`` call that first registered the event.
  553. :raises ValueError: If the call to ``register`` uses ``unique_id``
  554. but the value for ``unique_id_uses_count`` differs from the
  555. ``unique_id_uses_count`` value declared by the very first
  556. ``register`` call for that ``unique_id``.
  557. """
  558. self._events.register(event_name, handler, unique_id,
  559. unique_id_uses_count=unique_id_uses_count)
  560. def unregister(self, event_name, handler=None, unique_id=None,
  561. unique_id_uses_count=False):
  562. """Unregister a handler with an event.
  563. :type event_name: str
  564. :param event_name: The name of the event.
  565. :type handler: callable
  566. :param handler: The callback to unregister.
  567. :type unique_id: str
  568. :param unique_id: A unique identifier identifying the callback
  569. to unregister. You can provide either the handler or the
  570. unique_id, you do not have to provide both.
  571. :param unique_id_uses_count: boolean
  572. :param unique_id_uses_count: Specifies if the event should maintain
  573. a count when a ``unique_id`` is registered and unregisted. The
  574. event can only be completely unregistered once every ``register``
  575. call using the ``unique_id`` has been matched by an ``unregister``
  576. call. If the ``unique_id`` is specified, subsequent
  577. ``unregister`` calls must use the same value for
  578. ``unique_id_uses_count`` as the ``register`` call that first
  579. registered the event.
  580. :raises ValueError: If the call to ``unregister`` uses ``unique_id``
  581. but the value for ``unique_id_uses_count`` differs from the
  582. ``unique_id_uses_count`` value declared by the very first
  583. ``register`` call for that ``unique_id``.
  584. """
  585. self._events.unregister(event_name, handler=handler,
  586. unique_id=unique_id,
  587. unique_id_uses_count=unique_id_uses_count)
  588. def emit(self, event_name, **kwargs):
  589. return self._events.emit(event_name, **kwargs)
  590. def emit_first_non_none_response(self, event_name, **kwargs):
  591. responses = self._events.emit(event_name, **kwargs)
  592. return first_non_none_response(responses)
  593. def get_component(self, name):
  594. return self._components.get_component(name)
  595. def register_component(self, name, component):
  596. self._components.register_component(name, component)
  597. def lazy_register_component(self, name, component):
  598. self._components.lazy_register_component(name, component)
  599. def create_client(self, service_name, region_name=None, api_version=None,
  600. use_ssl=True, verify=None, endpoint_url=None,
  601. ks_access_key_id=None, ks_secret_access_key=None,
  602. ks_session_token=None, config=None, customer_path=None):
  603. """Create a kscore client.
  604. :type service_name: string
  605. :param service_name: The name of the service for which a client will
  606. be created. You can use the ``Sesssion.get_available_services()``
  607. method to get a list of all available service names.
  608. :type region_name: string
  609. :param region_name: The name of the region associated with the client.
  610. A client is associated with a single region.
  611. :type api_version: string
  612. :param api_version: The API version to use. By default, kscore will
  613. use the latest API version when creating a client. You only need
  614. to specify this parameter if you want to use a previous API version
  615. of the client.
  616. :type use_ssl: boolean
  617. :param use_ssl: Whether or not to use SSL. By default, SSL is used.
  618. Note that not all services support non-ssl connections.
  619. :type verify: boolean/string
  620. :param verify: Whether or not to verify SSL certificates.
  621. By default SSL certificates are verified. You can provide the
  622. following values:
  623. * False - do not validate SSL certificates. SSL will still be
  624. used (unless use_ssl is False), but SSL certificates
  625. will not be verified.
  626. * path/to/cert/bundle.pem - A filename of the CA cert bundle to
  627. uses. You can specify this argument if you want to use a
  628. different CA cert bundle than the one used by kscore.
  629. :type endpoint_url: string
  630. :param endpoint_url: The complete URL to use for the constructed
  631. client. Normally, kscore will automatically construct the
  632. appropriate URL to use when communicating with a service. You can
  633. specify a complete URL (including the "http/https" scheme) to
  634. override this behavior. If this value is provided, then
  635. ``use_ssl`` is ignored.
  636. :type ks_access_key_id: string
  637. :param ks_access_key_id: The access key to use when creating
  638. the client. This is entirely optional, and if not provided,
  639. the credentials configured for the session will automatically
  640. be used. You only need to provide this argument if you want
  641. to override the credentials used for this specific client.
  642. :type ks_secret_access_key: string
  643. :param ks_secret_access_key: The secret key to use when creating
  644. the client. Same semantics as ks_access_key_id above.
  645. :type ks_session_token: string
  646. :param ks_session_token: The session token to use when creating
  647. the client. Same semantics as ks_access_key_id above.
  648. :type config: kscore.client.Config
  649. :param config: Advanced client configuration options. If a value
  650. is specified in the client config, its value will take precedence
  651. over environment variables and configuration values, but not over
  652. a value passed explicitly to the method. If a default config
  653. object is set on the session, the config object used when creating
  654. the client will be the result of calling ``merge()`` on the
  655. default config with the config provided to this call.
  656. :rtype: kscore.client.BaseClient
  657. :return: A kscore client instance
  658. """
  659. default_client_config = self.get_default_client_config()
  660. # If a config is provided and a default config is set, then
  661. # use the config resulting from merging the two.
  662. if config is not None and default_client_config is not None:
  663. config = default_client_config.merge(config)
  664. # If a config was not provided then use the default
  665. # client config from the session
  666. elif default_client_config is not None:
  667. config = default_client_config
  668. # Figure out the user-provided region based on the various
  669. # configuration options.
  670. if region_name is None:
  671. if config and config.region_name is not None:
  672. region_name = config.region_name
  673. else:
  674. region_name = self.get_config_variable('region')
  675. # Figure out the verify value base on the various
  676. # configuration options.
  677. if verify is None:
  678. verify = self.get_config_variable('ca_bundle')
  679. if api_version is None:
  680. api_version = self.get_config_variable('api_versions').get(
  681. service_name, None)
  682. loader = self.get_component('data_loader')
  683. event_emitter = self.get_component('event_emitter')
  684. response_parser_factory = self.get_component(
  685. 'response_parser_factory')
  686. if ks_secret_access_key is not None:
  687. credentials = kscore.credentials.Credentials(
  688. access_key=ks_access_key_id,
  689. secret_key=ks_secret_access_key,
  690. token=ks_session_token)
  691. else:
  692. credentials = self.get_credentials()
  693. if endpoint_url is None:
  694. domain = self.get_domain()
  695. if domain:
  696. if use_ssl:
  697. endpoint_url = 'https://' + service_name + '.' + domain.ks_domain
  698. else:
  699. endpoint_url = 'http://' + service_name + '.' + domain.ks_domain
  700. endpoint_resolver = self.get_component('endpoint_resolver')
  701. client_creator = kscore.client.ClientCreator(
  702. loader, endpoint_resolver, self.user_agent(), event_emitter,
  703. retryhandler, translate, response_parser_factory)
  704. client = client_creator.create_client(
  705. service_name=service_name, region_name=region_name,
  706. is_secure=use_ssl, endpoint_url=endpoint_url, verify=verify,
  707. credentials=credentials, scoped_config=self.get_scoped_config(),
  708. client_config=config, api_version=api_version, customer_path=customer_path)
  709. return client
  710. def get_available_partitions(self):
  711. """Lists the available partitions found on disk
  712. :rtype: list
  713. :return: Returns a list of partition names (e.g., ["aws", "aws-cn"])
  714. """
  715. resolver = self.get_component('endpoint_resolver')
  716. return resolver.get_available_partitions()
  717. def get_available_regions(self, service_name, partition_name='aws',
  718. allow_non_regional=False):
  719. """Lists the region and endpoint names of a particular partition.
  720. :type service_name: string
  721. :param service_name: Name of a service to list endpoint for (e.g., s3).
  722. This parameter accepts a service name (e.g., "elb") or endpoint
  723. prefix (e.g., "elasticloadbalancing").
  724. :type partition_name: string
  725. :param partition_name: Name of the partition to limit endpoints to.
  726. (e.g., KSYUN for the public KSYUN endpoints, aws-cn for KSYUN China
  727. endpoints, aws-us-gov for KSYUN GovCloud (US) Endpoints, etc.
  728. :type allow_non_regional: bool
  729. :param allow_non_regional: Set to True to include endpoints that are
  730. not regional endpoints (e.g., s3-external-1,
  731. fips-us-gov-west-1, etc).
  732. :return: Returns a list of endpoint names (e.g., ["us-east-1"]).
  733. """
  734. resolver = self.get_component('endpoint_resolver')
  735. results = []
  736. try:
  737. service_data = self.get_service_data(service_name)
  738. endpoint_prefix = service_data['metadata'].get(
  739. 'endpointPrefix', service_name)
  740. results = resolver.get_available_endpoints(
  741. endpoint_prefix, partition_name, allow_non_regional)
  742. except UnknownServiceError:
  743. pass
  744. return results
  745. class ComponentLocator(object):
  746. """Service locator for session components."""
  747. def __init__(self):
  748. self._components = {}
  749. self._deferred = {}
  750. def get_component(self, name):
  751. if name in self._deferred:
  752. factory = self._deferred[name]
  753. self._components[name] = factory()
  754. # Only delete the component from the deferred dict after
  755. # successfully creating the object from the factory as well as
  756. # injecting the instantiated value into the _components dict.
  757. del self._deferred[name]
  758. try:
  759. return self._components[name]
  760. except KeyError:
  761. raise ValueError("Unknown component: %s" % name)
  762. def register_component(self, name, component):
  763. self._components[name] = component
  764. try:
  765. del self._deferred[name]
  766. except KeyError:
  767. pass
  768. def lazy_register_component(self, name, no_arg_factory):
  769. self._deferred[name] = no_arg_factory
  770. try:
  771. del self._components[name]
  772. except KeyError:
  773. pass
  774. def get_session(env_vars=None):
  775. """
  776. Return a new session object.
  777. """
  778. return Session(env_vars)