diff --git a/samples/refresh_tasks.py b/samples/refresh_tasks.py new file mode 100644 index 000000000..214a2131b --- /dev/null +++ b/samples/refresh_tasks.py @@ -0,0 +1,74 @@ +#### +# This script demonstrates how to use the Tableau Server Client +# to query extract refresh tasks and run them as needed. +# +# To run the script, you must have installed Python 2.7.X or 3.3 and later. +#### + +import argparse +import getpass +import logging + +import tableauserverclient as TSC + + +def handle_run(server, args): + task = server.tasks.get_by_id(args.id) + print(server.tasks.run(task)) + + +def handle_list(server, _): + tasks, pagination = server.tasks.get() + for task in tasks: + print("{}".format(task)) + + +def handle_info(server, args): + task = server.tasks.get_by_id(args.id) + print("{}".format(task)) + + +def main(): + parser = argparse.ArgumentParser(description='Get all of the refresh tasks available on a server') + parser.add_argument('--server', '-s', required=True, help='server address') + parser.add_argument('--username', '-u', required=True, help='username to sign into server') + parser.add_argument('--site', '-S', default=None) + parser.add_argument('-p', default=None) + + parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', + help='desired logging level (set to error by default)') + + subcommands = parser.add_subparsers() + + list_arguments = subcommands.add_parser('list') + list_arguments.set_defaults(func=handle_list) + + run_arguments = subcommands.add_parser('run') + run_arguments.add_argument('id', default=None) + run_arguments.set_defaults(func=handle_run) + + info_arguments = subcommands.add_parser('info') + info_arguments.add_argument('id', default=None) + info_arguments.set_defaults(func=handle_info) + + args = parser.parse_args() + + if args.p is None: + password = getpass.getpass("Password: ") + else: + password = args.p + + # Set logging level based on user input, or error by default + logging_level = getattr(logging, args.logging_level.upper()) + logging.basicConfig(level=logging_level) + + # SIGN IN + tableau_auth = TSC.TableauAuth(args.username, password, args.site) + server = TSC.Server(args.server) + server.version = '2.6' + with server.auth.sign_in(tableau_auth): + args.func(server, args) + + +if __name__ == '__main__': + main() diff --git a/tableauserverclient/__init__.py b/tableauserverclient/__init__.py index acf639c56..47d14dbfb 100644 --- a/tableauserverclient/__init__.py +++ b/tableauserverclient/__init__.py @@ -2,7 +2,7 @@ from .models import ConnectionCredentials, ConnectionItem, DatasourceItem,\ GroupItem, PaginationItem, ProjectItem, ScheduleItem, \ SiteItem, TableauAuth, UserItem, ViewItem, WorkbookItem, UnpopulatedPropertyError, \ - HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem + HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem, TaskItem from .server import RequestOptions, ImageRequestOptions, Filter, Sort, Server, ServerResponseError,\ MissingRequiredFieldError, NotSignedInError, Pager diff --git a/tableauserverclient/models/__init__.py b/tableauserverclient/models/__init__.py index b248ea399..cb26f4eaa 100644 --- a/tableauserverclient/models/__init__.py +++ b/tableauserverclient/models/__init__.py @@ -10,6 +10,7 @@ from .server_info_item import ServerInfoItem from .site_item import SiteItem from .tableau_auth import TableauAuth +from .task_item import TaskItem from .user_item import UserItem from .view_item import ViewItem from .workbook_item import WorkbookItem diff --git a/tableauserverclient/models/schedule_item.py b/tableauserverclient/models/schedule_item.py index 84b070044..f84f726e4 100644 --- a/tableauserverclient/models/schedule_item.py +++ b/tableauserverclient/models/schedule_item.py @@ -33,6 +33,9 @@ def __init__(self, name, priority, schedule_type, execution_order, interval_item self.priority = priority self.schedule_type = schedule_type + def __repr__(self): + return "".format(**self.__dict__) + @property def created_at(self): return self._created_at @@ -106,7 +109,7 @@ def _parse_common_tags(self, schedule_xml): (_, name, _, _, updated_at, _, next_run_at, end_schedule_at, execution_order, priority, interval_item) = self._parse_element(schedule_xml) - self._set_values(id=None, + self._set_values(id_=None, name=name, state=None, created_at=None, @@ -120,10 +123,10 @@ def _parse_common_tags(self, schedule_xml): return self - def _set_values(self, id, name, state, created_at, updated_at, schedule_type, + def _set_values(self, id_, name, state, created_at, updated_at, schedule_type, next_run_at, end_schedule_at, execution_order, priority, interval_item): - if id is not None: - self._id = id + if id_ is not None: + self._id = id_ if name: self._name = name if state: @@ -147,16 +150,20 @@ def _set_values(self, id, name, state, created_at, updated_at, schedule_type, @classmethod def from_response(cls, resp): - all_schedule_items = [] parsed_response = ET.fromstring(resp) + return cls.from_element(parsed_response) + + @classmethod + def from_element(cls, parsed_response): + all_schedule_items = [] all_schedule_xml = parsed_response.findall('.//t:schedule', namespaces=NAMESPACE) for schedule_xml in all_schedule_xml: - (id, name, state, created_at, updated_at, schedule_type, next_run_at, + (id_, name, state, created_at, updated_at, schedule_type, next_run_at, end_schedule_at, execution_order, priority, interval_item) = cls._parse_element(schedule_xml) schedule_item = cls(name, priority, schedule_type, execution_order, interval_item) - schedule_item._set_values(id=id, + schedule_item._set_values(id_=id_, name=None, state=state, created_at=created_at, diff --git a/tableauserverclient/models/task_item.py b/tableauserverclient/models/task_item.py new file mode 100644 index 000000000..b87c0eaa6 --- /dev/null +++ b/tableauserverclient/models/task_item.py @@ -0,0 +1,38 @@ +import xml.etree.ElementTree as ET +from .. import NAMESPACE +from .schedule_item import ScheduleItem + + +class TaskItem(object): + def __init__(self, id_, task_type, priority, consecutive_failed_count=0, schedule_id=None): + self.id = id_ + self.task_type = task_type + self.priority = priority + self.consecutive_failed_count = consecutive_failed_count + self.schedule_id = schedule_id + + def __repr__(self): + return "".format(**self.__dict__) + + @classmethod + def from_response(cls, xml): + parsed_response = ET.fromstring(xml) + all_tasks_xml = parsed_response.findall( + './/t:task/t:extractRefresh', namespaces=NAMESPACE) + + all_tasks = (TaskItem._parse_element(x) for x in all_tasks_xml) + + return list(all_tasks) + + @classmethod + def _parse_element(cls, element): + schedule = None + schedule_element = element.find('.//t:schedule', namespaces=NAMESPACE) + if schedule_element is not None: + schedule = schedule_element.get('id', None) + task_type = element.get('type', None) + priority = int(element.get('priority', -1)) + consecutive_failed_count = int(element.get('consecutiveFailedCount', 0)) + id_ = element.get('id', None) + return cls(id_, task_type, priority, consecutive_failed_count, schedule) diff --git a/tableauserverclient/server/__init__.py b/tableauserverclient/server/__init__.py index 504d5b5b8..ab386c0ca 100644 --- a/tableauserverclient/server/__init__.py +++ b/tableauserverclient/server/__init__.py @@ -4,7 +4,7 @@ from .sort import Sort from .. import ConnectionItem, DatasourceItem,\ GroupItem, PaginationItem, ProjectItem, ScheduleItem, SiteItem, TableauAuth,\ - UserItem, ViewItem, WorkbookItem, NAMESPACE + UserItem, ViewItem, WorkbookItem, TaskItem, NAMESPACE from .endpoint import Auth, Datasources, Endpoint, Groups, Projects, Schedules, \ Sites, Users, Views, Workbooks, ServerResponseError, MissingRequiredFieldError from .server import Server diff --git a/tableauserverclient/server/endpoint/__init__.py b/tableauserverclient/server/endpoint/__init__.py index d9dca0f42..323934f12 100644 --- a/tableauserverclient/server/endpoint/__init__.py +++ b/tableauserverclient/server/endpoint/__init__.py @@ -7,6 +7,7 @@ from .schedules_endpoint import Schedules from .server_info_endpoint import ServerInfo 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 diff --git a/tableauserverclient/server/endpoint/tasks_endpoint.py b/tableauserverclient/server/endpoint/tasks_endpoint.py new file mode 100644 index 000000000..5d884ab4b --- /dev/null +++ b/tableauserverclient/server/endpoint/tasks_endpoint.py @@ -0,0 +1,42 @@ +from .endpoint import Endpoint +from .exceptions import MissingRequiredFieldError +from .. import TaskItem, PaginationItem, RequestFactory +import logging + +logger = logging.getLogger('tableau.endpoint.tasks') + + +class Tasks(Endpoint): + @property + def baseurl(self): + return "{0}/sites/{1}/tasks/extractRefreshes".format(self.parent_srv.baseurl, + self.parent_srv.site_id) + + def get(self, req_options=None): + logger.info('Querying all tasks for the site') + url = self.baseurl + server_response = self.get_request(url, req_options) + + pagination_item = PaginationItem.from_response(server_response.content) + all_extract_tasks = TaskItem.from_response(server_response.content) + return all_extract_tasks, pagination_item + + def get_by_id(self, task_id): + if not task_id: + error = "No Task ID provided" + raise ValueError(error) + logger.info("Querying a single task by id ({})".format(task_id)) + url = "{}/{}".format(self.baseurl, task_id) + server_response = self.get_request(url) + return TaskItem.from_response(server_response.content)[0] + + def run(self, task_item): + if not task_item.id: + error = "User item missing ID." + raise MissingRequiredFieldError(error) + + url = "{0}/{1}/runNow".format(self.baseurl, task_item.id) + print(url) + run_req = RequestFactory.Task.run_req(task_item) + server_response = self.post_request(url, run_req) + return server_response.content diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 25d89ce15..439f517cb 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -1,5 +1,6 @@ from ..datetime_helpers import format_datetime import xml.etree.ElementTree as ET +from functools import wraps from requests.packages.urllib3.fields import RequestField from requests.packages.urllib3.filepost import encode_multipart_formdata @@ -16,6 +17,14 @@ def _add_multipart(parts): return xml_request, content_type +def _tsrequest_wrapped(func): + def wrapper(self, *args, **kwargs): + xml_request = ET.Element('tsRequest') + func(xml_request, *args, **kwargs) + return ET.tostring(xml_request) + return wrapper + + class AuthRequest(object): def signin_req(self, auth_item): xml_request = ET.Element('tsRequest') @@ -331,6 +340,13 @@ def update_req(self, connection_item): return ET.tostring(xml_request) +class TaskRequest(object): + @_tsrequest_wrapped + def run_req(xml_request, task_item): + # Send an empty tsRequest + pass + + class RequestFactory(object): Auth = AuthRequest() Datasource = DatasourceRequest() @@ -341,6 +357,7 @@ class RequestFactory(object): Schedule = ScheduleRequest() Site = SiteRequest() Tag = TagRequest() + Task = TaskRequest() User = UserRequest() Workbook = WorkbookRequest() WorkbookConnection = WorkbookConnection() diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index dade12205..acd82c68e 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -75,3 +75,23 @@ def apply_query_params(self, url): params.append('resolution={0}'.format(self.imageresolution)) return "{0}?{1}".format(url, '&'.join(params)) + + +class ImageRequestOptions(RequestOptionsBase): + # if 'high' isn't specified, the REST API endpoint returns an image with standard resolution + class Resolution: + High = 'high' + + def __init__(self, imageresolution=None): + self.imageresolution = imageresolution + + def image_resolution(self, imageresolution): + self.imageresolution = imageresolution + return self + + def apply_query_params(self, url): + params = [] + if self.image_resolution: + params.append('resolution={0}'.format(self.imageresolution)) + + return "{0}?{1}".format(url, '&'.join(params)) diff --git a/tableauserverclient/server/server.py b/tableauserverclient/server/server.py index a998bd76b..339262fba 100644 --- a/tableauserverclient/server/server.py +++ b/tableauserverclient/server/server.py @@ -2,7 +2,7 @@ from .exceptions import NotSignedInError from .endpoint import Sites, Views, Users, Groups, Workbooks, Datasources, Projects, Auth, \ - Schedules, ServerInfo, ServerInfoEndpointNotFoundError + Schedules, ServerInfo, Tasks, ServerInfoEndpointNotFoundError import requests @@ -40,6 +40,7 @@ def __init__(self, server_address): self.projects = Projects(self) self.schedules = Schedules(self) self.server_info = ServerInfo(self) + self.tasks = Tasks(self) def add_http_options(self, options_dict): self._http_options.update(options_dict)