diff --git a/tableauserverclient/__init__.py b/tableauserverclient/__init__.py index bab2cf05..1299c33b 100644 --- a/tableauserverclient/__init__.py +++ b/tableauserverclient/__init__.py @@ -56,6 +56,7 @@ PDFRequestOptions, RequestOptions, MissingRequiredFieldError, + FailedSignInError, NotSignedInError, ServerResponseError, Filter, @@ -79,6 +80,7 @@ "DatabaseItem", "DataFreshnessPolicyItem", "DatasourceItem", + "FailedSignInError", "FavoriteItem", "FlowItem", "FlowRunItem", diff --git a/tableauserverclient/server/__init__.py b/tableauserverclient/server/__init__.py index f5cd1d23..87cc9460 100644 --- a/tableauserverclient/server/__init__.py +++ b/tableauserverclient/server/__init__.py @@ -11,7 +11,7 @@ from tableauserverclient.server.sort import Sort from tableauserverclient.server.server import Server from tableauserverclient.server.pager import Pager -from tableauserverclient.server.endpoint.exceptions import NotSignedInError +from tableauserverclient.server.endpoint.exceptions import FailedSignInError, NotSignedInError from tableauserverclient.server.endpoint import ( Auth, @@ -57,6 +57,7 @@ "Sort", "Server", "Pager", + "FailedSignInError", "NotSignedInError", "Auth", "CustomViews", diff --git a/tableauserverclient/server/endpoint/endpoint.py b/tableauserverclient/server/endpoint/endpoint.py index bef96fde..7ff71baa 100644 --- a/tableauserverclient/server/endpoint/endpoint.py +++ b/tableauserverclient/server/endpoint/endpoint.py @@ -19,6 +19,7 @@ from tableauserverclient.server.request_options import RequestOptions from tableauserverclient.server.endpoint.exceptions import ( + FailedSignInError, ServerResponseError, InternalServerError, NonXMLResponseError, @@ -160,7 +161,7 @@ def _check_status(self, server_response: "Response", url: Optional[str] = None): try: if server_response.status_code == 401: # TODO: catch this in server.py and attempt to sign in again, in case it's a session expiry - raise NotSignedInError(server_response.content, url) + raise FailedSignInError.from_response(server_response.content, self.parent_srv.namespace, url) raise ServerResponseError.from_response(server_response.content, self.parent_srv.namespace, url) except ParseError: diff --git a/tableauserverclient/server/endpoint/exceptions.py b/tableauserverclient/server/endpoint/exceptions.py index 17d789d0..77332da3 100644 --- a/tableauserverclient/server/endpoint/exceptions.py +++ b/tableauserverclient/server/endpoint/exceptions.py @@ -1,13 +1,20 @@ from defusedxml.ElementTree import fromstring -from typing import Optional +from typing import Mapping, Optional, TypeVar + + +def split_pascal_case(s: str) -> str: + return "".join([f" {c}" if c.isupper() else c for c in s]).strip() class TableauError(Exception): pass -class ServerResponseError(TableauError): - def __init__(self, code, summary, detail, url=None): +T = TypeVar("T") + + +class XMLError(TableauError): + def __init__(self, code: str, summary: str, detail: str, url: Optional[str] = None) -> None: self.code = code self.summary = summary self.detail = detail @@ -18,7 +25,7 @@ def __str__(self): return f"\n\n\t{self.code}: {self.summary}\n\t\t{self.detail}" @classmethod - def from_response(cls, resp, ns, url=None): + def from_response(cls, resp, ns, url): # Check elements exist before .text parsed_response = fromstring(resp) try: @@ -33,6 +40,10 @@ def from_response(cls, resp, ns, url=None): return error_response +class ServerResponseError(XMLError): + pass + + class InternalServerError(TableauError): def __init__(self, server_response, request_url: Optional[str] = None): self.code = server_response.status_code @@ -51,6 +62,11 @@ class NotSignedInError(TableauError): pass +class FailedSignInError(XMLError, NotSignedInError): + def __str__(self): + return f"{split_pascal_case(self.__class__.__name__)}: {super().__str__()}" + + class ItemTypeNotAllowed(TableauError): pass diff --git a/test/test_auth.py b/test/test_auth.py index eaf13481..48100ad8 100644 --- a/test/test_auth.py +++ b/test/test_auth.py @@ -63,7 +63,7 @@ def test_sign_in_error(self): with requests_mock.mock() as m: m.post(self.baseurl + "/signin", text=response_xml, status_code=401) tableau_auth = TSC.TableauAuth("testuser", "wrongpassword") - self.assertRaises(TSC.NotSignedInError, self.server.auth.sign_in, tableau_auth) + self.assertRaises(TSC.FailedSignInError, self.server.auth.sign_in, tableau_auth) def test_sign_in_invalid_token(self): with open(SIGN_IN_ERROR_XML, "rb") as f: @@ -71,7 +71,7 @@ def test_sign_in_invalid_token(self): with requests_mock.mock() as m: m.post(self.baseurl + "/signin", text=response_xml, status_code=401) tableau_auth = TSC.PersonalAccessTokenAuth(token_name="mytoken", personal_access_token="invalid") - self.assertRaises(TSC.NotSignedInError, self.server.auth.sign_in, tableau_auth) + self.assertRaises(TSC.FailedSignInError, self.server.auth.sign_in, tableau_auth) def test_sign_in_without_auth(self): with open(SIGN_IN_ERROR_XML, "rb") as f: @@ -79,7 +79,7 @@ def test_sign_in_without_auth(self): with requests_mock.mock() as m: m.post(self.baseurl + "/signin", text=response_xml, status_code=401) tableau_auth = TSC.TableauAuth("", "") - self.assertRaises(TSC.NotSignedInError, self.server.auth.sign_in, tableau_auth) + self.assertRaises(TSC.FailedSignInError, self.server.auth.sign_in, tableau_auth) def test_sign_out(self): with open(SIGN_IN_XML, "rb") as f: