8000 Merge pull request #573 from tableau/development · rshide/server-client-python@c937520 · GitHub
[go: up one dir, main page]

Skip to content

Commit c937520

Browse files
author
Chris Shin
authored
Merge pull request tableau#573 from tableau/development
Merging v0.10 changes from development to master * Added a way to handle non-xml errors (tableau#515) * Added Webhooks endpoints for create, delete, get, list, and test (tableau#523, tableau#532) * Added delete method in the tasks endpoint (tableau#524) * Added description attribute to WorkbookItem (tableau#533) * Added support for materializeViews as schedule and task types (tableau#542) * Added warnings to schedules (tableau#550, tableau#551) * Added ability to update parent_id attribute of projects (tableau#560, tableau#567) * Improved filename behavior for download endpoints (tableau#517) * Improved logging (tableau#508) * Fixed runtime error in permissions endpoint (tableau#513) * Fixed move_workbook_sites sample (tableau#503) * Fixed project permissions endpoints (tableau#527) * Fixed login.py sample to accept site name (tableau#549)
2 parents a6cc77d + a0fb114 commit c937520

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+984
-175
lines changed

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
dist: xenial
22
language: python
33
python:
4-
- "2.7"
54
- "3.5"
65
- "3.6"
76
- "3.7"

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
## 0.10 (21 Feb 2020)
2+
3+
* Added a way to handle non-xml errors (#515)
4+
* Added Webhooks endpoints for create, delete, get, list, and test (#523, #532)
5+
* Added delete method in the tasks endpoint (#524)
6+
* Added description attribute to WorkbookItem (#533)
7+
* Added support for materializeViews as schedule and task types (#542)
8+
* Added warnings to schedules (#550, #551)
9+
* Added ability to update parent_id attribute of projects (#560, #567)
10+
* Improved filename behavior for download endpoints (#517)
11+
* Improved logging (#508)
12+
* Fixed runtime error in permissions endpoint (#513)
13+
* Fixed move_workbook_sites sample (#503)
14+
* Fixed project permissions endpoints (#527)
15+
* Fixed login.py sample to accept site name (#549)
16+
117
## 0.9 (4 Oct 2019)
218

319
* Added Metadata API endpoints (#431)

CONTRIBUTORS.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ The following people have contributed to this project to make it possible, and w
2828
* [Christian Oliff](https://github.com/coliff)
2929
* [Albin Antony](https://github.com/user9747)
3030
* [prae04](https://github.com/prae04)
31+
* [Martin Peters](https://github.com/martinbpeters)
32+
* [Sherman K](https://github.com/shrmnk)
33+
* [Jorge Fonseca](https://github.com/JorgeFonseca)
34+
* [Kacper Wolkiewicz](https://github.com/wolkiewiczk)
35+
* [Dahai Guo](https://github.com/guodah)
36+
* [Geraldine Zanolli](https://github.com/illonage)
3137

3238
## Core Team
3339

@@ -41,4 +47,3 @@ The following people have contributed to this project to make it possible, and w
4147
* [Priya Reguraman](https://github.com/preguraman)
4248
* [Jac Fitzgerald](https://github.com/jacalata)
4349
* [Dan Zucker](https://github.com/dzucker-tab)
44-
* [Irwin Dolobowsky](https://github.com/irwando)

contributing.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,14 @@ creating a PR can be found in the [Development Guide](https://tableau.github.io/
5353
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
5454
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
5555
somewhere.
56+
57+
58+
## Getting Started
59+
> pip install versioneer
60+
> python setup.py build
61+
> python setup.py test
62+
>
63+
64+
### before committing
65+
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
66+
> pycodestyle tableauserverclient test samples

samples/explore_webhooks.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
####
2+
# This script demonstrates how to use the Tableau Server Client
3+
# to interact with webhooks. It explores the different
4+
# functions that the Server API supports on webhooks.
5+
#
6+
# With no flags set, this sample will query all webhooks,
7+
# pick one webhook and print the name of the webhook.
8+
# Adding flags will demonstrate the specific feature
9+
# on top of the general operations.
10+
####
11+
12+
import argparse
13+
import getpass
14+
import logging
15+
import os.path
16+
17+
import tableauserverclient as TSC
18+
19+
20+
def main():
21+
22+
parser = argparse.ArgumentParser(description='Explore webhook functions supported by the Server API.')
23+
parser.add_argument('--server', '-s', required=True, help='server address')
24+
parser.add_argument('--username', '-u', required=True, help='username to sign into server')
25+
parser.add_argument('--site', '-S', default=None)
26+
parser.add_argument('-p', default=None, help='password')
27+
parser.add_argument('--create', '-c', help='create a webhook')
28+
parser.add_argument('--delete', '-d', help='delete a webhook', action='store_true')
29+
parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error',
30+
help='desired logging level (set to error by default)')
31+
32+
args = parser.parse_args()
33+
if args.p is None:
34+
password = getpass.getpass("Password: ")
35+
else:
36+
password = args.p
37+
38+
# Set logging level based on user input, or error by default
39+
logging_level = getattr(logging, args.logging_level.upper())
40+
logging.basicConfig(level=logging_level)
41+
42+
# SIGN IN
43+
tableau_auth = TSC.TableauAuth(args.username, password, args.site)
44+
print("Signing in to " + args.server + " [" + args.site + "] as " + args.username)
45+
server = TSC.Server(args.server)
46+
47+
# Set http options to disable verifying SSL
48+
server.add_http_options({'verify': False})
49+
50+
server.use_server_version()
51+
52+
with server.auth.sign_in(tableau_auth):
53+
54+
# Create webhook if create flag is set (-create, -c)
55+
if args.create:
56+
57+
new_webhook = TSC.WebhookItem()
58+
new_webhook.name = args.create
59+
new_webhook.url = "https://ifttt.com/maker-url"
60+
new_webhook.event = "datasource-created"
61+
print(new_webhook)
62+
new_webhook = server.webhooks.create(new_webhook)
63+
print("Webhook created. ID: {}".format(new_webhook.id))
64+
65+
# Gets all webhook items
66+
all_webhooks, pagination_item = server.webhooks.get()
67+
print("\nThere are {} webhooks on site: ".format(pagination_item.total_available))
68+
print([webhook.name for webhook in all_webhooks])
69+
70+
if all_webhooks:
71+
# Pick one webhook from the list and delete it
72+
sample_webhook = all_webhooks[0]
73+
# sample_webhook.delete()
74+
print("+++"+sample_webhook.name)
75+
76+
if (args.delete):
77+
print("Deleting webhook " + sample_webhook.name)
78+
server.webhooks.delete(sample_webhook.id)
79+
80+
81+
if __name__ == '__main__':
82+
main()

samples/list.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,36 @@
77
import argparse
88
import getpass
99
import logging
10+
import os
11+
import sys
1012

1113
import tableauserverclient as TSC
1214

1315

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

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

24-
parser.add_argument('resource_type', choices=['workbook', 'datasource', 'project', 'view', 'job'])
26+
parser.add_argument('resource_type', choices=['workbook', 'datasource', 'project', 'view', 'job', 'webhooks'])
2527

2628
args = parser.parse_args()
27-
28-
if args.password is None:
29-
password = getpass.getpass("Password: ")
30-
else:
31-
password = args.password
29+
token = os.environ.get('TOKEN', args.token)
30+
if not token:
31+
print("--token or TOKEN environment variable needs to be set")
32+
sys.exit(1)
3233

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

3738
# SIGN IN
38-
tableau_auth = TSC.TableauAuth(args.username, password, args.site)
39+
tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, token, site_id=args.site)
3940
server = TSC.Server(args.server, use_server_version=True)
4041
with server.auth.sign_in(tableau_auth):
4142
endpoint = {
@@ -44,6 +45,7 @@ def main():
4445
'view': server.views,
4546
'job': server.jobs,
4647
'project': server.projects,
48+
'webhooks': server.webhooks,
4749
}.get(args.resource_type)
4850

4951
for resource in TSC.Pager(endpoint.get):

samples/login.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def main():
2222
group = parser.add_mutually_exclusive_group(required=True)
2323
group.add_argument('--username', '-u', help='username to sign into the server')
2424
group.add_argument('--token-name', '-n', help='name of the personal access token used to sign into the server')
25+
parser.add_argument('--sitename', '-S', default=None)
2526

2627
args = parser.parse_args()
2728

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

4243
else:
4344
# Trying to authenticate using personal access tokens.
44-
personal_access_token = getpass.getpass("Personal Access Token: ")
45+
personal_access_token = input("Personal Access Token: ")
4546
tableau_auth = TSC.PersonalAccessTokenAuth(token_name=args.token_name,
46-
personal_access_token=personal_access_token)
47+
personal_access_token=personal_access_token, site_id=args.sitename)
4748
with server.auth.sign_in_with_personal_access_token(tableau_auth):
4849
print('Logged in successfully')
4950

samples/move_workbook_sites.py

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def main():
5858
workbook_path = source_server.workbooks.download(all_workbooks[0].id, tmpdir)
5959

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

74-
# Step 5: Find destination site's default project
75-
pagination_info, dest_projects = dest_server.projects.get()
76-
target_project = next((project for project in dest_projects if project.is_default()), None)
77-
78-
# Step 6: If default project is found, form a new workbook item and publish.
79-
if target_project is not None:
80-
new_workbook = TSC.WorkbookItem(name=args.workbook_name, project_id=target_project.id)
81-
new_workbook = dest_server.workbooks.publish(new_workbook, workbook_path,
82-
mode=TSC.Server.PublishMode.Overwrite)
83-
print("Successfully moved {0} ({1})".format(new_workbook.name, new_workbook.id))
84-
else:
85-
error = "The default project could not be found."
86-
raise LookupError(error)
87-
88-
# Step 7: Delete workbook from source site and delete temp directory
74+
# Step 5: Create a new workbook item and publish workbook. Note that
75+
# an empty project_id will publish to the 'Default' project.
76+
new_workbook = TSC.WorkbookItem(name=args.workbook_name, project_id="")
77+
new_workbook = dest_server.workbooks.publish(new_workbook, workbook_path,
78+
mode=TSC.Server.PublishMode.Overwrite)
79+
print("Successfully moved {0} ({1})".format(new_workbook.name, new_workbook.id))
80+
81+
# Step 6: Delete workbook from source site and delete temp directory
8982
source_server.workbooks.delete(all_workbooks[0].id)
9083

9184
finally:

setup.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@
2626
setup_requires=pytest_runner,
2727
install_requires=[
2828
'requests>=2.11,<3.0',
29-
'urllib3==1.24.3'
29+
'urllib3>=1.24.3,<2.0'
3030
],
3131
tests_require=[
3232
'requests-mock>=1.0,<2.0',
33-
'pytest'
33+
'pytest',
34+
'mock'
3435
]
3536
)

tableauserverclient/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
GroupItem, JobItem, BackgroundJobItem, PaginationItem, ProjectItem, ScheduleItem,\
44
SiteItem, TableauAuth, PersonalAccessTokenAuth, UserItem, ViewItem, WorkbookItem, UnpopulatedPropertyError,\
55
HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem, TaskItem,\
6-
SubscriptionItem, Target, PermissionsRule, Permission, DatabaseItem, TableItem, ColumnItem, FlowItem
6+
SubscriptionItem, Target, PermissionsRule, Permission, DatabaseItem, TableItem, ColumnItem, FlowItem, \
7+
WebhookItem, PersonalAccessTokenAuth
78
from .server import RequestOptions, CSVRequestOptions, ImageRequestOptions, PDFRequestOptions, Filter, Sort, \
89
Server, ServerResponseError, MissingRequiredFieldError, NotSignedInError, Pager
910
from ._version import get_versions
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
1+
import os
12
ALLOWED_SPECIAL = (' ', '.', '_', '-')
23

34

45
def to_filename(string_to_sanitize):
56
sanitized = (c for c in string_to_sanitize if c.isalnum() or c in ALLOWED_SPECIAL)
67
return "".join(sanitized)
8+
9+
10+
def make_download_path(filepath, filename):
11+
download_path = None
12+
13+
if filepath is None:
14+
download_path = filename
15+
16+
elif os.path.isdir(filepath):
17+
download_path = os.path.join(filepath, filename)
18+
19+
else:
20+
download_path = filepath + os.path.splitext(filename)[1]
21+
22+
return download_path

tableauserverclient/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@
2323
from .workbook_item import WorkbookItem
2424
from .subscription_item import SubscriptionItem
2525
from .permissions_item import PermissionsRule, Permission
26+
from .webhook_item import WebhookItem
27+
from .personal_access_token_auth import PersonalAccessTokenAuth

tableauserverclient/models/pagination_item.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,12 @@ def from_response(cls, resp, ns):
2929
pagination_item._page_size = int(pagination_xml.get('pageSize', '-1'))
3030
pagination_item._total_available = int(pagination_xml.get('totalAvailable', '-1'))
3131
return pagination_item
32+
33+
@classmethod
34+
def from_single_page_list(cls, l):
35+
item = cls()
36+
item._page_number = 1
37+
item._page_size = len(l)
38+
item._total_available = len(l)
39+
40+
return item

tableauserverclient/models/personal_access_token_auth.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ def __init__(self, token_name, personal_access_token, site_id=''):
99
@property
1010
def credentials(self):
1111
return {'personalAccessTokenName': self.token_name, 'personalAccessTokenSecret': self.personal_access_token}
12+
13+
def __repr__(self):
14+
return "<PersonalAccessToken name={} token={}>".format(self.token_name, self.personal_access_token)

0 commit comments

Comments
 (0)
0