Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b4c8667
added support for new image api endpoint. added sample demonstrating …
jz-huang Dec 14, 2016
82163c9
added new sample to docs
jz-huang Dec 14, 2016
b962116
Response to code reviews. Put all request options into 1 file. rename…
jz-huang Dec 14, 2016
feba8c8
update sample name in docs
jz-huang Dec 14, 2016
2da7bef
pep8 compliance fix
jz-huang Dec 14, 2016
301a1cd
Test request construction (#91)
t8y8 Nov 2, 2016
069dac9
Initial implementation to address #102 and provide datetime objects
Nov 16, 2016
06f5119
Remove setters and move to doing the conversion during parsing
Nov 17, 2016
75a3623
Test request construction (#91)
t8y8 Nov 2, 2016
cc8d544
Initial implementation to address #102 and provide datetime objects
Nov 16, 2016
ab82968
Fix pep8 failures
Nov 16, 2016
590f130
Remove setters and move to doing the conversion during parsing
Nov 17, 2016
00e3015
Added datasource tagging functionality along with unit test and added…
lbrendanl Feb 16, 2017
391fdec
Added view tagging functionality along with unit test and added sampl…
lbrendanl Feb 17, 2017
5cf4c19
Removed accidently duplicate methods in ViewItem
lbrendanl Feb 17, 2017
8c5ea2a
Refeactored code so that views/datasource/workbooks have a shared bas…
lbrendanl Feb 17, 2017
71e600a
Dropped extraneous __init__ change.
lbrendanl Feb 17, 2017
af7acaa
Removed print statemets, nit cleanups.
lbrendanl Feb 17, 2017
16469a9
Fixed pycodestyle errors.
lbrendanl Feb 17, 2017
31df99b
Refactored inheritance for tagged resources to composition pattern.
lbrendanl Feb 17, 2017
c3d9017
Pythony naming.
lbrendanl Feb 17, 2017
e8f4b0a
Added error handling to the resource tagger to display message to use…
lbrendanl Feb 17, 2017
f1987e1
Linter fix.
lbrendanl Feb 17, 2017
bd142f6
Responding to CR feedback:
lbrendanl Feb 21, 2017
16e2c06
Removed unused import.
lbrendanl Feb 22, 2017
f7bbc63
Add api annotation to all current endpoints (#125)
t8y8 Jan 24, 2017
c1c1c93
Download with extract_only and parameter checking (#143)
t8y8 Feb 13, 2017
1cc8f39
Extract refresh support (#159)
Mar 24, 2017
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: 2 additions & 0 deletions docs/docs/samples.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ The following list describes the samples available in the repository:

* `explore_workbook.py`. Queries workbooks, selects a workbook, populates the connections and views for a workbook, then updates the workbook.

* `download_view_image.py`. Queries for view based on name specified in filter, populates the image and saves the image to specified file path.

* `move_workbook_projects.py`. Updates the properties of a workbook to move the workbook from one project to another.

* `move_workbook_sites.py`. Downloads a workbook, stores it in-memory, and uploads it to another site.
Expand Down
68 changes: 68 additions & 0 deletions samples/download_view_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
####
# This script demonstrates how to use the Tableau Server Client
# to download a high resolution image of a view from Tableau Server.
#
# For more information, refer to the documentations on 'Query View Image'
# (https://onlinehelp.tableau.com/current/api/rest_api/en-us/help.htm)
#
# 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 main():

parser = argparse.ArgumentParser(description='Query View Image From Server')
parser.add_argument('--server', '-s', required=True, help='server address')
parser.add_argument('--site-id', '-si', required=False,
help='content url for site the view is on')
parser.add_argument('--username', '-u', required=True, help='username to sign into server')
parser.add_argument('--view-name', '-v', required=True,
help='name of view to download an image of')
parser.add_argument('--filepath', '-f', required=True, help='filepath to save the image returned')
parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error',
help='desired logging level (set to error by default)')

args = parser.parse_args()

password = getpass.getpass("Password: ")

# Set logging level based on user input, or error by default
logging_level = getattr(logging, args.logging_level.upper())
logging.basicConfig(level=logging_level)

# Step 1: Sign in to server.
site_id = args.site_id
if not site_id:
site_id = ""
tableau_auth = TSC.TableauAuth(args.username, password, site_id=site_id)
server = TSC.Server(args.server)
# The new endpoint was introduced in Version 2.5
server.version = 2.5

with server.auth.sign_in(tableau_auth):
# Step 2: Query for the view that we want an image of
req_option = TSC.RequestOptions()
req_option.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name,
TSC.RequestOptions.Operator.Equals, args.view_name))
all_views, pagination_item = server.views.get(req_option)
if not all_views:
raise LookupError("View with the specified name was not found.")
view_item = all_views[0]

# Step 3: Query the image endpoint and save the image to the specified location
image_req_option = TSC.ImageRequestOptions(imageresolution=TSC.ImageRequestOptions.Resolution.High)
server.views.populate_image(view_item, image_req_option)

with open(args.filepath, "wb") as image_file:
image_file.write(view_item.image)

print("View image saved to {0}".format(args.filepath))

if __name__ == '__main__':
main()
11 changes: 11 additions & 0 deletions samples/explore_datasource.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def main():
# SIGN IN
tableau_auth = TSC.TableauAuth(args.username, password)
server = TSC.Server(args.server)
server.use_highest_version()
with server.auth.sign_in(tableau_auth):
# Query projects for use when demonstrating publishing and updating
all_projects, pagination_item = server.projects.get()
Expand Down Expand Up @@ -67,6 +68,16 @@ def main():
print(["{0}({1})".format(connection.id, connection.datasource_name)
for connection in sample_datasource.connections])

# Add some tags to the datasource
original_tag_set = set(sample_datasource.tags)
sample_datasource.tags.update('a', 'b', 'c', 'd')
server.datasources.update(sample_datasource)
print("\nOld tag set: {}".format(original_tag_set))
print("New tag set: {}".format(sample_datasource.tags))

# Delete all tags that were added by setting tags to original
sample_datasource.tags = original_tag_set
server.datasources.update(sample_datasource)

if __name__ == '__main__':
main()
17 changes: 15 additions & 2 deletions samples/explore_workbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def main():
# SIGN IN
tableau_auth = TSC.TableauAuth(args.username, password)
server = TSC.Server(args.server)
server.use_highest_version()

overwrite_true = TSC.Server.PublishMode.Overwrite

Expand Down Expand Up @@ -82,14 +83,26 @@ def main():
sample_workbook.tags.update('a', 'b', 'c', 'd')
sample_workbook.show_tabs = True
server.workbooks.update(sample_workbook)
print("\nOld tag set: {}".format(original_tag_set))
print("New tag set: {}".format(sample_workbook.tags))
print("\nWorkbook's old tag set: {}".format(original_tag_set))
print("Workbook's new tag set: {}".format(sample_workbook.tags))
print("Workbook tabbed: {}".format(sample_workbook.show_tabs))

# Delete all tags that were added by setting tags to original
sample_workbook.tags = original_tag_set
server.workbooks.update(sample_workbook)

# Add tag to just one view
sample_view = sample_workbook.views[0]
original_tag_set = set(sample_view.tags)
sample_view.tags.add("view_tag")
server.views.update(sample_view)
print("\nView's old tag set: {}".format(original_tag_set))
print("View's new tag set: {}".format(sample_view.tags))

# Delete tag from just one view
sample_view.tags = original_tag_set
server.views.update(sample_view)

if args.download:
# Download
path = server.workbooks.download(sample_workbook.id, args.download)
Expand Down
74 changes: 74 additions & 0 deletions samples/refresh_tasks.py
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()
4 changes: 2 additions & 2 deletions tableauserverclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from .models import ConnectionCredentials, ConnectionItem, DatasourceItem,\
GroupItem, PaginationItem, ProjectItem, ScheduleItem, \
SiteItem, TableauAuth, UserItem, ViewItem, WorkbookItem, UnpopulatedPropertyError, \
HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem
from .server import RequestOptions, Filter, Sort, Server, ServerResponseError,\
HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem, TaskItem
from .server import RequestOptions, ImageRequestOptions, Filter, Sort, Server, ServerResponseError,\
MissingRequiredFieldError, NotSignedInError, Pager

__version__ = '0.0.1'
Expand Down
4 changes: 0 additions & 4 deletions tableauserverclient/datetime_helpers.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import datetime


# This code below is from the python documentation for tzinfo: https://docs.python.org/2.3/lib/datetime-tzinfo.html
ZERO = datetime.timedelta(0)
HOUR = datetime.timedelta(hours=1)

# A UTC class.


class UTC(datetime.tzinfo):
"""UTC"""
Expand All @@ -22,7 +19,6 @@ def dst(self, dt):


utc = UTC()

TABLEAU_DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"


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
13 changes: 6 additions & 7 deletions tableauserverclient/models/datasource_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@
from .tag_item import TagItem
from .. import NAMESPACE
from ..datetime_helpers import parse_datetime
import copy


class DatasourceItem(object):
def __init__(self, project_id, name=None):
self._connections = None
self._content_url = None
self._created_at = None
self._datasource_type = None
self._id = None
self._initial_tags = set()
self._project_name = None
self._tags = set()
self._datasource_type = None
self._updated_at = None
self.name = name
self.owner_id = None
self.project_id = project_id
self.tags = set()

@property
def connections(self):
Expand Down Expand Up @@ -52,10 +54,6 @@ def project_id(self, value):
def project_name(self):
return self._project_name

@property
def tags(self):
return self._tags

@property
def datasource_type(self):
return self._datasource_type
Expand Down Expand Up @@ -90,7 +88,8 @@ def _set_values(self, id, name, datasource_type, content_url, created_at,
if updated_at:
self._updated_at = updated_at
if tags:
self._tags = tags
self.tags = tags
self._initial_tags = copy.copy(tags)
if project_id:
self.project_id = project_id
if project_name:
Expand Down
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)
Loading