Skip to content

Commit

Permalink
Merge pull request #573 from tableau/development
Browse files Browse the repository at this point in the history
Merging v0.10 changes from development to master

* Added a way to handle non-xml errors (#515)
* Added Webhooks endpoints for create, delete, get, list, and test (#523, #532)
* Added delete method in the tasks endpoint (#524)
* Added description attribute to WorkbookItem (#533)
* Added support for materializeViews as schedule and task types (#542)
* Added warnings to schedules (#550, #551)
* Added ability to update parent_id attribute of projects (#560, #567)
* Improved filename behavior for download endpoints (#517)
* Improved logging (#508)
* Fixed runtime error in permissions endpoint (#513)
* Fixed move_workbook_sites sample (#503)
* Fixed project permissions endpoints (#527)
* Fixed login.py sample to accept site name (#549)
  • Loading branch information
Chris Shin authored Feb 21, 2020
2 parents a6cc77d + a0fb114 commit c937520
Show file tree
Hide file tree
Showing 53 changed files with 984 additions and 175 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
dist: xenial
language: python
python:
- "2.7"
- "3.5"
- "3.6"
- "3.7"
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
## 0.10 (21 Feb 2020)

* Added a way to handle non-xml errors (#515)
* Added Webhooks endpoints for create, delete, get, list, and test (#523, #532)
* Added delete method in the tasks endpoint (#524)
* Added description attribute to WorkbookItem (#533)
* Added support for materializeViews as schedule and task types (#542)
* Added warnings to schedules (#550, #551)
* Added ability to update parent_id attribute of projects (#560, #567)
* Improved filename behavior for download endpoints (#517)
* Improved logging (#508)
* Fixed runtime error in permissions endpoint (#513)
* Fixed move_workbook_sites sample (#503)
* Fixed project permissions endpoints (#527)
* Fixed login.py sample to accept site name (#549)

## 0.9 (4 Oct 2019)

* Added Metadata API endpoints (#431)
Expand Down
7 changes: 6 additions & 1 deletion CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ The following people have contributed to this project to make it possible, and w
* [Christian Oliff](https://github.com/coliff)
* [Albin Antony](https://github.com/user9747)
* [prae04](https://github.com/prae04)
* [Martin Peters](https://github.com/martinbpeters)
* [Sherman K](https://github.com/shrmnk)
* [Jorge Fonseca](https://github.com/JorgeFonseca)
* [Kacper Wolkiewicz](https://github.com/wolkiewiczk)
* [Dahai Guo](https://github.com/guodah)
* [Geraldine Zanolli](https://github.com/illonage)

## Core Team

Expand All @@ -41,4 +47,3 @@ The following people have contributed to this project to make it possible, and w
* [Priya Reguraman](https://github.com/preguraman)
* [Jac Fitzgerald](https://github.com/jacalata)
* [Dan Zucker](https://github.com/dzucker-tab)
* [Irwin Dolobowsky](https://github.com/irwando)
11 changes: 11 additions & 0 deletions contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,14 @@ creating a PR can be found in the [Development Guide](https://tableau.github.io/
If the feature is complex or has multiple solutions that could be equally appropriate approaches, it would be helpful to file an issue to discuss the
design trade-offs of each solution before implementing, to allow us to collectively arrive at the best solution, which most likely exists in the middle
somewhere.


## Getting Started
> pip install versioneer
> python setup.py build
> python setup.py test
>
### before committing
Our CI runs include a python lint run, so you should run this locally and fix complaints before committing as this will fail your checkin
> pycodestyle tableauserverclient test samples
82 changes: 82 additions & 0 deletions samples/explore_webhooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
####
# This script demonstrates how to use the Tableau Server Client
# to interact with webhooks. It explores the different
# functions that the Server API supports on webhooks.
#
# With no flags set, this sample will query all webhooks,
# pick one webhook and print the name of the webhook.
# Adding flags will demonstrate the specific feature
# on top of the general operations.
####

import argparse
import getpass
import logging
import os.path

import tableauserverclient as TSC


def main():

parser = argparse.ArgumentParser(description='Explore webhook functions supported by the Server API.')
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, help='password')
parser.add_argument('--create', '-c', help='create a webhook')
parser.add_argument('--delete', '-d', help='delete a webhook', action='store_true')
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()
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)
print("Signing in to " + args.server + " [" + args.site + "] as " + args.username)
server = TSC.Server(args.server)

# Set http options to disable verifying SSL
server.add_http_options({'verify': False})

server.use_server_version()

with server.auth.sign_in(tableau_auth):

# Create webhook if create flag is set (-create, -c)
if args.create:

new_webhook = TSC.WebhookItem()
new_webhook.name = args.create
new_webhook.url = "https://ifttt.com/maker-url"
new_webhook.event = "datasource-created"
print(new_webhook)
new_webhook = server.webhooks.create(new_webhook)
print("Webhook created. ID: {}".format(new_webhook.id))

# Gets all webhook items
all_webhooks, pagination_item = server.webhooks.get()
print("\nThere are {} webhooks on site: ".format(pagination_item.total_available))
print([webhook.name for webhook in all_webhooks])

if all_webhooks:
# Pick one webhook from the list and delete it
sample_webhook = all_webhooks[0]
# sample_webhook.delete()
print("+++"+sample_webhook.name)

if (args.delete):
print("Deleting webhook " + sample_webhook.name)
server.webhooks.delete(sample_webhook.id)


if __name__ == '__main__':
main()
22 changes: 12 additions & 10 deletions samples/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,36 @@
import argparse
import getpass
import logging
import os
import sys

import tableauserverclient as TSC


def main():
parser = argparse.ArgumentParser(description='List out the names and LUIDs for different resource types')
parser.add_argument('--server', '-s', required=True, help='server address')
parser.add_argument('--site', '-S', default=None, help='site to log into, do not specify for default site')
parser.add_argument('--username', '-u', required=True, help='username to sign into server')
parser.add_argument('--password', '-p', default=None, help='password for the user')
parser.add_argument('--site', '-S', default="", help='site to log into, do not specify for default site')
parser.add_argument('--token-name', '-n', required=True, help='username to signin under')
parser.add_argument('--token', '-t', help='personal access token for logging in')

parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error',
help='desired logging level (set to error by default)')

parser.add_argument('resource_type', choices=['workbook', 'datasource', 'project', 'view', 'job'])
parser.add_argument('resource_type', choices=['workbook', 'datasource', 'project', 'view', 'job', 'webhooks'])

args = parser.parse_args()

if args.password is None:
password = getpass.getpass("Password: ")
else:
password = args.password
token = os.environ.get('TOKEN', args.token)
if not token:
print("--token or TOKEN environment variable needs to be set")
sys.exit(1)

# 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)
tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, token, site_id=args.site)
server = TSC.Server(args.server, use_server_version=True)
with server.auth.sign_in(tableau_auth):
endpoint = {
Expand All @@ -44,6 +45,7 @@ def main():
'view': server.views,
'job': server.jobs,
'project': server.projects,
'webhooks': server.webhooks,
}.get(args.resource_type)

for resource in TSC.Pager(endpoint.get):
Expand Down
5 changes: 3 additions & 2 deletions samples/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def main():
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--username', '-u', help='username to sign into the server')
group.add_argument('--token-name', '-n', help='name of the personal access token used to sign into the server')
parser.add_argument('--sitename', '-S', default=None)

args = parser.parse_args()

Expand All @@ -41,9 +42,9 @@ def main():

else:
# Trying to authenticate using personal access tokens.
personal_access_token = getpass.getpass("Personal Access Token: ")
personal_access_token = input("Personal Access Token: ")
tableau_auth = TSC.PersonalAccessTokenAuth(token_name=args.token_name,
personal_access_token=personal_access_token)
personal_access_token=personal_access_token, site_id=args.sitename)
with server.auth.sign_in_with_personal_access_token(tableau_auth):
print('Logged in successfully')

Expand Down
25 changes: 9 additions & 16 deletions samples/move_workbook_sites.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def main():
workbook_path = source_server.workbooks.download(all_workbooks[0].id, tmpdir)

# Step 4: Check if destination site exists, then sign in to the site
pagination_info, all_sites = source_server.sites.get()
all_sites, pagination_info = source_server.sites.get()
found_destination_site = any((True for site in all_sites if
args.destination_site.lower() == site.content_url.lower()))
if not found_destination_site:
Expand All @@ -71,21 +71,14 @@ def main():
# because of the different auth token and site ID.
with dest_server.auth.sign_in(tableau_auth):

# Step 5: Find destination site's default project
pagination_info, dest_projects = dest_server.projects.get()
target_project = next((project for project in dest_projects if project.is_default()), None)

# Step 6: If default project is found, form a new workbook item and publish.
if target_project is not None:
new_workbook = TSC.WorkbookItem(name=args.workbook_name, project_id=target_project.id)
new_workbook = dest_server.workbooks.publish(new_workbook, workbook_path,
mode=TSC.Server.PublishMode.Overwrite)
print("Successfully moved {0} ({1})".format(new_workbook.name, new_workbook.id))
else:
error = "The default project could not be found."
raise LookupError(error)

# Step 7: Delete workbook from source site and delete temp directory
# Step 5: Create a new workbook item and publish workbook. Note that
# an empty project_id will publish to the 'Default' project.
new_workbook = TSC.WorkbookItem(name=args.workbook_name, project_id="")
new_workbook = dest_server.workbooks.publish(new_workbook, workbook_path,
mode=TSC.Server.PublishMode.Overwrite)
print("Successfully moved {0} ({1})".format(new_workbook.name, new_workbook.id))

# Step 6: Delete workbook from source site and delete temp directory
source_server.workbooks.delete(all_workbooks[0].id)

finally:
Expand Down
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@
setup_requires=pytest_runner,
install_requires=[
'requests>=2.11,<3.0',
'urllib3==1.24.3'
'urllib3>=1.24.3,<2.0'
],
tests_require=[
'requests-mock>=1.0,<2.0',
'pytest'
'pytest',
'mock'
]
)
3 changes: 2 additions & 1 deletion tableauserverclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
GroupItem, JobItem, BackgroundJobItem, PaginationItem, ProjectItem, ScheduleItem,\
SiteItem, TableauAuth, PersonalAccessTokenAuth, UserItem, ViewItem, WorkbookItem, UnpopulatedPropertyError,\
HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem, TaskItem,\
SubscriptionItem, Target, PermissionsRule, Permission, DatabaseItem, TableItem, ColumnItem, FlowItem
SubscriptionItem, Target, PermissionsRule, Permission, DatabaseItem, TableItem, ColumnItem, FlowItem, \
WebhookItem, PersonalAccessTokenAuth
from .server import RequestOptions, CSVRequestOptions, ImageRequestOptions, PDFRequestOptions, Filter, Sort, \
Server, ServerResponseError, MissingRequiredFieldError, NotSignedInError, Pager
from ._version import get_versions
Expand Down
16 changes: 16 additions & 0 deletions tableauserverclient/filesys_helpers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
import os
ALLOWED_SPECIAL = (' ', '.', '_', '-')


def to_filename(string_to_sanitize):
sanitized = (c for c in string_to_sanitize if c.isalnum() or c in ALLOWED_SPECIAL)
return "".join(sanitized)


def make_download_path(filepath, filename):
download_path = None

if filepath is None:
download_path = filename

elif os.path.isdir(filepath):
download_path = os.path.join(filepath, filename)

else:
download_path = filepath + os.path.splitext(filename)[1]

return download_path
2 changes: 2 additions & 0 deletions tableauserverclient/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@
from .workbook_item import WorkbookItem
from .subscription_item import SubscriptionItem
from .permissions_item import PermissionsRule, Permission
from .webhook_item import WebhookItem
from .personal_access_token_auth import PersonalAccessTokenAuth
9 changes: 9 additions & 0 deletions tableauserverclient/models/pagination_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,12 @@ def from_response(cls, resp, ns):
pagination_item._page_size = int(pagination_xml.get('pageSize', '-1'))
pagination_item._total_available = int(pagination_xml.get('totalAvailable', '-1'))
return pagination_item

@classmethod
def from_single_page_list(cls, l):
item = cls()
item._page_number = 1
item._page_size = len(l)
item._total_available = len(l)

return item
3 changes: 3 additions & 0 deletions tableauserverclient/models/personal_access_token_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ def __init__(self, token_name, personal_access_token, site_id=''):
@property
def credentials(self):
return {'personalAccessTokenName': self.token_name, 'personalAccessTokenSecret': self.personal_access_token}

def __repr__(self):
return "<PersonalAccessToken name={} token={}>".format(self.token_name, self.personal_access_token)
Loading

0 comments on commit c937520

Please sign in to comment.