Skip to content
Open
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
2 changes: 1 addition & 1 deletion tableauserverclient/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .namespace import NAMESPACE
from .models import ConnectionItem, DatasourceItem,\
from .models import ConnectionItem, DatasourceItem, ExtractRefreshTaskItem,\
GroupItem, PaginationItem, ProjectItem, ScheduleItem, \
SiteItem, TableauAuth, UserItem, ViewItem, WorkbookItem, UnpopulatedPropertyError, \
HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem
Expand Down
1 change: 1 addition & 0 deletions tableauserverclient/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .connection_item import ConnectionItem
from .datasource_item import DatasourceItem
from .exceptions import UnpopulatedPropertyError
from .extract_refresh_task_item import ExtractRefreshTaskItem
from .group_item import GroupItem
from .interval_item import IntervalItem, DailyInterval, WeeklyInterval, MonthlyInterval, HourlyInterval
from .pagination_item import PaginationItem
Expand Down
3 changes: 3 additions & 0 deletions tableauserverclient/models/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
class UnpopulatedPropertyError(Exception):
pass

class ResponseBodyError(Exception):
pass
93 changes: 93 additions & 0 deletions tableauserverclient/models/extract_refresh_task_item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import xml.etree.ElementTree as ET
from .. import NAMESPACE
from item_types import ItemTypes
from task_item import TaskItem
from exceptions import ResponseBodyError
from property_decorators import property_is_int, property_is_enum

class ExtractRefreshTaskItem(TaskItem):

class RefreshType:
FullRefresh = "FullRefresh"
IncrementalRefresh = "IncrementalRefresh"

# A user should never create this item. It should only ever be created by a return call from
# a REST API. Possibly we could be more vigilant in hiding the constructor
def __init__(self, id, schedule_id, priority, refresh_type, item_type, item_id):
super(ExtractRefreshTaskItem, self).__init__(id, schedule_id)
self.priority = priority
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The 'type' parameter in init is assigned to self.type but never used elsewhere in the class

This unused assignment could lead to confusion and potential bugs. Consider removing it if not needed.

self.type = type
self.refresh_type = refresh_type
self.item_type = item_type
self.item_id = item_id

@property
def priority(self):
return self._priority

@priority.setter
@property_is_int(range=(1, 100))
def priority(self, value):
self._priority = value

@property
def refresh_type(self):
return self._refresh_type

@refresh_type.setter
@property_is_enum(RefreshType)
def refresh_type(self, value):
self._refresh_type = value

@property
def item_type(self):
return self._item_type

@item_type.setter
def item_type(self, value):
# Check that it is Datasource or Workbook
if not (value in [ItemTypes.Datasource, ItemTypes.Workbook]):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Simplify logical expression using De Morgan identities (de-morgan)

Suggested change
if not (value in [ItemTypes.Datasource, ItemTypes.Workbook]):
if value not in [ItemTypes.Datasource, ItemTypes.Workbook]:

error = "Invalid value: {0}. item_type must be either ItemTypes.Datasource or ItemTypes.Workbook".format(value)
raise ValueError(error)
self._item_type = value

@property
def item_id(self):
return self._item_id

@item_id.setter
def item_id(self, value):
self._item_id = value


@classmethod
def from_response(cls, resp, schedule_id):
tasks_items = list()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Replace list() with [] (list-literal)

Suggested change
tasks_items = list()
tasks_items = []


ExplanationThe most concise and Pythonic way to create a list is to use the [] notation.

This fits in with the way we create lists with elements, saving a bit of mental
energy that might be taken up with thinking about two different ways of creating
lists.

x = ["first", "second"]

Doing things this way has the added advantage of being a nice little performance
improvement.

Here are the timings before and after the change:

$ python3 -m timeit "x = list()"
5000000 loops, best of 5: 63.3 nsec per loop
$ python3 -m timeit "x = []"
20000000 loops, best of 5: 15.8 nsec per loop

Similar reasoning and performance results hold for replacing dict() with {}.

parsed_response = ET.fromstring(resp)
extract_tags = parsed_response.findall('.//t:extract', namespaces=NAMESPACE)
for extract_tag in extract_tags:
(id, priority, refresh_type, item_type, item_id) = cls._parse_element(extract_tag)

task = cls(id, schedule_id, priority, refresh_type, item_type, item_id)
tasks_items.append(task)
return tasks_items

@staticmethod
def _parse_element(extract_tag):
id = extract_tag.get('id')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Don't assign to builtin variable id (avoid-builtin-shadow)


