8000 Type hint Job by jorwoods · Pull Request #939 · tableau/server-client-python · GitHub
[go: up one dir, main page]

Skip to content

Type hint Job #939

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/slack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
106 changes: 56 additions & 50 deletions tableauserverclient/models/job_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,35 @@
from ..datetime_helpers import parse_datetime


from typing import List, Optional, TYPE_CHECKING

if TYPE_CHECKING:
import datetime


class JobItem(object):
class FinishCode:
"""
Status codes as documented on
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
Expand All @@ -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

Expand All @@ -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)

Expand Down Expand Up @@ -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
Expand All @@ -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]
Expand Down
9 changes: 1 addition & 8 deletions tableauserverclient/models/workbook_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 13 additions & 7 deletions tableauserverclient/server/endpoint/jobs_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,19 @@

logger = logging.getLogger("tableau.endpoint.jobs")

from typing import List, Optional, Tuple, TYPE_CHECKING, Union



class Jobs(Endpoint):
@property
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
Expand All @@ -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)
Expand Down
20 changes: 10 additions & 10 deletions test/test_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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)
Expand All @@ -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:
Expand All @@ -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'
Expand All @@ -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'
Expand All @@ -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'
Expand Down
0