From 0da852e8abd24d279c37ddd225c0044658db7930 Mon Sep 17 00:00:00 2001 From: Chris Shin Date: Tue, 21 Jul 2020 10:16:01 -0700 Subject: [PATCH 01/14] Fixes login sample to pass in sitename for username/password auth (cherry picked from commit 4cbd8007f64dbc93e1bf89981f5cfbcec01f83bb) --- samples/login.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/login.py b/samples/login.py index 57a929f6b..339a711cb 100644 --- a/samples/login.py +++ b/samples/login.py @@ -36,7 +36,7 @@ def main(): if args.username: # Trying to authenticate using username and password. password = getpass.getpass("Password: ") - tableau_auth = TSC.TableauAuth(args.username, password) + tableau_auth = TSC.TableauAuth(args.username, password, site_id=args.sitename) with server.auth.sign_in(tableau_auth): print('Logged in successfully') From 3041b4aecaf5dac3a4af3cb8797f114f3063fa5b Mon Sep 17 00:00:00 2001 From: Chris Shin Date: Wed, 22 Jul 2020 14:09:10 -0700 Subject: [PATCH 02/14] Fixes default sitename in login sample and adds more print statements (#652) (cherry picked from commit ccbbc49b5278d6c4605262612cb18ebd265aea0a) --- samples/login.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/samples/login.py b/samples/login.py index 339a711cb..29e02e14e 100644 --- a/samples/login.py +++ b/samples/login.py @@ -22,7 +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) + parser.add_argument('--sitename', '-S', default='') args = parser.parse_args() @@ -36,13 +36,18 @@ def main(): if args.username: # Trying to authenticate using username and password. password = getpass.getpass("Password: ") + + print("\nSigning in...\nServer: {}\nSite: {}\nUsername: {}".format(args.server, args.sitename, args.username)) tableau_auth = TSC.TableauAuth(args.username, password, site_id=args.sitename) with server.auth.sign_in(tableau_auth): print('Logged in successfully') else: # Trying to authenticate using personal access tokens. - personal_access_token = input("Personal Access Token: ") + personal_access_token = getpass.getpass("Personal Access Token: ") + + print("\nSigning in...\nServer: {}\nSite: {}\nToken name: {}" + .format(args.server, args.sitename, args.token_name)) tableau_auth = TSC.PersonalAccessTokenAuth(token_name=args.token_name, personal_access_token=personal_access_token, site_id=args.sitename) with server.auth.sign_in_with_personal_access_token(tableau_auth): From d68244b0a43a97f7f36841d573e3354b46ebd99f Mon Sep 17 00:00:00 2001 From: Chris Shin Date: Wed, 22 Jul 2020 15:23:10 -0700 Subject: [PATCH 03/14] Updates changelog with patch notes --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa01dac19..d0da3f294 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.12.1 (22 July 2020) + +* Fixed login.py sample to properly handle sitename (#652) + ## 0.12 (10 July 2020) * Added hidden_views parameter to workbook publish method (#614) From f0efba0e30e71442dce760f377ce3fe29b20dbf4 Mon Sep 17 00:00:00 2001 From: Rickey Shideler Date: Fri, 7 Aug 2020 18:28:52 -0500 Subject: [PATCH 04/14] Added webpage_url to workbooks --- .vscode/settings.json | 4 ++++ tableauserverclient/models/workbook_item.py | 20 ++++++++++++++------ test/assets/workbook_get.xml | 5 ++--- test/test_workbook.py | 5 +++++ 4 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..389498638 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "editor.formatOnSave": false, + +} \ No newline at end of file diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index a7decd41f..794c12099 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -12,6 +12,7 @@ class WorkbookItem(object): def __init__(self, project_id, name=None, show_tabs=False): self._connections = None self._content_url = None + self._webpage_url = None self._created_at = None self._id = None self._initial_tags = set() @@ -51,6 +52,10 @@ def permissions(self): def content_url(self): return self._content_url + @property + def webpage_url(self): + return self._webpage_url + @property def created_at(self): return self._created_at @@ -152,17 +157,17 @@ def _parse_common_tags(self, workbook_xml, ns): if not isinstance(workbook_xml, ET.Element): workbook_xml = ET.fromstring(workbook_xml).find('.//t:workbook', namespaces=ns) if workbook_xml is not None: - (_, _, _, _, description, updated_at, _, show_tabs, + (_, _, _, _, _, description, updated_at, _, show_tabs, project_id, project_name, owner_id, _, _, data_acceleration_config) = self._parse_element(workbook_xml, ns) - self._set_values(None, None, None, None, description, updated_at, + self._set_values(None, None, None, None, None, description, updated_at, None, show_tabs, project_id, project_name, owner_id, None, None, data_acceleration_config) return self - def _set_values(self, id, name, content_url, created_at, description, updated_at, + def _set_values(self, id, name, content_url, webpage_url, created_at, description, updated_at, size, show_tabs, project_id, project_name, owner_id, tags, views, data_acceleration_config): if id is not None: @@ -171,6 +176,8 @@ def _set_values(self, id, name, content_url, created_at, description, updated_at self.name = name if content_url: self._content_url = content_url + if webpage_url: + self._webpage_url = webpage_url if created_at: self._created_at = created_at if description: @@ -201,12 +208,12 @@ def from_response(cls, resp, ns): parsed_response = ET.fromstring(resp) all_workbook_xml = parsed_response.findall('.//t:workbook', namespaces=ns) for workbook_xml in all_workbook_xml: - (id, name, content_url, created_at, description, updated_at, size, show_tabs, + (id, name, content_url, webpage_url, created_at, description, updated_at, size, show_tabs, project_id, project_name, owner_id, tags, views, data_acceleration_config) = cls._parse_element(workbook_xml, ns) workbook_item = cls(project_id) - workbook_item._set_values(id, name, content_url, created_at, description, updated_at, + workbook_item._set_values(id, name, content_url, webpage_url, created_at, description, updated_at, size, show_tabs, None, project_name, owner_id, tags, views, data_acceleration_config) all_workbook_items.append(workbook_item) @@ -217,6 +224,7 @@ def _parse_element(workbook_xml, ns): id = workbook_xml.get('id', None) name = workbook_xml.get('name', None) content_url = workbook_xml.get('contentUrl', None) + webpage_url = workbook_xml.get('webpageUrl', None) created_at = parse_datetime(workbook_xml.get('createdAt', None)) description = workbook_xml.get('description', None) updated_at = parse_datetime(workbook_xml.get('updatedAt', None)) @@ -256,7 +264,7 @@ def _parse_element(workbook_xml, ns): if data_acceleration_elem is not None: data_acceleration_config = parse_data_acceleration_config(data_acceleration_elem) - return id, name, content_url, created_at, description, updated_at, size, show_tabs, \ + return id, name, content_url, webpage_url, created_at, description, updated_at, size, show_tabs, \ project_id, project_name, owner_id, tags, views, data_acceleration_config diff --git a/test/assets/workbook_get.xml b/test/assets/workbook_get.xml index e5fd3967b..873ca3848 100644 --- a/test/assets/workbook_get.xml +++ b/test/assets/workbook_get.xml @@ -2,13 +2,12 @@ - + - - + diff --git a/test/test_workbook.py b/test/test_workbook.py index f1d9df9e0..48b47e60f 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -1,3 +1,5 @@ + +#%% import unittest import os import requests_mock @@ -32,6 +34,7 @@ UPDATE_XML = os.path.join(TEST_ASSET_DIR, 'workbook_update.xml') UPDATE_PERMISSIONS = os.path.join(TEST_ASSET_DIR, 'workbook_update_permissions.xml') +#%% class WorkbookTests(unittest.TestCase): def setUp(self): @@ -54,6 +57,7 @@ def test_get(self): self.assertEqual('6d13b0ca-043d-4d42-8c9d-3f3313ea3a00', all_workbooks[0].id) self.assertEqual('Superstore', all_workbooks[0].name) self.assertEqual('Superstore', all_workbooks[0].content_url) + self.assertEqual('http://tableauserver/#/workbooks/1/views', all_workbooks[0].webpage_url) self.assertEqual(False, all_workbooks[0].show_tabs) self.assertEqual(1, all_workbooks[0].size) self.assertEqual('2016-08-03T20:34:04Z', format_datetime(all_workbooks[0].created_at)) @@ -66,6 +70,7 @@ def test_get(self): self.assertEqual('3cc6cd06-89ce-4fdc-b935-5294135d6d42', all_workbooks[1].id) self.assertEqual('SafariSample', all_workbooks[1].name) self.assertEqual('SafariSample', all_workbooks[1].content_url) + self.assertEqual('http://tableauserver/#/workbooks/2/views', all_workbooks[1].webpage_url) self.assertEqual(False, all_workbooks[1].show_tabs) self.assertEqual(26, all_workbooks[1].size) self.assertEqual('2016-07-26T20:34:56Z', format_datetime(all_workbooks[1].created_at)) From 6d5c72f319203fff11551dad76ff8aee2c172807 Mon Sep 17 00:00:00 2001 From: Rickey Shideler Date: Fri, 7 Aug 2020 18:30:38 -0500 Subject: [PATCH 05/14] removed vscode files --- .vscode/settings.json | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 389498638..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "editor.formatOnSave": false, - -} \ No newline at end of file From 8ea294d75219837113a1431cea3f9fc241b1685a Mon Sep 17 00:00:00 2001 From: absentmoose <32021884+absentmoose@users.noreply.github.com> Date: Sat, 8 Aug 2020 13:53:26 -0500 Subject: [PATCH 06/14] Update workbook_item.py Removing whitespace --- tableauserverclient/models/workbook_item.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index 794c12099..3a3ddcdf9 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -177,7 +177,7 @@ def _set_values(self, id, name, content_url, webpage_url, created_at, descriptio if content_url: self._content_url = content_url if webpage_url: - self._webpage_url = webpage_url + self._webpage_url = webpage_url if created_at: self._created_at = created_at if description: From 55714f9cf527880a7cdd1ac3cab88b62f76749fb Mon Sep 17 00:00:00 2001 From: absentmoose <32021884+absentmoose@users.noreply.github.com> Date: Sat, 8 Aug 2020 13:58:58 -0500 Subject: [PATCH 07/14] Update test_workbook.py Removing cells --- test/test_workbook.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/test_workbook.py b/test/test_workbook.py index 48b47e60f..296228baf 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -1,5 +1,3 @@ - -#%% import unittest import os import requests_mock @@ -34,7 +32,6 @@ UPDATE_XML = os.path.join(TEST_ASSET_DIR, 'workbook_update.xml') UPDATE_PERMISSIONS = os.path.join(TEST_ASSET_DIR, 'workbook_update_permissions.xml') -#%% class WorkbookTests(unittest.TestCase): def setUp(self): @@ -57,8 +54,8 @@ def test_get(self): self.assertEqual('6d13b0ca-043d-4d42-8c9d-3f3313ea3a00', all_workbooks[0].id) self.assertEqual('Superstore', all_workbooks[0].name) self.assertEqual('Superstore', all_workbooks[0].content_url) - self.assertEqual('http://tableauserver/#/workbooks/1/views', all_workbooks[0].webpage_url) self.assertEqual(False, all_workbooks[0].show_tabs) + self.assertEqual('http://tableauserver/#/workbooks/1/views', all_workbooks[0].webpage_url) self.assertEqual(1, all_workbooks[0].size) self.assertEqual('2016-08-03T20:34:04Z', format_datetime(all_workbooks[0].created_at)) self.assertEqual('description for Superstore', all_workbooks[0].description) @@ -70,7 +67,7 @@ def test_get(self): self.assertEqual('3cc6cd06-89ce-4fdc-b935-5294135d6d42', all_workbooks[1].id) self.assertEqual('SafariSample', all_workbooks[1].name) self.assertEqual('SafariSample', all_workbooks[1].content_url) - self.assertEqual('http://tableauserver/#/workbooks/2/views', all_workbooks[1].webpage_url) + self.assertEqual('http://tableauserver/#/workbooks/2/views', all_workbooks[1].webpage_url) self.assertEqual(False, all_workbooks[1].show_tabs) self.assertEqual(26, all_workbooks[1].size) self.assertEqual('2016-07-26T20:34:56Z', format_datetime(all_workbooks[1].created_at)) @@ -105,6 +102,7 @@ def test_get_by_id(self): self.assertEqual('3cc6cd06-89ce-4fdc-b935-5294135d6d42', single_workbook.id) self.assertEqual('SafariSample', single_workbook.name) self.assertEqual('SafariSample', single_workbook.content_url) + self.assertEqual('http://tableauserver/#/workbooks/2/views', single_workbook.webpage_url) self.assertEqual(False, single_workbook.show_tabs) self.assertEqual(26, single_workbook.size) self.assertEqual('2016-07-26T20:34:56Z', format_datetime(single_workbook.created_at)) From 4b04e9dceb2ef7cd4375d326fee4ccfdcaa06a92 Mon Sep 17 00:00:00 2001 From: absentmoose <32021884+absentmoose@users.noreply.github.com> Date: Sat, 8 Aug 2020 14:02:03 -0500 Subject: [PATCH 08/14] Update test_workbook.py Removing whitespace --- test/test_workbook.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_workbook.py b/test/test_workbook.py index 296228baf..91cf092bf 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -55,7 +55,7 @@ def test_get(self): self.assertEqual('Superstore', all_workbooks[0].name) self.assertEqual('Superstore', all_workbooks[0].content_url) self.assertEqual(False, all_workbooks[0].show_tabs) - self.assertEqual('http://tableauserver/#/workbooks/1/views', all_workbooks[0].webpage_url) + self.assertEqual('http://tableauserver/#/workbooks/1/views', all_workbooks[0].webpage_url) self.assertEqual(1, all_workbooks[0].size) self.assertEqual('2016-08-03T20:34:04Z', format_datetime(all_workbooks[0].created_at)) self.assertEqual('description for Superstore', all_workbooks[0].description) @@ -67,7 +67,7 @@ def test_get(self): self.assertEqual('3cc6cd06-89ce-4fdc-b935-5294135d6d42', all_workbooks[1].id) self.assertEqual('SafariSample', all_workbooks[1].name) self.assertEqual('SafariSample', all_workbooks[1].content_url) - self.assertEqual('http://tableauserver/#/workbooks/2/views', all_workbooks[1].webpage_url) + self.assertEqual('http://tableauserver/#/workbooks/2/views', all_workbooks[1].webpage_url) self.assertEqual(False, all_workbooks[1].show_tabs) self.assertEqual(26, all_workbooks[1].size) self.assertEqual('2016-07-26T20:34:56Z', format_datetime(all_workbooks[1].created_at)) @@ -102,7 +102,7 @@ def test_get_by_id(self): self.assertEqual('3cc6cd06-89ce-4fdc-b935-5294135d6d42', single_workbook.id) self.assertEqual('SafariSample', single_workbook.name) self.assertEqual('SafariSample', single_workbook.content_url) - self.assertEqual('http://tableauserver/#/workbooks/2/views', single_workbook.webpage_url) + self.assertEqual('http://tableauserver/#/workbooks/2/views', single_workbook.webpage_url) self.assertEqual(False, single_workbook.show_tabs) self.assertEqual(26, single_workbook.size) self.assertEqual('2016-07-26T20:34:56Z', format_datetime(single_workbook.created_at)) From 56cb5d8075223d6bed5e4f81d1dbf5a85daded91 Mon Sep 17 00:00:00 2001 From: absentmoose <32021884+absentmoose@users.noreply.github.com> Date: Sat, 8 Aug 2020 14:04:13 -0500 Subject: [PATCH 09/14] Update workbook_get_by_id.xml Adding webpage url --- test/assets/workbook_get_by_id.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/assets/workbook_get_by_id.xml b/test/assets/workbook_get_by_id.xml index 1b2fe9120..98dfc4a75 100644 --- a/test/assets/workbook_get_by_id.xml +++ b/test/assets/workbook_get_by_id.xml @@ -1,6 +1,6 @@ - + @@ -11,4 +11,4 @@ - \ No newline at end of file + From 5a2df0be7b22763edee4e2dd9af1a388837d8736 Mon Sep 17 00:00:00 2001 From: jorwoods Date: Tue, 21 Jul 2020 17:18:21 -0500 Subject: [PATCH 10/14] Create notes property from XML response (#571) Create notes property from XML response Co-authored-by: Jordan Woods --- tableauserverclient/models/job_item.py | 12 ++++++++++-- test/assets/job_get_by_id.xml | 14 ++++++++++++++ test/test_job.py | 20 +++++++++++++++++--- 3 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 test/assets/job_get_by_id.xml diff --git a/tableauserverclient/models/job_item.py b/tableauserverclient/models/job_item.py index 58d1f1396..cc8b7df43 100644 --- a/tableauserverclient/models/job_item.py +++ b/tableauserverclient/models/job_item.py @@ -3,7 +3,8 @@ class JobItem(object): - def __init__(self, id_, job_type, progress, created_at, started_at=None, completed_at=None, finish_code=0): + def __init__(self, id_, job_type, progress, created_at, started_at=None, + completed_at=None, finish_code=0, notes=None): self._id = id_ self._type = job_type self._progress = progress @@ -11,6 +12,7 @@ def __init__(self, id_, job_type, progress, created_at, started_at=None, complet self._started_at = started_at self._completed_at = completed_at self._finish_code = finish_code + self._notes = notes or [] @property def id(self): @@ -40,6 +42,10 @@ def completed_at(self): def finish_code(self): return self._finish_code + @property + def notes(self): + return self._notes + def __repr__(self): return "".format(**self.__dict__) @@ -63,7 +69,9 @@ def _parse_element(cls, element, ns): started_at = parse_datetime(element.get('startedAt', None)) completed_at = parse_datetime(element.get('completedAt', None)) finish_code = element.get('finishCode', -1) - return cls(id_, type_, progress, created_at, started_at, completed_at, finish_code) + notes = [note.text for note in + element.findall('.//t:notes', namespaces=ns)] or None + return cls(id_, type_, progress, created_at, started_at, completed_at, finish_code, notes) class BackgroundJobItem(object): diff --git a/test/assets/job_get_by_id.xml b/test/assets/job_get_by_id.xml new file mode 100644 index 000000000..b142dfe2f --- /dev/null +++ b/test/assets/job_get_by_id.xml @@ -0,0 +1,14 @@ + + + + + Job detail notes + + + More detail + + + diff --git a/test/test_job.py b/test/test_job.py index ee80450ca..08b98b815 100644 --- a/test/test_job.py +++ b/test/test_job.py @@ -4,10 +4,12 @@ import requests_mock import tableauserverclient as TSC from tableauserverclient.datetime_helpers import utc +from ._utils import read_xml_asset TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') -GET_XML = os.path.join(TEST_ASSET_DIR, 'job_get.xml') +GET_XML = 'job_get.xml' +GET_BY_ID_XML = 'job_get_by_id.xml' class JobTests(unittest.TestCase): @@ -22,8 +24,7 @@ def setUp(self): self.baseurl = self.server.jobs.baseurl def test_get(self): - with open(GET_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + response_xml = read_xml_asset(GET_XML) with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) all_jobs, pagination_item = self.server.jobs.get() @@ -41,6 +42,19 @@ def test_get(self): self.assertEqual(started_at, job.started_at) self.assertEqual(ended_at, job.ended_at) + def test_get_by_id(self): + response_xml = read_xml_asset(GET_BY_ID_XML) + job_id = '2eef4225-aa0c-41c4-8662-a76d89ed7336' + with requests_mock.mock() as m: + m.get('{0}/{1}'.format(self.baseurl, job_id), text=response_xml) + job = self.server.jobs.get_by_id(job_id) + + created_at = datetime(2020, 5, 13, 20, 23, 45, tzinfo=utc) + updated_at = datetime(2020, 5, 13, 20, 25, 18, tzinfo=utc) + ended_at = datetime(2020, 5, 13, 20, 25, 18, tzinfo=utc) + self.assertEqual(job_id, job.id) + self.assertListEqual(job.notes, ['Job detail notes']) + def test_get_before_signin(self): self.server._auth_token = None self.assertRaises(TSC.NotSignedInError, self.server.jobs.get) From e21f4bc64788e4774faf9aa3c9c95c3b7dca2051 Mon Sep 17 00:00:00 2001 From: Brian Cantoni Date: Tue, 4 Aug 2020 18:21:29 -0700 Subject: [PATCH 11/14] Minor edits and cleanup --- README.md | 6 +++--- contributing.md | 27 ++++++++++++++++----------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 51e23549a..e2c30704a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Tableau Server Client (Python) -[![Tableau Supported](https://img.shields.io/badge/Support%20Level-Tableau%20Supported-53bd92.svg)](https://www.tableau.com/support-levels-it-and-developer-tools) + +[![Tableau Supported](https://img.shields.io/badge/Support%20Level-Tableau%20Supported-53bd92.svg)](https://www.tableau.com/support-levels-it-and-developer-tools) [![Build Status](https://travis-ci.org/tableau/server-client-python.svg?branch=master)](https://travis-ci.org/tableau/server-client-python) Use the Tableau Server Client (TSC) library to increase your productivity as you interact with the Tableau Server REST API. With the TSC library you can do almost everything that you can do with the REST API, including: @@ -7,8 +8,7 @@ Use the Tableau Server Client (TSC) library to increase your productivity as you * Create users and groups. * Query projects, sites, and more. -This repository contains Python source code and sample files. +This repository contains Python source code and sample files. Python versions 3.5 and up are supported. For more information on installing and using TSC, see the documentation: - diff --git a/contributing.md b/contributing.md index 4c7cdef00..c7f487ec3 100644 --- a/contributing.md +++ b/contributing.md @@ -15,7 +15,7 @@ a feature do not require the CLA. ## Issues and Feature Requests -To submit an issue/bug report, or to request a feature, please submit a [github issue](https://github.com/tableau/server-client-python/issues) to the repo. +To submit an issue/bug report, or to request a feature, please submit a [GitHub issue](https://github.com/tableau/server-client-python/issues) to the repo. If you are submitting a bug report, please provide as much information as you can, including clear and concise repro steps, attaching any necessary files to assist in the repro. **Be sure to scrub the files of any potentially sensitive information. Issues are public.** @@ -48,19 +48,24 @@ anyone can add to an issue: ## Fixes, Implementations, and Documentation For all other things, please submit a PR that includes the fix, documentation, or new code that you are trying to contribute. More information on -creating a PR can be found in the [Development Guide](https://tableau.github.io/server-client-python/docs/dev-guide) +creating a PR can be found in the [Development Guide](https://tableau.github.io/server-client-python/docs/dev-guide). 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 + +```shell +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. + +```shell +pycodestyle tableauserverclient test samples +``` From 8f08228e24ff4ca7e62461d18c3b22a3976cd90e Mon Sep 17 00:00:00 2001 From: Brian Cantoni Date: Tue, 4 Aug 2020 18:22:29 -0700 Subject: [PATCH 12/14] Rename a duplicate test method so they all run --- test/test_task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_task.py b/test/test_task.py index cf7879305..789f97187 100644 --- a/test/test_task.py +++ b/test/test_task.py @@ -105,7 +105,7 @@ def test_get_materializeviews_tasks(self): self.assertEqual(parse_datetime('2019-12-09T22:30:00Z'), task.schedule_item.next_run_at) self.assertEqual(parse_datetime('2019-12-09T20:45:04Z'), task.last_run_at) - def test_delete(self): + def test_delete_data_acceleration(self): with requests_mock.mock() as m: m.delete('{}/{}/{}'.format( self.server.tasks.baseurl, TaskItem.Type.DataAcceleration, From 83accbd8ed4f50bc4e94612bc284234134ffc29c Mon Sep 17 00:00:00 2001 From: Brian Cantoni Date: Tue, 4 Aug 2020 18:23:38 -0700 Subject: [PATCH 13/14] Cleanup test comments and descriptions, no functional changes --- samples/add_default_permission.py | 2 +- samples/create_group.py | 4 ++-- samples/create_project.py | 2 +- samples/download_view_image.py | 2 +- samples/export.py | 9 ++++++++- samples/export_wb.py | 7 +++++-- samples/filter_sort_groups.py | 4 ++-- samples/filter_sort_projects.py | 3 +-- samples/kill_all_jobs.py | 2 +- samples/list.py | 2 +- samples/pagination_sample.py | 2 +- samples/query_permissions.py | 2 +- samples/refresh.py | 2 +- samples/set_refresh_schedule.py | 10 +++++++++- 14 files changed, 35 insertions(+), 18 deletions(-) diff --git a/samples/add_default_permission.py b/samples/add_default_permission.py index b6dbdd479..63c38f53d 100644 --- a/samples/add_default_permission.py +++ b/samples/add_default_permission.py @@ -17,7 +17,7 @@ def main(): - parser = argparse.ArgumentParser(description='Add workbook default permission for a given project') + parser = argparse.ArgumentParser(description='Add workbook default permissions for a given project.') 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, help='Site to sign into - default site if not provided') diff --git a/samples/create_group.py b/samples/create_group.py index c6865bc56..7f9dc1e96 100644 --- a/samples/create_group.py +++ b/samples/create_group.py @@ -1,5 +1,5 @@ #### -# This script demonstrates how to create groups using the Tableau +# This script demonstrates how to create a group using the Tableau # Server Client. # # To run the script, you must have installed Python 3.5 or later. @@ -17,7 +17,7 @@ def main(): - parser = argparse.ArgumentParser(description='Creates sample schedules for each type of frequency.') + parser = argparse.ArgumentParser(description='Creates a sample user group.') 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('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', diff --git a/samples/create_project.py b/samples/create_project.py index ac55da17e..0380cb8a0 100644 --- a/samples/create_project.py +++ b/samples/create_project.py @@ -26,7 +26,7 @@ def create_project(server, project_item): def main(): - parser = argparse.ArgumentParser(description='Get all of the refresh tasks available on a server') + parser = argparse.ArgumentParser(description='Create new projects.') 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) diff --git a/samples/download_view_image.py b/samples/download_view_image.py index ce6dd3165..07162eebf 100644 --- a/samples/download_view_image.py +++ b/samples/download_view_image.py @@ -17,7 +17,7 @@ def main(): - parser = argparse.ArgumentParser(description='Query View Image From Server') + parser = argparse.ArgumentParser(description='Download image of a specified view.') 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') diff --git a/samples/export.py b/samples/export.py index 67b3319a8..b8cd01140 100644 --- a/samples/export.py +++ b/samples/export.py @@ -1,3 +1,10 @@ +#### +# This script demonstrates how to export a view using the Tableau +# Server Client. +# +# To run the script, you must have installed Python 3.5 or later. +#### + import argparse import getpass import logging @@ -6,7 +13,7 @@ def main(): - parser = argparse.ArgumentParser(description='Export a view as an image, pdf, or csv') + parser = argparse.ArgumentParser(description='Export a view as an image, PDF, or CSV') 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) diff --git a/samples/export_wb.py b/samples/export_wb.py index 8d3640ab4..334d57c89 100644 --- a/samples/export_wb.py +++ b/samples/export_wb.py @@ -1,9 +1,12 @@ -# +#### # This sample uses the PyPDF2 library for combining pdfs together to get the full pdf for all the views in a # workbook. # # You will need to do `pip install PyPDF2` to use this sample. # +# To run the script, you must have installed Python 3.5 or later. +#### + import argparse import getpass @@ -48,7 +51,7 @@ def cleanup(tempdir): def main(): - parser = argparse.ArgumentParser(description='Export to PDF all of the views in a workbook') + parser = argparse.ArgumentParser(description='Export to PDF all of the views in a workbook.') 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') diff --git a/samples/filter_sort_groups.py b/samples/filter_sort_groups.py index fa0c2318e..f8123a29c 100644 --- a/samples/filter_sort_groups.py +++ b/samples/filter_sort_groups.py @@ -1,5 +1,5 @@ #### -# This script demonstrates how to filter groups using the Tableau +# This script demonstrates how to filter and sort groups using the Tableau # Server Client. # # To run the script, you must have installed Python 3.5 or later. @@ -24,7 +24,7 @@ def create_example_group(group_name='Example Group', server=None): def main(): - parser = argparse.ArgumentParser(description='Filter on groups') + parser = argparse.ArgumentParser(description='Filter and sort groups.') 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('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', diff --git a/samples/filter_sort_projects.py b/samples/filter_sort_projects.py index 91633f38f..0c62614b0 100644 --- a/samples/filter_sort_projects.py +++ b/samples/filter_sort_projects.py @@ -2,7 +2,6 @@ # This script demonstrates how to use the Tableau Server Client # to filter and sort on the name of the projects present on site. # -# # To run the script, you must have installed Python 3.5 or later. #### @@ -26,7 +25,7 @@ def create_example_project(name='Example Project', content_permissions='LockedTo def main(): - parser = argparse.ArgumentParser(description='Get all of the refresh tasks available on a server') + parser = argparse.ArgumentParser(description='Filter and sort projects.') 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) diff --git a/samples/kill_all_jobs.py b/samples/kill_all_jobs.py index 9d3c7836a..1aeb7298e 100644 --- a/samples/kill_all_jobs.py +++ b/samples/kill_all_jobs.py @@ -12,7 +12,7 @@ def main(): - parser = argparse.ArgumentParser(description='Cancel all of the running background jobs') + parser = argparse.ArgumentParser(description='Cancel all of the running background jobs.') 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') diff --git a/samples/list.py b/samples/list.py index 84b3c70d2..10e11ac04 100644 --- a/samples/list.py +++ b/samples/list.py @@ -14,7 +14,7 @@ def main(): - parser = argparse.ArgumentParser(description='List out the names and LUIDs for different resource types') + 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="", help='site to log into, do not specify for default site') parser.add_argument('--token-name', '-n', required=True, help='username to signin under') diff --git a/samples/pagination_sample.py b/samples/pagination_sample.py index 25effd7b2..6779023ba 100644 --- a/samples/pagination_sample.py +++ b/samples/pagination_sample.py @@ -19,7 +19,7 @@ def main(): - parser = argparse.ArgumentParser(description='Return a list of all of the workbooks on your server') + parser = argparse.ArgumentParser(description='Demonstrate pagination on the list of workbooks on the 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('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', diff --git a/samples/query_permissions.py b/samples/query_permissions.py index 48120f398..a253adc9a 100644 --- a/samples/query_permissions.py +++ b/samples/query_permissions.py @@ -14,7 +14,7 @@ def main(): - parser = argparse.ArgumentParser(description='Query permissions of a given resource') + parser = argparse.ArgumentParser(description='Query permissions of a given resource.') 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, help='Site to sign into - default site if not provided') diff --git a/samples/refresh.py b/samples/refresh.py index ba3a2f183..96937a6e3 100644 --- a/samples/refresh.py +++ b/samples/refresh.py @@ -12,7 +12,7 @@ def main(): - parser = argparse.ArgumentParser(description='Get all of the refresh tasks available on a server') + parser = argparse.ArgumentParser(description='Trigger a refresh task on a workbook or datasource.') 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) diff --git a/samples/set_refresh_schedule.py b/samples/set_refresh_schedule.py index edb94f47e..2d4761560 100644 --- a/samples/set_refresh_schedule.py +++ b/samples/set_refresh_schedule.py @@ -1,3 +1,11 @@ +#### +# This script demonstrates how to set the refresh schedule for +# a workbook or datasource. +# +# To run the script, you must have installed Python 3.5 or later. +#### + + import argparse import getpass import logging @@ -6,7 +14,7 @@ def usage(args): - parser = argparse.ArgumentParser(description='Explore workbook functions supported by the Server API.') + parser = argparse.ArgumentParser(description='Set refresh schedule for a workbook or datasource.') 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('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', From b2b08d353ac488f45b9ef284c6b84b162d4a3191 Mon Sep 17 00:00:00 2001 From: Brian Cantoni Date: Tue, 4 Aug 2020 21:32:39 -0700 Subject: [PATCH 14/14] Add support for Python 3.8 Only test_publish_with_hidden_view() needed changes because the order of attributes in the XML request body were swapped for some reason in 3.8 compared to prior Pythons. --- .travis.yml | 1 + test/test_workbook.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 68cee02ad..41316d700 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ python: - "3.5" - "3.6" - "3.7" + - "3.8" # command to install dependencies install: - "pip install -e ." diff --git a/test/test_workbook.py b/test/test_workbook.py index 48b47e60f..4ad18d779 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -2,6 +2,7 @@ #%% import unittest import os +import re import requests_mock import tableauserverclient as TSC import xml.etree.ElementTree as ET @@ -461,8 +462,8 @@ def test_publish_with_hidden_view(self): hidden_views=['GDP per capita']) request_body = m._adapter.request_history[0]._request.body - self.assertIn( - b'', request_body) + self.assertTrue(re.search(rb'<\/views>', request_body)) + self.assertTrue(re.search(rb'<\/views>', request_body)) def test_publish_async(self): self.server.version = '3.0'