Skip to content

Commit

Permalink
Merge pull request #4 from YongSangUn/error_handler
Browse files Browse the repository at this point in the history
refactor: func call() for better error handling; now returns single d…
  • Loading branch information
YongSangUn authored Sep 4, 2024
2 parents 7f7f075 + 5e4ba97 commit 289371a
Show file tree
Hide file tree
Showing 6 changed files with 296 additions and 70 deletions.
34 changes: 26 additions & 8 deletions src/pyqcloud_sdk/__init__.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
# -*- coding: utf-8 -*-

__version__ = "0.0.1"
__version__ = "0.0.2"

from .base import QcloudBase
from .config import Config
from .services import Services

from .exceptions import (
ServiceError,
ServiceJsonNotFoundError,
ServiceJsonLoadError,
APIError,
AuthenticationError,
ClientError,
ConfigError,
AuthenticationError,
QcloudWrapperError,
ServerError,
ServiceDefinitionError,
ServiceDiscoveryError,
ServiceNotFoundError,
)
from .logging import logger, setup_logging
from .services import Services

from .logging import logger
__all__ = [
"QcloudBase",
"Config",
"Services",
"QcloudWrapperError",
"ConfigError",
"AuthenticationError",
"ServiceDiscoveryError",
"ServiceNotFoundError",
"ServiceDefinitionError",
"APIError",
"ClientError",
"ServerError",
"logger",
"setup_logging",
]
159 changes: 132 additions & 27 deletions src/pyqcloud_sdk/base.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-


import os
from time import sleep
from typing import Any
from typing import Any, Optional

from tencentcloud.common.common_client import CommonClient
from tencentcloud.common.credential import Credential
Expand All @@ -12,34 +11,78 @@
from tencentcloud.common.profile.http_profile import HttpProfile

from .config import Config
from .exceptions import AuthenticationError, ClientError, ConfigError
from .exceptions import (
AuthenticationError,
ClientError,
QcloudWrapperError,
ServerError,
)
from .logging import logger


class QcloudBase:
def __init__(self, service_config: dict):
"""Base class for interacting with Tencent Cloud services."""

def __init__(self, service_config: dict, client: Optional[CommonClient] = None):
"""
Initializes a QcloudBase object.
Args:
service_config (dict): Configuration settings for the Tencent Cloud service.
client (Optional[CommonClient], optional): An optional instance of CommonClient.
If not provided, one will be created.
"""
self.config = Config()
self.config._deserialize(service_config)
# if not self.config.Region:
# raise ConfigError("Parameter 'region' is None")
self.client = client

def set_region(self, region: str):
"""
Sets the region for the Tencent Cloud client.
Args:
region (str): The region to use.
"""
self.config.Region = region
logger.info(f"Region set to: {region}")

def set_secret_key(self, secretKey: str):
self.config.SecretKey = secretKey
def set_secret_key(self, secret_key: str):
"""
Sets the SecretKey for authentication.
Args:
secret_key (str): The Tencent Cloud SecretKey.
"""
self.config.SecretKey = secret_key
logger.info("SecretKey set.")

def set_secret_id(self, secretId: str):
self.config.SecretId = secretId
def set_secret_id(self, secret_id: str):
"""
Sets the SecretId for authentication.
Args:
secret_id (str): The Tencent Cloud SecretId.
"""
self.config.SecretId = secret_id
logger.info("SecretId set.")

