Skip to content

Commit

Permalink
Make Exceptions detailed (ProtoThis#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
Quentame committed May 14, 2020
1 parent 11512e8 commit 9a523c7
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 95 deletions.
3 changes: 3 additions & 0 deletions synology_dsm/const.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
"""Library constants."""

# APIs
API_INFO = "SYNO.API.Info"
API_AUTH = "SYNO.API.Auth"

# SYNO.*
ERROR_COMMON = {
Expand Down
61 changes: 31 additions & 30 deletions synology_dsm/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
# -*- coding: utf-8 -*-
"""Library exceptions."""
from .const import ERROR_AUTH, ERROR_COMMON, ERROR_DOWNLOAD_SEARCH, ERROR_DOWNLOAD_TASK, ERROR_FILE, ERROR_SURVEILLANCE, ERROR_VIRTUALIZATION
from .const import API_AUTH, ERROR_AUTH, ERROR_COMMON, ERROR_DOWNLOAD_SEARCH, ERROR_DOWNLOAD_TASK, ERROR_FILE, ERROR_SURVEILLANCE, ERROR_VIRTUALIZATION

class SynologyDSMException(Exception):
"""Generic Synology DSM exception."""
pass
def __init__(self, api, code, details=None):
reason = ERROR_COMMON.get(code)
if api and not reason:
if api == API_AUTH:
reason = ERROR_AUTH.get(code)
elif "SYNO.DownloadStation" in api:
if "BTSearch" in api:
reason = ERROR_DOWNLOAD_SEARCH.get(code)
elif "Task" in api:
reason = ERROR_DOWNLOAD_TASK.get(code)
elif "SYNO.FileStation" in api:
reason = ERROR_FILE.get(code)
elif "SYNO.SurveillanceStation" in api:
reason = ERROR_SURVEILLANCE.get(code)
elif "SYNO.Virtualization" in api:
reason = ERROR_VIRTUALIZATION.get(code)
if not reason:
reason = "Unknown"

error_message={"api": api, "code": code, "reason": reason, "details": details}
super(SynologyDSMException, self).__init__(error_message)

# Request
class SynologyDSMRequestException(SynologyDSMException):
Expand All @@ -15,37 +35,18 @@ def __init__(self, exception):
if hasattr(exception.args[0], "reason"):
ex_reason = exception.args[0].reason
message = "%s = %s" % (ex_class, ex_reason)
super(SynologyDSMRequestException, self).__init__(message)
super(SynologyDSMRequestException, self).__init__(None, -1, message)

# API
class SynologyDSMAPINotExistsException(SynologyDSMException):
"""API not exists exception."""
def __init__(self, api):
message = "API %s does not exists" % api
super(SynologyDSMAPINotExistsException, self).__init__(message)
super(SynologyDSMAPINotExistsException, self).__init__(api, -2, "API %s does not exists" % api)

class SynologyDSMAPIErrorException(SynologyDSMException):
"""API returns an error exception."""
def __init__(self, api, code):
reason = ERROR_COMMON.get(code)
if api and not reason:
if api == "SYNO.API.Auth":
reason = ERROR_AUTH.get(code)
elif "SYNO.DownloadStation" in api:
if "BTSearch" in api:
reason = ERROR_DOWNLOAD_SEARCH.get(code)
elif "Task" in api:
reason = ERROR_DOWNLOAD_TASK.get(code)
elif "SYNO.FileStation" in api:
reason = ERROR_FILE.get(code)
elif "SYNO.SurveillanceStation" in api:
reason = ERROR_SURVEILLANCE.get(code)
elif "SYNO.Virtualization" in api:
reason = ERROR_VIRTUALIZATION.get(code)
if not reason:
reason = "Unknown"
message = "\n Code: %s\n Reason: %s" % (str(code), reason)
super(SynologyDSMAPIErrorException, self).__init__(message)
def __init__(self, api, code, details):
super(SynologyDSMAPIErrorException, self).__init__(api, code, details)

# Login
class SynologyDSMLoginFailedException(SynologyDSMException):
Expand All @@ -57,32 +58,32 @@ class SynologyDSMLoginInvalidException(SynologyDSMLoginFailedException):
"""Invalid password & not admin account exception."""
def __init__(self, username):
message = "Invalid password or not admin account: %s" % username
super(SynologyDSMLoginInvalidException, self).__init__(message)
super(SynologyDSMLoginInvalidException, self).__init__(API_AUTH, 400, message)


class SynologyDSMLoginDisabledAccountException(SynologyDSMLoginFailedException):
"""Guest & disabled account exception."""
def __init__(self, username):
message = "Guest or disabled account: %s" % username
super(SynologyDSMLoginDisabledAccountException, self).__init__(message)
super(SynologyDSMLoginDisabledAccountException, self).__init__(API_AUTH, 401, message)


class SynologyDSMLoginPermissionDeniedException(SynologyDSMLoginFailedException):
"""No access to login exception."""
def __init__(self, username):
message = "Permission denied for account: %s" % username
super(SynologyDSMLoginPermissionDeniedException, self).__init__(message)
super(SynologyDSMLoginPermissionDeniedException, self).__init__(API_AUTH, 402, message)


class SynologyDSMLogin2SARequiredException(SynologyDSMLoginFailedException):
"""2SA required to login exception."""
def __init__(self, username):
message = "Two-step authentication required for account: %s" % username
super(SynologyDSMLogin2SARequiredException, self).__init__(message)
super(SynologyDSMLogin2SARequiredException, self).__init__(API_AUTH, 403, message)


class SynologyDSMLogin2SAFailedException(SynologyDSMLoginFailedException):
"""2SA code failed exception."""
def __init__(self):
message = "Two-step authentication failed, retry with a new pass code"
super(SynologyDSMLogin2SAFailedException, self).__init__(message)
super(SynologyDSMLogin2SAFailedException, self).__init__(API_AUTH, 404, message)
20 changes: 10 additions & 10 deletions synology_dsm/synology_dsm.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,12 @@
from .api.dsm.information import SynoDSMInformation
from .api.dsm.network import SynoDSMNetwork
from .api.storage.storage import SynoStorage
from .const import API_AUTH, API_INFO


class SynologyDSM(object):
"""Class containing the main Synology DSM functions."""

API_INFO = "SYNO.API.Info"
API_AUTH = "SYNO.API.Auth"

DSM_5_WEIRD_URL_API = [
SynoStorage.API_KEY,
]
Expand Down Expand Up @@ -103,9 +101,9 @@ def _build_url(self, api):

def discover_apis(self):
"""Retreives available API infos from the NAS."""
if self._apis.get(self.API_AUTH):
if self._apis.get(API_AUTH):
return
self._apis = self.get(self.API_INFO, "query")["data"]
self._apis = self.get(API_INFO, "query")["data"]

@property
def apis(self):
Expand Down Expand Up @@ -134,7 +132,7 @@ def login(self, otp_code=None):
params["device_id"] = self._device_token

# Request login
result = self.get(self.API_AUTH, "login", params)
result = self.get(API_AUTH, "login", params)

# Handle errors
if result.get("error"):
Expand Down Expand Up @@ -183,11 +181,11 @@ def _request(
):
"""Handles API request."""
# Discover existing APIs
if api != self.API_INFO:
if api != API_INFO:
self.discover_apis()

# Check if logged
if not self._session_id and api not in [self.API_AUTH, self.API_INFO]:
if not self._session_id and api not in [API_AUTH, API_INFO]:
self.login()

# Build request params
Expand Down Expand Up @@ -220,15 +218,17 @@ def _request(
self._debuglog(str(response))

# Handle data errors
if response.get("error") and api != self.API_AUTH:
if response.get("error") and api != API_AUTH:
self._debuglog("Session error: " + str(response["error"]["code"]))
if response["error"]["code"] == 119 and retry_once:
# Session ID not valid, see https://github.com/aerialls/synology-srm/pull/3
self._session_id = None
self._syno_token = None
self._device_token = None
return self._request(request_method, api, method, params, False)
raise SynologyDSMAPIErrorException(api, response["error"]["code"])
raise SynologyDSMAPIErrorException(
api, response["error"]["code"], response["error"].get("errors")
)

return response

Expand Down
9 changes: 7 additions & 2 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from synology_dsm.api.dsm.information import SynoDSMInformation
from synology_dsm.api.dsm.network import SynoDSMNetwork
from synology_dsm.api.storage.storage import SynoStorage
from synology_dsm.const import API_AUTH, API_INFO

from .const import (
ERROR_INSUFFICIENT_USER_PRIVILEGE,
Expand All @@ -26,6 +27,7 @@
DSM_6_DSM_INFORMATION,
DSM_6_DSM_NETWORK,
DSM_6_CORE_UTILIZATION,
DSM_6_CORE_UTILIZATION_ERROR_1055,
DSM_6_CORE_SECURITY,
DSM_6_STORAGE_STORAGE_DS213_PLUS_SHR1_2DISKS_2VOLS,
DSM_6_STORAGE_STORAGE_DS918_PLUS_RAID5_3DISKS_1VOL,
Expand Down Expand Up @@ -115,6 +117,7 @@ def __init__(

self.dsm_version = 6 # 5 or 6
self.disks_redundancy = "RAID" # RAID or SHR[number][_EXPANSION]
self.error = False

def _execute_request(self, method, url, **kwargs):
url += urlencode(kwargs["params"])
Expand Down Expand Up @@ -148,10 +151,10 @@ def _execute_request(self, method, url, **kwargs):
if "https" not in url:
raise SynologyDSMRequestException(RequestException("Bad request"))

if self.API_INFO in url:
if API_INFO in url:
return API_SWITCHER[self.dsm_version]["API_INFO"]

if self.API_AUTH in url:
if API_AUTH in url:
if VALID_USER_2SA in url and VALID_PASSWORD in url:
if "otp_code" not in url and "device_id" not in url:
return API_SWITCHER[self.dsm_version]["AUTH_LOGIN_2SA"]
Expand Down Expand Up @@ -183,6 +186,8 @@ def _execute_request(self, method, url, **kwargs):
return API_SWITCHER[self.dsm_version]["CORE_SECURITY"]

if SynoCoreUtilization.API_KEY in url:
if self.error:
return DSM_6_CORE_UTILIZATION_ERROR_1055
return API_SWITCHER[self.dsm_version]["CORE_UTILIZATION"]

if SynoStorage.API_KEY in url:
Expand Down
5 changes: 4 additions & 1 deletion tests/api_data/dsm_6/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
DSM_6_AUTH_LOGIN_2SA,
DSM_6_AUTH_LOGIN_2SA_OTP,
)
from .core.const_6_core_utilization import DSM_6_CORE_UTILIZATION
from .core.const_6_core_utilization import (
DSM_6_CORE_UTILIZATION,
DSM_6_CORE_UTILIZATION_ERROR_1055,
)
from .core.const_6_core_security import DSM_6_CORE_SECURITY
from .dsm.const_6_dsm_info import DSM_6_DSM_INFORMATION
from .dsm.const_6_dsm_network import DSM_6_DSM_NETWORK
Expand Down
13 changes: 13 additions & 0 deletions tests/api_data/dsm_6/core/const_6_core_utilization.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
# -*- coding: utf-8 -*-
"""DSM 6 SYNO.Core.System.Utilization data."""

DSM_6_CORE_UTILIZATION_ERROR_1055 = {
"error": {
"code": 1055,
"errors": {
"err_key": "",
"err_line": 883,
"err_msg": "Transmition failed.",
"err_session": "",
},
},
"success": False,
}

DSM_6_CORE_UTILIZATION = {
"data": {
"cpu": {
Expand Down
Loading

0 comments on commit 9a523c7

Please sign in to comment.