ExplanationPython has a number of builtin variables: functions and constants that
form a part of the language, such as list, getattr, and type
(See https://docs.python.org/3/library/functions.html).
It is valid, in the language, to re-bind such variables:

list = [1, 2, 3]

However, this is considered poor practice.

  • It will confuse other developers.
  • It will confuse syntax highlighters and linters.
  • It means you can no longer use that builtin for its original purpose.

How can you solve this?

Rename the variable something more specific, such as integers.
In a pinch, my_list and similar names are colloquially-recognized
placeholders.

priority = int(extract_tag.get('priority'))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Missing error handling for malformed XML attributes in _parse_element

Add error handling for cases where priority is not a valid integer or other attributes are missing/malformed.

refresh_type = extract_tag.get('type')

datasource_tag = extract_tag.find('.//t:datasource', namespaces=NAMESPACE)
workbook_tag = extract_tag.find('.//t:workbook', namespaces=NAMESPACE)
if datasource_tag is not None:
item_type = ItemTypes.Datasource
item_id = datasource_tag.get('id')
elif workbook_tag is not None:
item_type = ItemTypes.Workbook
item_id = workbook_tag.get('id')
else:
error = "Missing workbook / datasource element for refresh task"
raise ResponseBodyError(error)

return id, priority, refresh_type, item_type, item_id
3 changes: 3 additions & 0 deletions tableauserverclient/models/item_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class ItemTypes:
Datasource = 'Datasource'
Workbook = 'Workbook'
13 changes: 13 additions & 0 deletions tableauserverclient/models/task_item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class TaskItem(object):

def __init__(self, id, schedule_id):
self._id = id
self._schedule_id = schedule_id

@property
def id(self):
return self._id

@property
def schedule_id(self):
return self._schedule_id
4 changes: 2 additions & 2 deletions tableauserverclient/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
from .request_options import RequestOptions
from .filter import Filter
from .sort import Sort
from .. import ConnectionItem, DatasourceItem,\
from .. import ConnectionItem, DatasourceItem, ExtractRefreshTaskItem,\
GroupItem, PaginationItem, ProjectItem, ScheduleItem, SiteItem, TableauAuth,\
UserItem, ViewItem, WorkbookItem, NAMESPACE
from .endpoint import Auth, Datasources, Endpoint, Groups, Projects, Schedules, \
Sites, Users, Views, Workbooks, ServerResponseError, MissingRequiredFieldError
Sites, Tasks, Users, Views, Workbooks, ServerResponseError, MissingRequiredFieldError
from .server import Server
from .exceptions import NotSignedInError
1 change: 1 addition & 0 deletions tableauserverclient/server/endpoint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .projects_endpoint import Projects
from .schedules_endpoint import Schedules
from .sites_endpoint import Sites
from .tasks_endpoint import Tasks
from .users_endpoint import Users
from .views_endpoint import Views
from .workbooks_endpoint import Workbooks
21 changes: 21 additions & 0 deletions tableauserverclient/server/endpoint/extract_refreshes_endpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from .endpoint import Endpoint
from .. import PaginationItem, ExtractRefreshTaskItem
import logging

logger = logging.getLogger('tableau.endpoint.tasks')

class ExtractRefreshes(Endpoint):
def __init__(self, parent_srv):
super(ExtractRefreshes, self).__init__()
self.parent_srv = parent_srv

@property
def baseurl(self):
return "{0}/sites/{1}".format(self.parent_srv.baseurl, self.parent_srv.site_id)

def get_for_schedule(self, schedule_id, req_options=None):
url = "{0}/schedules/{1}/extracts".format(self.baseurl, schedule_id)
server_response = self.get_request(url, req_options)
pagination_item = PaginationItem.from_response(server_response.content)
tasks = ExtractRefreshTaskItem.from_response(server_response.content, schedule_id)
return tasks, pagination_item
11 changes: 11 additions & 0 deletions tableauserverclient/server/endpoint/tasks_endpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from .endpoint import Endpoint
from extract_refreshes_endpoint import ExtractRefreshes
import logging

logger = logging.getLogger('tableau.endpoint.tasks')

class Tasks(Endpoint):
def __init__(self, parent_srv):
super(Tasks, self).__init__()
self.parent_srv = parent_srv
self.extract_refreshes = ExtractRefreshes(parent_srv)
3 changes: 2 additions & 1 deletion tableauserverclient/server/server.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .exceptions import NotSignedInError
from .endpoint import Sites, Views, Users, Groups, Workbooks, Datasources, Projects, Auth, Schedules
from .endpoint import Sites, Views, Users, Groups, Workbooks, Datasources, Projects, Auth, Schedules, Tasks
import requests


Expand Down Expand Up @@ -27,6 +27,7 @@ def __init__(self, server_address):
self.datasources = Datasources(self)
self.projects = Projects(self)
self.schedules = Schedules(self)
self.tasks = Tasks(self)

def add_http_options(self, options_dict):
self._http_options.update(options_dict)
Expand Down