def _try_set_secret_from_env(
self,
id_env_name: str = "TENCENTCLOUD_SECRET_ID",
key_env_name: str = "TENCENTCLOUD_SECRET_KEY",
) -> bool:
"""
Attempts to set the SecretId and SecretKey from environment variables.
Args:
id_env_name (str, optional): The environment variable name for SecretId.
Defaults to "TENCENTCLOUD_SECRET_ID".
key_env_name (str, optional): The environment variable name for SecretKey.
Defaults to "TENCENTCLOUD_SECRET_KEY".
Returns:
bool: True if successful, False otherwise.
"""
secret_id = os.environ.get(id_env_name)
secret_key = os.environ.get(key_env_name)
if not secret_id or not secret_key:
Expand All @@ -50,34 +93,72 @@ def _try_set_secret_from_env(
logger.info("Secrets set from environment variables.")
return True

def new_client(self) -> CommonClient:
def _get_client(self) -> CommonClient:
"""
Creates or returns an instance of CommonClient.
Returns:
CommonClient: An instance of CommonClient for making API calls.
Raises:
AuthenticationError: If authentication fails.
"""
if self.client:
return self.client

if not self.config.SecretId or not self.config.SecretKey:
logger.warning("SecretId or SecretKey is None, attempting to use environment values.")
if not self._try_set_secret_from_env():
raise AuthenticationError("SecretId or SecretKey is not set")

cred = Credential(self.config.SecretId, self.config.SecretKey)
httpProfile = HttpProfile()
httpProfile.endpoint = self.config.EndPoint
clientProfile = ClientProfile()
clientProfile.httpProfile = httpProfile
http_profile = HttpProfile()
http_profile.endpoint = self.config.EndPoint
client_profile = ClientProfile()
client_profile.httpProfile = http_profile

logger.info(
f"Creating a new client for module: {self.config.Module}, version: {self.config.Version}, region: {self.config.Region}"
f"Creating a new client for module: {self.config.Module}, "
f"version: {self.config.Version}, region: {self.config.Region}"
)
return CommonClient(
self.config.Module,
self.config.Version,
cred,
self.config.Region,
profile=clientProfile,
profile=client_profile,
)

def call(self, action: str, action_params: dict = {}, headers: dict = {}) -> Any:
"""
Makes a request to a Tencent Cloud API.
Args:
action (str): The API action to perform.
action_params (dict, optional): Parameters for the API call. Defaults to {}.
headers (dict, optional): Additional headers for the request. Defaults to {}.
Returns:
Any: The API response data.
Raises:
AuthenticationError: If authentication fails.
ClientError: For other client-side errors.
ServerError: For errors originating from the Tencent Cloud server.
"""
try:
client = self.new_client()
logger.info(f"Calling action: {action} with params: {action_params}")
client = self._get_client()
logger.info(f"Calling action: {action} with params: {action_params}, headers: {headers}")
resp = client.call_json(action, action_params, headers=headers)
logger.debug(f"Response: {resp}")

if isinstance(resp, dict) and resp.get("Response", {}).get("Error"):
error_info = resp["Response"]["Error"]
raise ServerError(
error_info.get("Message", "Tencent Cloud API Error"),
error_info.get("RequestId"),
)

return resp
except AuthenticationError as err:
logger.error(f"Authentication Error: {err}")
Expand All @@ -86,16 +167,39 @@ def call(self, action: str, action_params: dict = {}, headers: dict = {}) -> Any
logger.error(f"Client Error: {err}")
raise err
except TencentCloudSDKException as err:
raise err
logger.error(f"Tencent Cloud SDK Exception: {err}")
raise ServerError(str(err)) from err
except Exception as err:
logger.exception(f"An unexpected error occurred: {err}")
raise QcloudWrapperError(f"An unexpected error occurred: {err}") from err

def call_with_retry(
self, action: str, action_params: dict, max_retries: int = 5, retries: int = 0, retry_time: int = 5
self,
action: str,
action_params: dict,
max_retries: int = 5,
retries: int = 0,
retry_time: int = 5,
) -> Any:
"""Calls Tencent Cloud API, retries if encountering errors related to ongoing tasks."""

"""
Calls Tencent Cloud API, retries if encountering errors related to ongoing tasks.
Args:
action (str): The API action to perform.
action_params (dict): Parameters for the API call.
max_retries (int, optional): Maximum number of retries. Defaults to 5.
retries (int, optional): Current retry count. Defaults to 0.
retry_time (int, optional): Time to sleep between retries (in seconds). Defaults to 5.
Returns:
Any: The API response data.
Raises:
ServerError: If the maximum number of retries is reached and the error persists.
"""
try:
return self.call(action=action, action_params=action_params)
except TencentCloudSDKException as err:
except ServerError as err:
if "tasks are being processed" in str(err) or "task is working" in str(err):
if retries < max_retries:
retries += 1
Expand All @@ -104,8 +208,9 @@ def call_with_retry(
return self.call_with_retry(action, action_params, max_retries, retries, retry_time)
else:
logger.error("Maximum number of retries reached.")
raise err
raise
else:
raise err
raise
except Exception as err:
return err
logger.exception(f"An unexpected error occurred: {err}")
raise QcloudWrapperError(f"An unexpected error occurred: {err}") from err
19 changes: 14 additions & 5 deletions src/pyqcloud_sdk/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@


class Config(object):
"""Configuration settings for the Tencent Cloud client."""

def __init__(self):
"""Initializes a Config object with default values."""
self.Module = None
self.Version = None
self.EndPoint = None
Expand All @@ -13,15 +16,21 @@ def __init__(self):
self.SecretKey = None

def _deserialize(self, config: dict):
"""
Deserializes configuration settings from a dictionary.
Args:
config (dict): A dictionary containing configuration settings.
"""
self.Module = config.get("Module")
self.Version = config.get("Version")
self.EndPoint = config.get("EndPoint")
self.Region = config.get("Region")
self.SecretId = config.get("SecretId")
self.SecretKey = config.get("SecretKey")
memeber_set = set(config.keys())
member_set = set(config.keys())
for name, _ in vars(self).items():
if name in memeber_set:
memeber_set.remove(name)
if len(memeber_set) > 0:
logger.warning("%s fileds are useless." % ",".join(memeber_set))
if name in member_set:
member_set.remove(name)
if len(member_set) > 0:
logger.warning("%s fields are useless." % ",".join(member_set))
55 changes: 49 additions & 6 deletions src/pyqcloud_sdk/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,68 @@
# -*- coding: utf-8 -*-


class ServiceError(Exception):
class QcloudWrapperError(Exception):
"""Base exception class for all qcloud-sdk-python-wrapper errors."""

pass


class ConfigError(QcloudWrapperError):
"""Raised for errors related to client configuration."""

pass


class ServiceJsonNotFoundError(ServiceError):
class AuthenticationError(ConfigError):
"""Raised for authentication-related errors."""

pass


class ServiceJsonLoadError(ServiceError):
class ServiceDiscoveryError(QcloudWrapperError):
"""Raised for errors during service discovery."""

pass


class ClientError(Exception):
class ServiceNotFoundError(ServiceDiscoveryError):
"""Raised when the requested Tencent Cloud service is not found."""

pass


class ConfigError(ClientError):
class ServiceDefinitionError(ServiceDiscoveryError):
"""Raised when there are errors in the service definition."""

pass


class AuthenticationError(ConfigError):
class APIError(QcloudWrapperError):
"""Base exception class for Tencent Cloud API call errors."""

pass


class ClientError(APIError):
"""Raised for client-side errors during API calls."""

pass


class ServerError(APIError):
"""Raised for errors originating from the Tencent Cloud server."""

def __init__(self, message, request_id=None):
super().__init__(message)
self._request_id = request_id

@property
def request_id(self):
"""str: The request ID returned by the Tencent Cloud API (if available)."""
return self._request_id


class LoggingError(QcloudWrapperError):
"""Raised for errors related to logging."""

pass
Loading

0 comments on commit 289371a

Please sign in to comment.