8000 Extract refresh support by graysonarts · Pull Request #159 · tableau/server-client-python · GitHub
[go: up one dir, main page]

Skip to content

Extract refresh support #159

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 12 commits into from
Mar 24, 2017
74 changes: 74 additions & 0 deletions samples/refresh_tasks.py
8000
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 1 addition & 1 deletion tableauserverclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from .models import ConnectionCredentials, ConnectionItem, DatasourceItem,\
GroupItem, PaginationItem, ProjectItem, ScheduleItem, \
SiteItem, TableauAuth, UserItem, ViewItem, WorkbookItem, UnpopulatedPropertyError, \
HourlyInterval, DailyInterval, W 10000 eeklyInterval, MonthlyInterval, IntervalItem
HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem, TaskItem
from .server import RequestOptions, ImageRequestOptions, Filter, Sort, Server, ServerResponseError,\
MissingRequiredFieldError, NotSignedInError, Pager

Expand Down
1 change: 1 addition & 0 deletions tableauserverclient/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
21 changes: 14 additions & 7 deletions tableauserverclient/models/schedule_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<Schedule#{_id} \"{_name}\" {interval_item}>".format(**self.__dict__)

@property
def created_at(self):
return self._created_at
Expand Down Expand Up @@ -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,
Expand All @@ -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:
Expand All @@ -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,
Expand Down
38 changes: 38 additions & 0 deletions tableauserverclient/models/task_item.py
Original file line number Diff line number Diff line change
@@ -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 "<Task#{id} {task_type} pri({priority}) failed({consecutive_failed_count}) schedule_id({" \
"schedule_id})>".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)
2 changes: 1 addition & 1 deletion tableauserverclient/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions tableauserverclient/server/endpoint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
42 changes: 42 additions & 0 deletions tableauserverclient/server/endpoint/tasks_endpoint.py
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions tableauserverclient/server/request_factory.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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')
Expand Down Expand Up @@ -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()
Expand All @@ -341,6 +357,7 @@ class RequestFactory(object):
Schedule = ScheduleRequest()
Site = SiteRequest()
Tag = TagRequest()
Task = TaskRequest()
User = UserRequest()
Workbook = WorkbookRequest()
WorkbookConnection = WorkbookConnection()
20 changes: 20 additions & 0 deletions tableauserverclient/server/request_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
3 changes: 2 additions & 1 deletion tableauserverclient/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
0