huaweicloud-sdk-python-v3/huaweicloud-sdk-core/huaweicloudsdkcore/client.py

431 lines
17 KiB
Python

# coding: utf-8
"""
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache LICENSE, Version 2.0 (the
"LICENSE"); you may not use this file except in compliance
with the LICENSE. You may obtain a copy of the LICENSE at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the LICENSE 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 datetime
import importlib
import json
import logging
import re
import sys
from concurrent.futures.thread import ThreadPoolExecutor
from logging.handlers import RotatingFileHandler
from typing import Mapping
import six
from six.moves.urllib.parse import quote, urlparse
from huaweicloudsdkcore.auth.credentials import BasicCredentials, get_credential_from_environment
from huaweicloudsdkcore.http.http_client import HttpClient
from huaweicloudsdkcore.http.http_config import HttpConfig
from huaweicloudsdkcore.http.http_handler import HttpHandler
from huaweicloudsdkcore.http.primitive_types import native_types_mapping
from huaweicloudsdkcore.http.primitive_types import primitive_types
from huaweicloudsdkcore.sdk_request import SdkRequest
from huaweicloudsdkcore.sdk_response import FutureSdkResponse
from huaweicloudsdkcore.sdk_stream_response import SdkStreamResponse
from huaweicloudsdkcore.utils import http_utils, core_utils
class ClientBuilder:
def __init__(self, client_type, credential_type=BasicCredentials.__name__):
self._client_type = client_type
self._credential_type = credential_type.split(',')
self._config = None
self._credentials = None
self._endpoint = None
self._http_handler = None
self._file_logger_handler = None
self._stream_logger_handler = None
def with_http_config(self, config: HttpConfig):
self._config = config
return self
def with_credentials(self, credentials):
self._credentials = credentials
return self
def with_endpoint(self, endpoint):
self._endpoint = endpoint
return self
def with_http_handler(self, http_handler):
self._http_handler = http_handler
return self
def with_file_log(self, path, log_level=logging.INFO, max_bytes=10485760, backup_count=5, format_string=None):
self._file_logger_handler = {
"path": path,
"log_level": log_level,
"max_bytes": max_bytes,
"backup_count": backup_count,
"format_string": format_string
}
return self
def with_stream_log(self, stream=sys.stdout, log_level=logging.INFO, format_string=None):
self._stream_logger_handler = {
"stream": stream,
"log_level": log_level,
"format_string": format_string
}
return self
def build(self):
if self._credentials is None:
self._credentials = get_credential_from_environment(self._client_type, self._credential_type[0])
if self._credentials.__class__.__name__ not in self._credential_type:
raise TypeError("credential type error, support credential type is %s" % ",".join(self._credential_type))
client = self._client_type() \
.with_endpoint(self._endpoint) \
.with_credentials(self._credentials) \
.with_config(self._config) \
.with_http_handler(self._http_handler)
if self._file_logger_handler is not None:
client.add_file_logger(**self._file_logger_handler)
if self._stream_logger_handler is not None:
client.add_stream_logger(**self._stream_logger_handler)
client.init_http_client()
return client
class Client:
def __init__(self):
self.preset_headers = {}
self._agent = {"User-Agent": "huaweicloud-usdk-python/3.0"}
self._logger = self._init_logger()
self._credentials = None
self._config = None
self._endpoint = None
self._http_client = None
self._http_handler = None
self.model_package = None
try:
exception_handler_model_name = "%s.exception_handler" % self.__module__[:self.__module__.rindex('.')]
self.exception_handler_model = importlib.import_module(exception_handler_model_name)
except ImportError:
self.exception_handler_model = None
@classmethod
def _init_logger(cls):
logger_name = 'HuaweiCloud-SDK-%s' % cls.__name__
logger = logging.getLogger(logger_name)
logger.propagate = False
return logger
def with_config(self, config):
self._config = config
return self
def with_credentials(self, credentials):
self._credentials = credentials
return self
def with_endpoint(self, endpoint):
self._endpoint = endpoint
return self
def with_http_handler(self, http_handler):
self._http_handler = http_handler if http_handler is not None else HttpHandler()
return self
def init_http_client(self):
exception_handler = None \
if self.exception_handler_model is None else getattr(self.exception_handler_model, "handle_exception")
self._http_client = HttpClient(self._config, self._http_handler, exception_handler, self._logger)
def add_stream_logger(self, stream, log_level, format_string):
self._logger.setLevel(log_level)
stream_handler = logging.StreamHandler(stream)
stream_handler.setLevel(log_level)
formatter = logging.Formatter(format_string if format_string is not None else core_utils.LOG_FORMAT)
stream_handler.setFormatter(formatter)
if stream_handler not in self._logger.handlers:
self._logger.addHandler(stream_handler)
def add_file_logger(self, path, log_level, max_bytes, backup_count, format_string):
self._logger.setLevel(log_level)
file_handler = RotatingFileHandler(path, maxBytes=max_bytes, backupCount=backup_count)
file_handler.setLevel(log_level)
formatter = logging.Formatter(format_string if format_string is not None else core_utils.LOG_FORMAT)
file_handler.setFormatter(formatter)
if file_handler not in self._logger.handlers:
self._logger.addHandler(file_handler)
def get_agent(self):
return self._agent
def get_credentials(self):
return self._credentials
def get_http_client(self):
return self._http_client
def _parse_header_params(self, collection_formats, header_params):
header_params = self.post_process_params(header_params) or {}
header_params.update(self.preset_headers)
if header_params:
header_params = http_utils.sanitize_for_serialization(header_params)
header_params = dict(http_utils.parameters_to_tuples(header_params, collection_formats))
header_params.update(self._agent)
return header_params
def _parse_path_params(self, collection_formats, path_params, resource_path, update_path_params):
path_params = self.post_process_params(path_params) or {}
if path_params:
path_params = http_utils.sanitize_for_serialization(path_params)
path_params = http_utils.parameters_to_tuples(path_params, collection_formats)
for k, v in path_params:
resource_path = resource_path.replace('{%s}' % k, quote(str(v), safe=''))
if update_path_params:
update_path_params = http_utils.sanitize_for_serialization(update_path_params)
update_path_params = http_utils.parameters_to_tuples(update_path_params, collection_formats)
for k, v in update_path_params:
resource_path = resource_path.replace('{%s}' % k, quote(str(v), safe=''))
return resource_path
def _parse_query_params(self, collection_formats, query_params):
query_params = self.post_process_params(query_params) or []
if query_params:
query_params = http_utils.sanitize_for_serialization(query_params)
query_params = http_utils.parameters_to_tuples(query_params, collection_formats)
return query_params
def _parse_post_params(self, collection_formats, post_params):
post_params = self.post_process_params(post_params) if post_params else {}
if post_params:
post_params = http_utils.sanitize_for_serialization(post_params)
post_params = http_utils.parameters_to_tuples(post_params, collection_formats)
return post_params
@classmethod
def _parse_body(cls, body, post_params):
if body:
if all([hasattr(body, '__iter__'), not isinstance(body, (str, bytes, list, tuple, Mapping))]):
return body
body = http_utils.sanitize_for_serialization(body)
body = json.dumps(body)
elif len(post_params) != 0:
body = post_params
else:
body = ""
return body
def _is_stream(self, response_type):
if type(response_type) == str and hasattr(self.model_package, response_type):
klass = getattr(self.model_package, response_type)
if issubclass(klass, SdkStreamResponse):
return True
return False
@classmethod
def post_process_params(cls, params):
if type(params) == dict:
for key in list(params.keys()):
if params[key] is None:
del [params[key]]
return params
elif type(params) == list:
list_filter = filter(lambda x: type(x) == tuple and len(x) == 2 and x[1] is not None, params)
return [i for i in list_filter]
def do_http_request(self, method, resource_path, path_params=None, query_params=None, header_params=None, body=None,
post_params=None, response_type=None, response_headers=None, collection_formats=None, request_type=None,
async_request=False):
url_parse_result = urlparse(self._endpoint)
schema = url_parse_result.scheme
host = url_parse_result.netloc
header_params = self._parse_header_params(collection_formats, header_params)
resource_path = self._parse_path_params(collection_formats, path_params, resource_path,
self._credentials.get_update_path_params())
query_params = self._parse_query_params(collection_formats, query_params)
post_params = self._parse_post_params(collection_formats, post_params)
body = self._parse_body(body, post_params)
stream = self._is_stream(response_type)
sdk_request = SdkRequest(method=method, schema=schema, host=host, resource_path=resource_path,
query_params=query_params, header_params=header_params, body=body, stream=stream)
future_request = self._credentials.process_auth_request(sdk_request, self._http_client)
if async_request:
executor = ThreadPoolExecutor(max_workers=8)
future_response = executor.submit(self._do_http_request_async, future_request, response_type,
response_headers)
return FutureSdkResponse(future_response, self._logger)
else:
request = future_request.result()
response = self._do_http_request_sync(request)
return self.sync_response_handler(response, response_type, response_headers)
def _do_http_request_sync(self, request):
response = self._http_client.do_request_sync(request)
return response
def _do_http_request_async(self, future_request, response_type, response_headers):
request = future_request.result()
future_response = self._http_client.do_request_async(
request=request, hooks=[self.async_response_hook_factory(response_type, response_headers)]
)
return future_response
def sync_response_handler(self, response, response_type, response_headers):
return_data = self.deserialize(response, response_type)
self.set_response_status_code(return_data, response)
if response_headers is not None and len(response_headers) > 0:
self.set_response_headers(return_data, response, response_headers)
return return_data
def async_response_hook_factory(self, response_type, response_headers):
def response_hook(resp, *args, **kwargs):
resp.data = self.sync_response_handler(resp, response_type, response_headers)
return response_hook
@classmethod
def set_response_status_code(cls, return_data, response):
setattr(return_data, "status_code", response.status_code)
@classmethod
def set_response_headers(cls, return_data, response, response_headers):
if not hasattr(return_data, "attribute_map"):
return
for attr in return_data.attribute_map:
if getattr(return_data, attr) is not None:
continue
key_in_response_headers = return_data.attribute_map[attr]
if key_in_response_headers in response_headers:
setattr(return_data, attr, response.headers[key_in_response_headers])
def deserialize(self, response, response_type):
if type(response_type) == str and hasattr(self.model_package, response_type):
klass = getattr(self.model_package, response_type)
if issubclass(klass, SdkStreamResponse):
return klass(response)
try:
data = json.loads(response.text)
except ValueError:
data = response.text
return self._deserialize(data, response_type)
def _deserialize(self, data, klass):
if data is None:
return None
if type(klass) == str:
if klass.startswith('list['):
sub_kls = re.match(r'list\[(.*)\]', klass).group(1)
return [self._deserialize(sub_data, sub_kls)
for sub_data in data]
if klass.startswith('dict('):
sub_kls = re.match(r'dict\(([^,]*), (.*)\)', klass).group(2)
return {k: self._deserialize(v, sub_kls)
for k, v in six.iteritems(data)}
if klass in native_types_mapping:
klass = native_types_mapping[klass]
else:
klass = getattr(self.model_package, klass)
if klass in primitive_types:
return self._deserialize_primitive(data, klass)
elif klass == object:
return self._deserialize_object(data)
elif klass == datetime.date:
return self._deserialize_date(data)
elif klass == datetime.datetime:
return self._deserialize_data_time(data)
else:
return self._deserialize_model(data, klass)
@classmethod
def _deserialize_primitive(cls, data, klass):
try:
return klass(data)
except UnicodeEncodeError:
return six.text_type(data)
except TypeError:
return data
@classmethod
def _deserialize_object(cls, value):
return value
@classmethod
def _deserialize_date(cls, string):
try:
from dateutil.parser import parse
return parse(string if string.endswith("Z") else string + "Z").date()
except ImportError:
return string
except ValueError:
return string
@classmethod
def _deserialize_data_time(cls, string):
try:
from dateutil.parser import parse
return parse(string if string.endswith("Z") else string + "Z")
except ImportError:
return string
except ValueError:
return string
def _deserialize_model(self, data, klass):
if not klass.openapi_types and not hasattr(klass, 'get_real_child_model'):
if type(data) == int and hasattr(klass, "_%s" % data):
return getattr(klass, "_%s" % data)
if type(data) == str and hasattr(klass, re.sub(r'\W+', '_', data).upper()):
return getattr(klass, re.sub(r'\W+', '_', data).upper())
if type(data) == bool and hasattr(klass, str(data).upper()):
return getattr(klass, str(data).upper())
if type(data) == float and hasattr(klass, ("_%s" % data).replace('.', '_')):
return getattr(klass, ("_%s" % data).replace('.', '_'))
return klass()
kwargs = {}
if klass.openapi_types is not None:
for attr, attr_type in six.iteritems(klass.openapi_types):
if data is not None and isinstance(data, (list, dict)):
if klass.attribute_map[attr] in data:
value = data[klass.attribute_map[attr]]
kwargs[attr] = self._deserialize(value, attr_type)
if klass.attribute_map[attr] == "body":
kwargs[attr] = self._deserialize(data, attr_type)
instance = klass(**kwargs)
if hasattr(instance, 'get_real_child_model'):
klass_name = instance.get_real_child_model(data)
if klass_name:
instance = self._deserialize(data, klass_name)
return instance