123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775 |
- # Copyright 2014 ksyun.com, Inc. or its affiliates. All Rights Reserved.
- #
- # Licensed under the Apache License, Version 2.0 (the "License"). You
- # may not use this file except in compliance with the License. A copy of
- # the License is located at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # or in the "license" file accompanying this file. This file is
- # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
- # ANY KIND, either express or implied. See the License for the specific
- # language governing permissions and limitations under the License.
- import collections
- import copy
- import logging
- import kscore.serialize
- import kscore.validate
- from kscore import waiter, xform_name
- from kscore.auth import AUTH_TYPE_MAPS
- from kscore.ksrequest import prepare_request_dict
- from kscore.config import Config
- from kscore.docs.docstring import ClientMethodDocstring
- from kscore.docs.docstring import PaginatorDocstring
- from kscore.endpoint import EndpointCreator
- from kscore.exceptions import ClientError, DataNotFoundError
- from kscore.exceptions import OperationNotPageableError
- from kscore.exceptions import UnknownSignatureVersionError
- from kscore.hooks import first_non_none_response
- from kscore.model import ServiceModel
- from kscore.paginate import Paginator
- from kscore.signers import RequestSigner
- from kscore.utils import CachedProperty
- from kscore.utils import fix_s3_host
- from kscore.utils import get_service_module_name
- from kscore.utils import switch_to_virtual_host_style
- from kscore.utils import switch_host_s3_accelerate
- from kscore.utils import S3_ACCELERATE_ENDPOINT
- logger = logging.getLogger(__name__)
- class ClientCreator(object):
- """Creates client objects for a service."""
- def __init__(self, loader, endpoint_resolver, user_agent, event_emitter,
- retry_handler_factory, retry_config_translator,
- response_parser_factory=None):
- self._loader = loader
- self._endpoint_resolver = endpoint_resolver
- self._user_agent = user_agent
- self._event_emitter = event_emitter
- self._retry_handler_factory = retry_handler_factory
- self._retry_config_translator = retry_config_translator
- self._response_parser_factory = response_parser_factory
- def create_client(self, service_name, region_name, is_secure=True,
- endpoint_url=None, verify=None,
- credentials=None, scoped_config=None,
- api_version=None,
- client_config=None,
- customer_path=None):
- service_model = self._load_service_model(service_name, api_version)
- customer_model = self._load_customer_model(customer_path, service_name, api_version)
- cls = self._create_client_class(service_name, service_model)
- client_args = self._get_client_args(customer_model, service_model, region_name, is_secure, endpoint_url,
- verify, credentials, scoped_config, client_config)
- return cls(**client_args)
- def create_client_class(self, service_name, api_version=None):
- service_model = self._load_service_model(service_name, api_version)
- return self._create_client_class(service_name, service_model)
- def _create_client_class(self, service_name, service_model):
- class_attributes = self._create_methods(service_model)
- py_name_to_operation_name = self._create_name_mapping(service_model)
- class_attributes['_PY_TO_OP_NAME'] = py_name_to_operation_name
- bases = [BaseClient]
- self._event_emitter.emit('creating-client-class.%s' % service_name,
- class_attributes=class_attributes,
- base_classes=bases)
- class_name = get_service_module_name(service_model)
- cls = type(str(class_name), tuple(bases), class_attributes)
- return cls
- def _load_customer_model(self, path, service_name, api_version=None):
- if path:
- json_model = self._loader.load_customer_model(path, service_name, 'service-2', 'customer',
- api_version=api_version)
- else:
- json_model = collections.OrderedDict()
- return json_model
- def _load_service_model(self, service_name, api_version=None):
- json_model = self._loader.load_service_model(service_name, 'service-2',
- api_version=api_version)
- service_model = ServiceModel(json_model, service_name=service_name)
- self._register_retries(service_model)
- return service_model
- def _register_retries(self, service_model):
- endpoint_prefix = service_model.endpoint_prefix
- # First, we load the entire retry config for all services,
- # then pull out just the information we need.
- original_config = self._loader.load_data('_retry')
- if not original_config:
- return
- retry_config = self._retry_config_translator.build_retry_config(
- endpoint_prefix, original_config.get('retry', {}),
- original_config.get('definitions', {}))
- logger.debug("Registering retry handlers for service: %s",
- service_model.service_name)
- handler = self._retry_handler_factory.create_retry_handler(
- retry_config, endpoint_prefix)
- unique_id = 'retry-config-%s' % endpoint_prefix
- self._event_emitter.register('needs-retry.%s' % endpoint_prefix,
- handler, unique_id=unique_id)
- def _inject_s3_configuration(self, config_kwargs, scoped_config,
- client_config):
- s3_configuration = None
- # Check the scoped config first.
- if scoped_config is not None:
- s3_configuration = scoped_config.get('s3')
- # Until we have proper validation of the config file (including
- # nested types), we have to account for the fact that the s3
- # key could be parsed as a string, e.g 's3 = foo'.
- # In the case we'll ignore the key for now.
- if not isinstance(s3_configuration, dict):
- logger.debug("The s3 config key is not a dictionary type, "
- "ignoring its value of: %s", s3_configuration)
- s3_configuration = None
- # Convert logic for s3 accelerate options in the scoped config
- # so that the various strings map to the appropriate boolean value.
- if s3_configuration and \
- 'use_accelerate_endpoint' in s3_configuration:
- # Make sure any further modifications to the s3 section will
- # not affect the scoped config by making a copy of it.
- s3_configuration = s3_configuration.copy()
- # Normalize on different possible values of True
- if s3_configuration['use_accelerate_endpoint'] in [
- True, 'True', 'true']:
- s3_configuration['use_accelerate_endpoint'] = True
- else:
- s3_configuration['use_accelerate_endpoint'] = False
- # Next specfic client config values takes precedence over
- # specific values in the scoped config.
- if client_config is not None:
- if client_config.s3 is not None:
- if s3_configuration is None:
- s3_configuration = client_config.s3
- else:
- # The current s3_configuration dictionary may be
- # from a source that only should be read from so
- # we want to be safe and just make a copy of it to modify
- # before it actually gets updated.
- s3_configuration = s3_configuration.copy()
- s3_configuration.update(client_config.s3)
- config_kwargs['s3'] = s3_configuration
- def _get_client_args(self, customer_model, service_model, region_name, is_secure,
- endpoint_url, verify, credentials,
- scoped_config, client_config):
- service_name = service_model.endpoint_prefix
- protocol = service_model.metadata['protocol']
- parameter_validation = True
- if client_config:
- parameter_validation = client_config.parameter_validation
- serializer = kscore.serialize.create_serializer(
- protocol, parameter_validation)
- event_emitter = copy.copy(self._event_emitter)
- response_parser = kscore.parsers.create_parser(protocol)
- endpoint_bridge = ClientEndpointBridge(
- self._endpoint_resolver, scoped_config, client_config,
- service_signing_name=service_model.metadata.get('signingName'))
- endpoint_config = endpoint_bridge.resolve(
- service_name, region_name, endpoint_url, is_secure)
- # Override the user agent if specified in the client config.
- user_agent = self._user_agent
- if client_config is not None:
- if client_config.user_agent is not None:
- user_agent = client_config.user_agent
- if client_config.user_agent_extra is not None:
- user_agent += ' %s' % client_config.user_agent_extra
- signer = RequestSigner(
- service_name, endpoint_config['signing_region'],
- endpoint_config['signing_name'],
- endpoint_config['signature_version'],
- credentials, event_emitter)
- # Create a new client config to be passed to the client based
- # on the final values. We do not want the user to be able
- # to try to modify an existing client with a client config.
- config_kwargs = dict(
- region_name=endpoint_config['region_name'],
- signature_version=endpoint_config['signature_version'],
- user_agent=user_agent)
- if client_config is not None:
- config_kwargs.update(
- connect_timeout=client_config.connect_timeout,
- read_timeout=client_config.read_timeout)
- # Add any additional s3 configuration for client
- self._inject_s3_configuration(
- config_kwargs, scoped_config, client_config)
- new_config = Config(**config_kwargs)
- endpoint_creator = EndpointCreator(event_emitter)
- endpoint = endpoint_creator.create_endpoint(
- service_model, region_name=endpoint_config['region_name'],
- endpoint_url=endpoint_config['endpoint_url'], verify=verify,
- response_parser_factory=self._response_parser_factory,
- timeout=(new_config.connect_timeout, new_config.read_timeout))
- return {
- 'serializer': serializer,
- 'endpoint': endpoint,
- 'response_parser': response_parser,
- 'event_emitter': event_emitter,
- 'request_signer': signer,
- 'service_model': service_model,
- 'loader': self._loader,
- 'client_config': new_config,
- 'customer_model': customer_model
- }
- def _create_methods(self, service_model):
- op_dict = {}
- for operation_name in service_model.operation_names:
- py_operation_name = xform_name(operation_name)
- op_dict[py_operation_name] = self._create_api_method(
- py_operation_name, operation_name, service_model)
- return op_dict
- def _create_name_mapping(self, service_model):
- # py_name -> OperationName, for every operation available
- # for a service.
- mapping = {}
- for operation_name in service_model.operation_names:
- py_operation_name = xform_name(operation_name)
- mapping[py_operation_name] = operation_name
- return mapping
- def _create_api_method(self, py_operation_name, operation_name,
- service_model):
- def _api_call(self, *args, **kwargs):
- # We're accepting *args so that we can give a more helpful
- # error message than TypeError: _api_call takes exactly
- # 1 argument.
- if args:
- raise TypeError(
- "%s() only accepts keyword arguments." % py_operation_name)
- # The "self" in this scope is referring to the BaseClient.
- return self._make_api_call(operation_name, kwargs)
- _api_call.__name__ = str(py_operation_name)
- # Add the docstring to the client method
- operation_model = service_model.operation_model(operation_name)
- docstring = ClientMethodDocstring(
- operation_model=operation_model,
- method_name=operation_name,
- event_emitter=self._event_emitter,
- method_description=operation_model.documentation,
- example_prefix='response = client.%s' % py_operation_name,
- include_signature=False
- )
- _api_call.__doc__ = docstring
- return _api_call
- class ClientEndpointBridge(object):
- """Bridges endpoint data and client creation"""
- DEFAULT_ENDPOINT = '{service}.{region}.api.ksyun.com'
- def __init__(self, endpoint_resolver, scoped_config=None,
- client_config=None, default_endpoint=None,
- service_signing_name=None):
- self.service_signing_name = service_signing_name
- self.endpoint_resolver = endpoint_resolver
- self.scoped_config = scoped_config
- self.client_config = client_config
- self.default_endpoint = default_endpoint or self.DEFAULT_ENDPOINT
- def resolve(self, service_name, region_name=None, endpoint_url=None,
- is_secure=True):
- region_name = self._check_default_region(service_name, region_name)
- resolved = self.endpoint_resolver.construct_endpoint(
- service_name, region_name)
- if resolved:
- return self._create_endpoint(
- resolved, service_name, region_name, endpoint_url, is_secure)
- else:
- return self._assume_endpoint(service_name, region_name,
- endpoint_url, is_secure)
- def _check_default_region(self, service_name, region_name):
- if region_name is not None:
- return region_name
- # Use the client_config region if no explicit region was provided.
- if self.client_config and self.client_config.region_name is not None:
- return self.client_config.region_name
- def _create_endpoint(self, resolved, service_name, region_name,
- endpoint_url, is_secure):
- region_name, signing_region = self._pick_region_values(
- resolved, region_name, endpoint_url)
- if endpoint_url is None:
- # Use the sslCommonName over the hostname for Python 2.6 compat.
- hostname = resolved.get('sslCommonName', resolved.get('hostname'))
- endpoint_url = self._make_url(hostname, is_secure,
- resolved.get('protocols', []))
- signature_version = self._resolve_signature_version(
- service_name, resolved)
- signing_name = self._resolve_signing_name(service_name, resolved)
- return self._create_result(
- service_name=service_name, region_name=region_name,
- signing_region=signing_region, signing_name=signing_name,
- endpoint_url=endpoint_url, metadata=resolved,
- signature_version=signature_version)
- def _assume_endpoint(self, service_name, region_name, endpoint_url,
- is_secure):
- if endpoint_url is None:
- # Expand the default hostname URI template.
- hostname = self.default_endpoint.format(
- service=service_name, region=region_name)
- endpoint_url = self._make_url(hostname, is_secure,
- ['http', 'https'])
- logger.debug('Assuming an endpoint for %s, %s: %s',
- service_name, region_name, endpoint_url)
- # We still want to allow the user to provide an explicit version.
- signature_version = self._resolve_signature_version(
- service_name, {'signatureVersions': ['v4']})
- signing_name = self._resolve_signing_name(service_name, resolved={})
- return self._create_result(
- service_name=service_name, region_name=region_name,
- signing_region=region_name, signing_name=signing_name,
- signature_version=signature_version, endpoint_url=endpoint_url,
- metadata={})
- def _create_result(self, service_name, region_name, signing_region,
- signing_name, endpoint_url, signature_version,
- metadata):
- return {
- 'service_name': service_name,
- 'region_name': region_name,
- 'signing_region': signing_region,
- 'signing_name': signing_name,
- 'endpoint_url': endpoint_url,
- 'signature_version': signature_version,
- 'metadata': metadata
- }
- def _make_url(self, hostname, is_secure, supported_protocols):
- if is_secure and 'https' in supported_protocols:
- scheme = 'https'
- else:
- scheme = 'http'
- return '%s://%s' % (scheme, hostname)
- def _resolve_signing_name(self, service_name, resolved):
- # CredentialScope overrides everything else.
- if 'credentialScope' in resolved \
- and 'service' in resolved['credentialScope']:
- return resolved['credentialScope']['service']
- # Use the signingName from the model if present.
- if self.service_signing_name:
- return self.service_signing_name
- # Just assume is the same as the service name.
- return service_name
- def _pick_region_values(self, resolved, region_name, endpoint_url):
- signing_region = region_name
- if endpoint_url is None:
- # Do not use the region name or signing name from the resolved
- # endpoint if the user explicitly provides an endpoint_url. This
- # would happen if we resolve to an endpoint where the service has
- # a "defaults" section that overrides all endpoint with a single
- # hostname and credentialScope. This has been the case historically
- # for how STS has worked. The only way to resolve an STS endpoint
- # was to provide a region_name and an endpoint_url. In that case,
- # we would still resolve an endpoint, but we would not use the
- # resolved endpointName or signingRegion because we want to allow
- # custom endpoints.
- region_name = resolved['endpointName']
- signing_region = region_name
- if 'credentialScope' in resolved \
- and 'region' in resolved['credentialScope']:
- signing_region = resolved['credentialScope']['region']
- return region_name, signing_region
- def _resolve_signature_version(self, service_name, resolved):
- # Client config overrides everything.
- client = self.client_config
- if client and client.signature_version is not None:
- return client.signature_version
- # Scoped config overrides picking from the endpoint metadata.
- scoped = self.scoped_config
- if scoped is not None:
- service_config = scoped.get(service_name)
- if service_config is not None and isinstance(service_config, dict):
- version = service_config.get('signature_version')
- if version:
- logger.debug(
- "Switching signature version for service %s "
- "to version %s based on config file override.",
- service_name, version)
- return version
- # Pick a signature version from the endpoint metadata if present.
- if 'signatureVersions' in resolved:
- potential_versions = resolved['signatureVersions']
- if service_name == 's3':
- # We currently prefer s3 over s3v4.
- if 's3' in potential_versions:
- return 's3'
- elif 's3v4' in potential_versions:
- return 's3v4'
- if 'v4' in potential_versions:
- return 'v4'
- # Now just iterate over the signature versions in order until we
- # find the first one that is known to KSCore.
- for known in AUTH_TYPE_MAPS:
- if known in potential_versions:
- return known
- raise UnknownSignatureVersionError(
- signature_version=resolved.get('signatureVersions'))
- class BaseClient(object):
- # This is actually reassigned with the py->op_name mapping
- # when the client creator creates the subclass. This value is used
- # because calls such as client.get_paginator('list_objects') use the
- # snake_case name, but we need to know the ListObjects form.
- # xform_name() does the ListObjects->list_objects conversion, but
- # we need the reverse mapping here.
- _PY_TO_OP_NAME = {}
- def __init__(self, serializer, endpoint, response_parser,
- event_emitter, request_signer, service_model, loader,
- client_config, customer_model):
- self._serializer = serializer
- self._endpoint = endpoint
- self._response_parser = response_parser
- self._request_signer = request_signer
- self._cache = {}
- self._loader = loader
- self._client_config = client_config
- self.meta = ClientMeta(event_emitter, self._client_config,
- endpoint.host, service_model,
- self._PY_TO_OP_NAME, customer_model)
- self._register_handlers()
- def _register_handlers(self):
- # Register the handler required to sign requests.
- self.meta.events.register('request-created.%s' %
- self.meta.service_model.endpoint_prefix,
- self._request_signer.handler)
- self._register_s3_specific_handlers()
- def _register_s3_specific_handlers(self):
- # Register all of the s3 specific handlers
- if self.meta.config.s3 is None:
- s3_addressing_style = None
- s3_accelerate = None
- else:
- s3_addressing_style = self.meta.config.s3.get('addressing_style')
- s3_accelerate = self.meta.config.s3.get('use_accelerate_endpoint')
- # Enable accelerate if the configuration is set to to true or the
- # endpoint being used matches one of the Accelerate endpoints.
- if s3_accelerate or S3_ACCELERATE_ENDPOINT in self._endpoint.host:
- self._force_virtual_style_s3_addressing()
- self.meta.events.register_first(
- 'request-created.s3', switch_host_s3_accelerate)
- elif s3_addressing_style:
- # Otherwise go ahead with the style the user may have specified.
- if s3_addressing_style == 'path':
- self._force_path_style_s3_addressing()
- elif s3_addressing_style == 'virtual':
- self._force_virtual_style_s3_addressing()
- def _force_path_style_s3_addressing(self):
- # Do not try to modify the host if path is specified. The
- # ``fix_s3_host`` usually switches the addresing style to virtual.
- self.meta.events.unregister('before-sign.s3', fix_s3_host)
- def _force_virtual_style_s3_addressing(self):
- # If the virtual host addressing style is being forced,
- # switch the default fix_s3_host handler for the more general
- # switch_to_virtual_host_style handler that does not have opt out
- # cases (other than throwing an error if the name is DNS incompatible)
- self.meta.events.unregister('before-sign.s3', fix_s3_host)
- self.meta.events.register(
- 'before-sign.s3', switch_to_virtual_host_style)
- @property
- def _service_model(self):
- return self.meta.service_model
- def _make_api_call(self, operation_name, api_params):
- request_context = {}
- operation_model = self._service_model.operation_model(operation_name)
- request_dict = self._convert_to_request_dict(
- api_params, operation_model, context=request_context)
- handler, event_response = self.meta.events.emit_until_response(
- 'before-call.{endpoint_prefix}.{operation_name}'.format(
- endpoint_prefix=self._service_model.endpoint_prefix,
- operation_name=operation_name),
- model=operation_model, params=request_dict,
- request_signer=self._request_signer, context=request_context)
- if event_response is not None:
- http, parsed_response = event_response
- else:
- http, parsed_response = self._endpoint.make_request(
- operation_model, request_dict)
- self.meta.events.emit(
- 'after-call.{endpoint_prefix}.{operation_name}'.format(
- endpoint_prefix=self._service_model.endpoint_prefix,
- operation_name=operation_name),
- http_response=http, parsed=parsed_response,
- model=operation_model, context=request_context
- )
- # result mapping to customer
- self._mapping_to_customer_resp(parsed_response, operation_name)
- if (operation_name == 'ListMetrics' or
- operation_name == 'GetMetricStatistics' or
- operation_name == 'GetMetricStatisticsBatch' or
- operation_name == 'GetMetricStatisticsBatchV2' or
- operation_name == 'ListMetricsV3' or
- operation_name == 'GetMetricStatisticsV3'):
- return parsed_response
- if http.status_code >= 300:
- raise ClientError(parsed_response, operation_name)
- else:
- return parsed_response
- def _mapping_to_customer_resp(self, parsed_response, operation_name):
- if parsed_response and type(parsed_response) == dict and self.meta.customer_model.__contains__(operation_name):
- self._mapping_recursion(parsed_response, self.meta.customer_model.get(operation_name))
- def _mapping_recursion(self, parsed_response, _dict):
- _del = []
- if type(parsed_response) == dict:
- _p = dict(parsed_response)
- for _item in _p.items():
- if _dict.__contains__(_item[0]):
- parsed_response[_dict.get(_item[0])] = _item[1]
- _del.append(_item[0])
- if type(_item[1]) == list or type(_item[1]) == dict:
- self._mapping_recursion(_item[1], _dict)
- for _d in _del:
- del parsed_response[_d]
- else:
- for _item in list(parsed_response):
- if type(_item) == dict:
- self._mapping_recursion(_item, _dict)
- def _convert_to_request_dict(self, api_params, operation_model,
- context=None):
- # Given the API params provided by the user and the operation_model
- # we can serialize the request to a request_dict.
- operation_name = operation_model.name
- # Emit an event that allows users to modify the parameters at the
- # beginning of the method. It allows handlers to modify existing
- # parameters or return a new set of parameters to use.
- responses = self.meta.events.emit(
- 'provide-client-params.{endpoint_prefix}.{operation_name}'.format(
- endpoint_prefix=self._service_model.endpoint_prefix,
- operation_name=operation_name),
- params=api_params, model=operation_model, context=context)
- api_params = first_non_none_response(responses, default=api_params)
- event_name = (
- 'before-parameter-build.{endpoint_prefix}.{operation_name}')
- self.meta.events.emit(
- event_name.format(
- endpoint_prefix=self._service_model.endpoint_prefix,
- operation_name=operation_name),
- params=api_params, model=operation_model, context=context)
- serializer = self._serializer
- if operation_model.is_rewrite_protocol:
- serializer = kscore.serialize.create_serializer(operation_model.protocol, True)
- request_dict = serializer.serialize_to_request(api_params, operation_model)
- prepare_request_dict(request_dict, endpoint_url=self._endpoint.host,
- user_agent=self._client_config.user_agent)
- return request_dict
- def get_paginator(self, operation_name):
- """Create a paginator for an operation.
- :type operation_name: string
- :param operation_name: The operation name. This is the same name
- as the method name on the client. For example, if the
- method name is ``create_foo``, and you'd normally invoke the
- operation as ``client.create_foo(**kwargs)``, if the
- ``create_foo`` operation can be paginated, you can use the
- call ``client.get_paginator("create_foo")``.
- :raise OperationNotPageableError: Raised if the operation is not
- pageable. You can use the ``client.can_paginate`` method to
- check if an operation is pageable.
- :rtype: L{kscore.paginate.Paginator}
- :return: A paginator object.
- """
- if not self.can_paginate(operation_name):
- raise OperationNotPageableError(operation_name=operation_name)
- else:
- actual_operation_name = self._PY_TO_OP_NAME[operation_name]
- # Create a new paginate method that will serve as a proxy to
- # the underlying Paginator.paginate method. This is needed to
- # attach a docstring to the method.
- def paginate(self, **kwargs):
- return Paginator.paginate(self, **kwargs)
- paginator_config = self._cache['page_config'][
- actual_operation_name]
- # Add the docstring for the paginate method.
- paginate.__doc__ = PaginatorDocstring(
- paginator_name=actual_operation_name,
- event_emitter=self.meta.events,
- service_model=self.meta.service_model,
- paginator_config=paginator_config,
- include_signature=False
- )
- # Rename the paginator class based on the type of paginator.
- paginator_class_name = str('%s.Paginator.%s' % (
- get_service_module_name(self.meta.service_model),
- actual_operation_name))
- # Create the new paginator class
- documented_paginator_cls = type(
- paginator_class_name, (Paginator,), {'paginate': paginate})
- paginator = documented_paginator_cls(
- getattr(self, operation_name),
- paginator_config)
- return paginator
- def can_paginate(self, operation_name):
- """Check if an operation can be paginated.
- :type operation_name: string
- :param operation_name: The operation name. This is the same name
- as the method name on the client. For example, if the
- method name is ``create_foo``, and you'd normally invoke the
- operation as ``client.create_foo(**kwargs)``, if the
- ``create_foo`` operation can be paginated, you can use the
- call ``client.get_paginator("create_foo")``.
- :return: ``True`` if the operation can be paginated,
- ``False`` otherwise.
- """
- if 'page_config' not in self._cache:
- try:
- page_config = self._loader.load_service_model(
- self._service_model.service_name,
- 'paginators-1',
- self._service_model.api_version)['pagination']
- self._cache['page_config'] = page_config
- except DataNotFoundError:
- self._cache['page_config'] = {}
- actual_operation_name = self._PY_TO_OP_NAME[operation_name]
- return actual_operation_name in self._cache['page_config']
- def _get_waiter_config(self):
- if 'waiter_config' not in self._cache:
- try:
- waiter_config = self._loader.load_service_model(
- self._service_model.service_name,
- 'waiters-2',
- self._service_model.api_version)
- self._cache['waiter_config'] = waiter_config
- except DataNotFoundError:
- self._cache['waiter_config'] = {}
- return self._cache['waiter_config']
- def get_waiter(self, waiter_name):
- config = self._get_waiter_config()
- if not config:
- raise ValueError("Waiter does not exist: %s" % waiter_name)
- model = waiter.WaiterModel(config)
- mapping = {}
- for name in model.waiter_names:
- mapping[xform_name(name)] = name
- if waiter_name not in mapping:
- raise ValueError("Waiter does not exist: %s" % waiter_name)
- return waiter.create_waiter_with_client(
- mapping[waiter_name], model, self)
- @CachedProperty
- def waiter_names(self):
- """Returns a list of all available waiters."""
- config = self._get_waiter_config()
- if not config:
- return []
- model = waiter.WaiterModel(config)
- # Waiter configs is a dict, we just want the waiter names
- # which are the keys in the dict.
- return [xform_name(name) for name in model.waiter_names]
- class ClientMeta(object):
- """Holds additional client methods.
- This class holds additional information for clients. It exists for
- two reasons:
- * To give advanced functionality to clients
- * To namespace additional client attributes from the operation
- names which are mapped to methods at runtime. This avoids
- ever running into collisions with operation names.
- """
- def __init__(self, events, client_config, endpoint_url, service_model,
- method_to_api_mapping, customer_model):
- self.events = events
- self._client_config = client_config
- self._endpoint_url = endpoint_url
- self._service_model = service_model
- self._method_to_api_mapping = method_to_api_mapping
- self._customer_model = customer_model
- @property
- def service_model(self):
- return self._service_model
- @property
- def region_name(self):
- return self._client_config.region_name
- @property
- def endpoint_url(self):
- return self._endpoint_url
- @property
- def customer_model(self):
- return self._customer_model
- @property
- def config(self):
- return self._client_config
- @property
- def method_to_api_mapping(self):
- return self._method_to_api_mapping
|