8000 Extract refresh support (#159) · ajbosco/server-client-python@1cc8f39 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1cc8f39

Browse files
Russell HayRussell Hay
authored andcommitted
Extract refresh support (tableau#159)
* Response to code reviews. Put all request options into 1 file. renamed sample to download_view_image.py. Comments clean up * Add api annotation to all current endpoints (tableau#125< 8000 /a>) Part two of tableau#125 This backfills all existing APIs with their minimum version. Checking this in early in release cycle for baketime. * initial implement of get all and get specific for Extract Refresh Tasks * fixing test failure in the schedule_item code * pep8 fixes * Download with extract_only and parameter checking (tableau#143) * Add a new decorator that checks parameters against the version. Used with the @api decorator. * Add extract_only flags to workbooks and data sources, protected behind v2.5 flag * Correct the path to extract refresh tasks * fixing missed pep8 failure * adding runNow to the interface * fixing pep8 issues * Add header documentation to the sample. * addressing tyler's feedback
1 parent c1c1c93 commit 1cc8f39

File tree

11 files changed

+211
-10
lines changed

11 files changed

+211
-10
lines changed

samples/refresh_tasks.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
####
2+
# This script demonstrates how to use the Tableau Server Client
3+
# to query extract refresh tasks and run them as needed.
4+
#
5+
# To run the script, you must have installed Python 2.7.X or 3.3 and later.
6+
####
7+
8+
import argparse
9+
import getpass
10+
import logging
11+
12+
import tableauserverclient as TSC
13+
14+
15+
def handle_run(server, args):
16+
task = server.tasks.get_by_id(args.id)
17+
print(server.tasks.run(task))
18+
19+
20+
def handle_list(server, _):
21+
tasks, pagination = server.tasks.get()
22+
for task in tasks:
23+
print("{}".format(task))
24+
25+
26+
def handle_info(server, args):
27+
task = server.tasks.get_by_id(args.id)
28+
print("{}".format(task))
29+
30+
31+
def main():
32+
parser = argparse.ArgumentParser(description='Get all of the refresh tasks available on a server')
33+
parser.add_argument('--server', '-s', required=True, help='server address')
34+
parser.add_argument('--username', '-u', required=True, help='username to sign into server')
35+
parser.add_argument('--site', '-S', default=None)
36+
parser.add_argument('-p', default=None)
37+
38+
parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error',
39+
help='desired logging level (set to error by default)')
40+
41+
subcommands = parser.add_subparsers()
42+
43+
list_arguments = subcommands.add_parser('list')
44+
list_arguments.set_defaults(func=handle_list)
45+
46+
run_arguments = subcommands.add_parser('run')
47+
run_arguments.add_argument('id', default=None)
48+
run_arguments.set_defaults(func=handle_run)
49+
50+
info_arguments = subcommands.add_parser('info')
51+
info_arguments.add_argument('id', default=None)
52+
info_arguments.set_defaults(func=handle_info)
53+
54+
args = parser.parse_args()
55+
56+
if args.p is None:
57+
password = getpass.getpass("Password: ")
58+
else:
59+
password = args.p
60+
61+
# Set logging level based on user input, or error by default
62+
logging_level = getattr(logging, args.logging_level.upper())
63+
logging.basicConfig(level=logging_level)
64+
65+
# SIGN IN
66+
tableau_auth = TSC.TableauAuth(args.username, password, args.site)
67+
server = TSC.Server(args.server)
68+
server.version = '2.6'
69+
with server.auth.sign_in(tableau_auth):
70+
args.func(server, args)
71+
72+
73+
if __name__ == '__main__':
74+
main()

tableauserverclient/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from .models import ConnectionCredentials, ConnectionItem, DatasourceItem,\
33
GroupItem, PaginationItem, ProjectItem, ScheduleItem, \
44
SiteItem, TableauAuth, UserItem, ViewItem, WorkbookItem, UnpopulatedPropertyError, \
5-
HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem
5+
HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem, TaskItem
66
from .server import RequestOptions, ImageRequestOptions, Filter, Sort, Server, ServerResponseError,\
77
MissingRequiredFieldError, NotSignedInError, Pager
88

tableauserverclient/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from .server_info_item import ServerInfoItem
1111
from .site_item import SiteItem
1212
from .tableau_auth import TableauAuth
13+
from .task_item import TaskItem
1314
from .user_item import UserItem
1415
from .view_item import ViewItem
1516
from .workbook_item import WorkbookItem

tableauserverclient/models/schedule_item.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ def __init__(self, name, priority, schedule_type, execution_order, interval_item
3333
self.priority = priority
3434
self.schedule_type = schedule_type
3535

36+
def __repr__(self):
37+
return "<Schedule#{_id} \"{_name}\" {interval_item}>".format(**self.__dict__)
38+
3639
@property
3740
def created_at(self):
3841
return self._created_at
@@ -106,7 +109,7 @@ def _parse_common_tags(self, schedule_xml):
106109
(_, name, _, _, updated_at, _, next_run_at, end_schedule_at, execution_order,
107110
priority, interval_item) = self._parse_element(schedule_xml)
108111

109-
self._set_values(id=None,
112+
self._set_values(id_=None,
110113
name=name,
111114
state=None,
112115
created_at=None,
@@ -120,10 +123,10 @@ def _parse_common_tags(self, schedule_xml):
120123

121124
return self
122125

123-
def _set_values(self, id, name, state, created_at, updated_at, schedule_type,
126+
def _set_values(self, id_, name, state, created_at, updated_at, schedule_type,
124127
next_run_at, end_schedule_at, execution_order, priority, interval_item):
125-
if id is not None:
126-
self._id = id
128+
if id_ is not None:
129+
self._id = id_
127130
if name:
128131
self._name = name
129132
if state:
@@ -147,16 +150,20 @@ def _set_values(self, id, name, state, created_at, updated_at, schedule_type,
147150

148151
@classmethod
149152
def from_response(cls, resp):
150-
all_schedule_items = []
151153
parsed_response = ET.fromstring(resp)
154+
return cls.from_element(parsed_response)
155+
156+
@classmethod
157+
def from_element(cls, parsed_response):
158+
all_schedule_items = []
152159
all_schedule_xml = parsed_response.findall('.//t:schedule', namespaces=NAMESPACE)
153160
for schedule_xml in all_schedule_xml:
154-
(id, name, state, created_at, updated_at, schedule_type, next_run_at,
161+
(id_, name, state, created_at, updated_at, schedule_type, next_run_at,
155162
end_schedule_at, execution_order, priority, interval_item) = cls._parse_element(schedule_xml)
156163

157164
schedule_item = cls(name, priority, schedule_type, execution_order, interval_item)
158165

159-
schedule_item._set_values(id=id,
166+
schedule_item._set_values(id_=id_,
160167
name=None,
161168
state=state,
162169
created_at=created_at,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import xml.etree.ElementTree as ET
2+
from .. import NAMESPACE
3+
from .schedule_item import ScheduleItem
4+
5+
6+
class TaskItem(object):
7+
def __init__(self, id_, task_type, priority, consecutive_failed_count=0, schedule_id=None):
8+
self.id = id_
9+
self.task_type = task_type
10+
self.priority = priority
11+
self.consecutive_failed_count = consecutive_failed_count
12+
self.schedule_id = schedule_id
13+
14+
def __repr__(self):
15+
return "<Task#{id} {task_type} pri({priority}) failed({consecutive_failed_count}) schedule_id({" \
< FEE1 code>16+
"schedule_id})>".format(**self.__dict__)
17+
18+
@classmethod
19+
def from_response(cls, xml):
20+
parsed_response = ET.fromstring(xml)
21+
all_tasks_xml = parsed_response.findall(
22+
'.//t:task/t:extractRefresh', namespaces=NAMESPACE)
23+
24+
all_tasks = (TaskItem._parse_element(x) for x in all_tasks_xml)
25+
26+
return list(all_tasks)
27+
28+
@classmethod
29+
def _parse_element(cls, element):
30+
schedule = None
31+
schedule_element = element.find('.//t:schedule', namespaces=NAMESPACE)
32+
if schedule_element is not None:
33+
schedule = schedule_element.get('id', None)
34+
task_type = element.get('type', None)
35+
priority = int(element.get('priority', -1))
36+
consecutive_failed_count = int(element.get('consecutiveFailedCount', 0))
37+
id_ = element.get('id', None)
38+
return cls(id_, task_type, priority, consecutive_failed_count, schedule)

tableauserverclient/server/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from .sort import Sort
55
from .. import ConnectionItem, DatasourceItem,\
66
GroupItem, PaginationItem, ProjectItem, ScheduleItem, SiteItem, TableauAuth,\
7-
UserItem, ViewItem, WorkbookItem, NAMESPACE
7+
UserItem, ViewItem, WorkbookItem, TaskItem, NAMESPACE
88
from .endpoint import Auth, Datasources, Endpoint, Groups, Projects, Schedules, \
99
Sites, Users, Views, Workbooks, ServerResponseError, MissingRequiredFieldError
1010
from .server import Server

tableauserverclient/server/endpoint/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .schedules_endpoint import Schedules
88
from .server_info_endpoint import ServerInfo
99
from .sites_endpoint import Sites
10+
from .tasks_endpoint import Tasks
1011
from .users_endpoint import Users
1112
from .views_endpoint import Views
1213
from .workbooks_endpoint import Workbooks
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from .endpoint import Endpoint
2+
from .exceptions import MissingRequiredFieldError
3+
from .. import TaskItem, PaginationItem, RequestFactory
4+
import logging
5+
6+
logger = logging.getLogger('tableau.endpoint.tasks')
7+
8+
9+
class Tasks(Endpoint):
10+
@property
11+
def baseurl(self):
12+
return "{0}/sites/{1}/tasks/extractRefreshes".format(self.parent_srv.baseurl,
13+
self.parent_srv.site_id)
14+
15+
def get(self, req_options=None):
16+
logger.info('Querying all tasks for the site')
17+
url = self.baseurl
18+
server_response = self.get_request(url, req_options)
19+
20+
pagination_item = PaginationItem.from_response(server_response.content)
21+
all_extract_tasks = TaskItem.from_response(server_response.content)
22+
return all_extract_tasks, pagination_item
23+
24+
def get_by_id(self, task_id):
25+
if not task_id:
26+
error = "No Task ID provided"
27+
raise ValueError(error)
28+
logger.info("Querying a single task by id ({})".format(task_id))
29+
url = "{}/{}".format(self.baseurl, task_id)
30+
server_response = self.get_request(url)
31+
return TaskItem.from_response(server_response.content)[0]
32+
33+
def run(self, task_item):
34+
if not task_item.id:
35+< 10000 div class="diff-text-inner"> error = "User item missing ID."
36+
raise MissingRequiredFieldError(error)
37+
38+
url = "{0}/{1}/runNow".format(self.baseurl, task_item.id)
39+
print(url)
40+
run_req = RequestFactory.Task.run_req(task_item)
41+
server_response = self.post_request(url, run_req)
42+
return server_response.content

tableauserverclient/server/request_factory.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from ..datetime_helpers import format_datetime
22
import xml.etree.ElementTree as ET
3+
from functools import wraps
34

45
from requests.packages.urllib3.fields import RequestField
56
from requests.packages.urllib3.filepost import encode_multipart_formdata
@@ -16,6 +17,14 @@ def _add_multipart(parts):
1617
return xml_request, content_type
1718

1819

20+
def _tsrequest_wrapped(func):
21+
def wrapper(self, *args, **kwargs):
22+
xml_request = ET.Element('tsRequest')
23+
func(xml_request, *args, **kwargs)
24+
return ET.tostring(xml_request)
25+
return wrapper
26+
27+
1928
class AuthRequest(object):
2029
def signin_req(self, auth_item):
2130
xml_request = ET.Element('tsRequest')
@@ -331,6 +340,13 @@ def update_req(self, connection_item):
331340
return ET.tostring(xml_request)
332341

333342

343+
class TaskRequest(object):
344+
@_tsrequest_wrapped
345+
def run_req(xml_request, task_item):
346+
# Send an empty tsRequest
347+
pass
348+
349+
334350
class RequestFactory(object):
335351
Auth = AuthRequest()
336352
Datasource = DatasourceRequest()
@@ -341,6 +357,7 @@ class RequestFactory(object):
341357
Schedule = ScheduleRequest()
342358
Site = SiteRequest()
343359
Tag = TagRequest()
360+
Task = TaskRequest()
344361
User = UserRequest()
345362
Workbook = WorkbookRequest()
346363
WorkbookConnection = WorkbookConnection()

tableauserverclient/server/request_options.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,23 @@ def apply_query_params(self, url):
7575
params.append('resolution={0}'.format(self.imageresolution))
7676

7777
return "{0}?{1}".format(url, '&'.join(params))
78+
79+
80+
class ImageRequestOptions(RequestOptionsBase):
81+
# if 'high' isn't specified, the REST API endpoint returns an image with standard resolution
82+
class Resolution:
83+
High = 'high'
84+
85+
def __init__(self, imageresolution=None):
86+
self.imageresolution = imageresolution
87+
88+
def image_resolution(self, imageresolution):
89+
self.imageresolution = imageresolution
90+
return self
91+
92+
def apply_query_params(self, url):
93+
params = []
94+
if self.image_resolution:
95+
params.append('resolution={0}'.format(self.imageresolution))
96+
97+
return "{0}?{1}".format(url, '&'.join(params))

0 commit comments

Comments
 (0)
0