diff --git a/.github/workflows/slack.yml b/.github/workflows/slack.yml index c3b17e8c4..292dbfa2c 100644 --- a/.github/workflows/slack.yml +++ b/.github/workflows/slack.yml @@ -8,6 +8,7 @@ jobs: name: Sends a message to Slack when a push, a pull request or an issue is made steps: - name: Send message to Slack API + continue-on-error: true uses: archive/github-actions-slack@v2.2.2 id: notify with: diff --git a/tableauserverclient/models/job_item.py b/tableauserverclient/models/job_item.py index d87ac730c..8c5fc52a6 100644 --- a/tableauserverclient/models/job_item.py +++ b/tableauserverclient/models/job_item.py @@ -3,6 +3,12 @@ from ..datetime_helpers import parse_datetime +from typing import List, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + import datetime + + class JobItem(object): class FinishCode: """ @@ -10,22 +16,22 @@ class FinishCode: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_jobs_tasks_and_schedules.htm#query_job """ - Success = 0 - Failed = 1 - Cancelled = 2 + Success: int = 0 + Failed: int = 1 + Cancelled: int = 2 def __init__( self, - id_, - job_type, - progress, - created_at, - started_at=None, - completed_at=None, - finish_code=0, - notes=None, - mode=None, - flow_run=None, + id_: str, + job_type: str, + progress: str, + created_at: "datetime.datetime", + started_at: Optional["datetime.datetime"] = None, + completed_at: Optional["datetime.datetime"] = None, + finish_code: int = 0, + notes: Optional[List[str]] = None, + mode: Optional[str] = None, + flow_run: Optional[FlowRunItem] = None ): self._id = id_ self._type = job_type @@ -34,48 +40,48 @@ def __init__( self._started_at = started_at self._completed_at = completed_at self._finish_code = finish_code - self._notes = notes or [] + self._notes: List[str] = notes or [] self._mode = mode self._flow_run = flow_run @property - def id(self): + def id(self) -> str: return self._id @property - def type(self): + def type(self) -> str: return self._type @property - def progress(self): + def progress(self) -> str: return self._progress @property - def created_at(self): + def created_at(self) -> "datetime.datetime": return self._created_at @property - def started_at(self): + def started_at(self) -> Optional["datetime.datetime"]: return self._started_at @property - def completed_at(self): + def completed_at(self) -> Optional["datetime.datetime"]: return self._completed_at @property - def finish_code(self): + def finish_code(self) -> int: return self._finish_code @property - def notes(self): + def notes(self) -> List[str]: return self._notes @property - def mode(self): + def mode(self) -> Optional[str]: return self._mode @mode.setter - def mode(self, value): + def mode(self, value: str) -> None: # check for valid data here self._mode = value @@ -94,7 +100,7 @@ def __repr__(self): ) @classmethod - def from_response(cls, xml, ns): + def from_response(cls, xml, ns) -> List["JobItem"]: parsed_response = ET.fromstring(xml) all_tasks_xml = parsed_response.findall(".//t:job", namespaces=ns) @@ -136,23 +142,23 @@ def _parse_element(cls, element, ns): class BackgroundJobItem(object): class Status: - Pending = "Pending" - InProgress = "InProgress" - Success = "Success" - Failed = "Failed" - Cancelled = "Cancelled" + Pending: str = "Pending" + InProgress: str = "InProgress" + Success: str = "Success" + Failed: str = "Failed" + Cancelled: str = "Cancelled" def __init__( self, - id_, - created_at, - priority, - job_type, - status, - title=None, - subtitle=None, - started_at=None, - ended_at=None, + id_: str, + created_at: "datetime.datetime", + priority: int, + job_type: str, + status: str, + title: Optional[str] = None, + subtitle: Optional[str] = None, + started_at: Optional["datetime.datetime"] = None, + ended_at: Optional["datetime.datetime"] = None, ): self._id = id_ self._type = job_type @@ -165,49 +171,49 @@ def __init__( self._subtitle = subtitle @property - def id(self): + def id(self) -> str: return self._id @property - def name(self): + def name(self) -> Optional[str]: """For API consistency - all other resource endpoints have a name attribute which is used to display what they are. Alias title as name to allow consistent handling of resources in the list sample.""" return self._title @property - def status(self): + def status(self) -> str: return self._status @property - def type(self): + def type(self) -> str: return self._type @property - def created_at(self): + def created_at(self) -> "datetime.datetime": return self._created_at @property - def started_at(self): + def started_at(self) -> Optional["datetime.datetime"]: return self._started_at @property - def ended_at(self): + def ended_at(self) -> Optional["datetime.datetime"]: return self._ended_at @property - def title(self): + def title(self) -> Optional[str]: return self._title @property - def subtitle(self): + def subtitle(self) -> Optional[str]: return self._subtitle @property - def priority(self): + def priority(self) -> int: return self._priority @classmethod - def from_response(cls, xml, ns): + def from_response(cls, xml, ns) -> List["BackgroundJobItem"]: parsed_response = ET.fromstring(xml) all_tasks_xml = parsed_response.findall(".//t:backgroundJob", namespaces=ns) return [cls._parse_element(x, ns) for x in all_tasks_xml] diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index ef0dc6f6f..19642f3b1 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -12,14 +12,7 @@ import copy import uuid -from typing import ( - Dict, - List, - Optional, - Set, - TYPE_CHECKING, - Union -) +from typing import Dict, List, Optional, Set, TYPE_CHECKING, Union if TYPE_CHECKING: from .connection_item import ConnectionItem diff --git a/tableauserverclient/server/endpoint/jobs_endpoint.py b/tableauserverclient/server/endpoint/jobs_endpoint.py index 3acd6ba24..bb47141a1 100644 --- a/tableauserverclient/server/endpoint/jobs_endpoint.py +++ b/tableauserverclient/server/endpoint/jobs_endpoint.py @@ -8,6 +8,9 @@ logger = logging.getLogger("tableau.endpoint.jobs") +from typing import List, Optional, Tuple, TYPE_CHECKING, Union + + class Jobs(Endpoint): @property @@ -15,7 +18,9 @@ def baseurl(self): return "{0}/sites/{1}/jobs".format(self.parent_srv.baseurl, self.parent_srv.site_id) @api(version="2.6") - def get(self, job_id=None, req_options=None): + def get( + self, job_id: Optional[str] = None, req_options: Optional[RequestOptionsBase] = None + ) -> Tuple[List[BackgroundJobItem], PaginationItem]: # Backwards Compatibility fix until we rev the major version if job_id is not None and isinstance(job_id, str): import warnings @@ -32,21 +37,22 @@ def get(self, job_id=None, req_options=None): return jobs, pagination_item @api(version="3.1") - def cancel(self, job_id): - id_ = getattr(job_id, "id", job_id) - url = "{0}/{1}".format(self.baseurl, id_) + def cancel(self, job_id: Union[str, JobItem]): + if isinstance(job_id, JobItem): + job_id = job_id.id + assert isinstance(job_id, str) + url = "{0}/{1}".format(self.baseurl, job_id) return self.put_request(url) @api(version="2.6") - def get_by_id(self, job_id): + def get_by_id(self, job_id: str) -> JobItem: logger.info("Query for information about job " + job_id) url = "{0}/{1}".format(self.baseurl, job_id) server_response = self.get_request(url) new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0] return new_job - @api(version="2.6") - def wait_for_job(self, job_id, *, timeout=None): + def wait_for_job(self, job_id: Union[str, JobItem], *, timeout: Optional[float] = None) -> JobItem: if isinstance(job_id, JobItem): job_id = job_id.id assert isinstance(job_id, str) diff --git a/test/test_job.py b/test/test_job.py index 70bca996c..03ad9b535 100644 --- a/test/test_job.py +++ b/test/test_job.py @@ -17,7 +17,7 @@ class JobTests(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: self.server = TSC.Server('http://test') self.server.version = '3.1' @@ -27,7 +27,7 @@ def setUp(self): self.baseurl = self.server.jobs.baseurl - def test_get(self): + def test_get(self) -> None: response_xml = read_xml_asset(GET_XML) with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) @@ -46,7 +46,7 @@ def test_get(self): self.assertEqual(started_at, job.started_at) self.assertEqual(ended_at, job.ended_at) - def test_get_by_id(self): + def test_get_by_id(self) -> None: response_xml = read_xml_asset(GET_BY_ID_XML) job_id = '2eef4225-aa0c-41c4-8662-a76d89ed7336' with requests_mock.mock() as m: @@ -56,26 +56,26 @@ def test_get_by_id(self): self.assertEqual(job_id, job.id) self.assertListEqual(job.notes, ['Job detail notes']) - def test_get_before_signin(self): + def test_get_before_signin(self) -> None: self.server._auth_token = None self.assertRaises(TSC.NotSignedInError, self.server.jobs.get) - def test_cancel_id(self): + def test_cancel_id(self) -> None: with requests_mock.mock() as m: m.put(self.baseurl + '/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', status_code=204) self.server.jobs.cancel('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') - def test_cancel_item(self): + def test_cancel_item(self) -> None: created_at = datetime(2018, 5, 22, 13, 0, 29, tzinfo=utc) started_at = datetime(2018, 5, 22, 13, 0, 37, tzinfo=utc) job = TSC.JobItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'backgroundJob', - 0, created_at, started_at, None, 0) + "0", created_at, started_at, None, 0) with requests_mock.mock() as m: m.put(self.baseurl + '/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', status_code=204) self.server.jobs.cancel(job) - def test_wait_for_job_finished(self): + def test_wait_for_job_finished(self) -> None: # Waiting for an already finished job, directly returns that job's info response_xml = read_xml_asset(GET_BY_ID_XML) job_id = '2eef4225-aa0c-41c4-8662-a76d89ed7336' @@ -87,7 +87,7 @@ def test_wait_for_job_finished(self): self.assertListEqual(job.notes, ['Job detail notes']) - def test_wait_for_job_failed(self): + def test_wait_for_job_failed(self) -> None: # Waiting for a failed job raises an exception response_xml = read_xml_asset(GET_BY_ID_FAILED_XML) job_id = '77d5e57a-2517-479f-9a3c-a32025f2b64d' @@ -97,7 +97,7 @@ def test_wait_for_job_failed(self): self.server.jobs.wait_for_job(job_id) - def test_wait_for_job_timeout(self): + def test_wait_for_job_timeout(self) -> None: # Waiting for a job which doesn't terminate will throw an exception response_xml = read_xml_asset(GET_BY_ID_INPROGRESS_XML) job_id = '77d5e57a-2517-479f-9a3c-a32025f2b64d'