From 82b4879180905b2e506a0b04e34e200b7f16a887 Mon Sep 17 00:00:00 2001 From: Russell Hay Date: Fri, 8 Nov 2019 15:17:15 -0800 Subject: [PATCH 01/17] Fix a decreated warning in the tests --- test/test_job.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/test_job.py b/test/test_job.py index 5da0f76fa..ee8316168 100644 --- a/test/test_job.py +++ b/test/test_job.py @@ -32,14 +32,14 @@ def test_get(self): started_at = datetime(2018, 5, 22, 13, 0, 37, tzinfo=utc) ended_at = datetime(2018, 5, 22, 13, 0, 45, tzinfo=utc) - self.assertEquals(1, pagination_item.total_available) - self.assertEquals('2eef4225-aa0c-41c4-8662-a76d89ed7336', job.id) - self.assertEquals('Success', job.status) - self.assertEquals('50', job.priority) - self.assertEquals('single_subscription_notify', job.type) - self.assertEquals(created_at, job.created_at) - self.assertEquals(started_at, job.started_at) - self.assertEquals(ended_at, job.ended_at) + self.assertEqual(1, pagination_item.total_available) + self.assertEqual('2eef4225-aa0c-41c4-8662-a76d89ed7336', job.id) + self.assertEqual('Success', job.status) + self.assertEqual('50', job.priority) + self.assertEqual('single_subscription_notify', job.type) + self.assertEqual(created_at, job.created_at) + self.assertEqual(started_at, job.started_at) + self.assertEqual(ended_at, job.ended_at) def test_get_before_signin(self): self.server._auth_token = None From 6b8d984fe974fc55e2e1ad105a4dbf198c2bf6c1 Mon Sep 17 00:00:00 2001 From: Russell Hay Date: Fri, 8 Nov 2019 15:17:49 -0800 Subject: [PATCH 02/17] Dropping 2.7 support since it's EOLEOY --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cc261b20c..68cee02ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ dist: xenial language: python python: - - "2.7" - "3.5" - "3.6" - "3.7" From d9e7657f44b7d284534ca53a058bfb6466210ede Mon Sep 17 00:00:00 2001 From: Russell Hay Date: Fri, 8 Nov 2019 15:19:21 -0800 Subject: [PATCH 03/17] fixes deprecated warning in test_requests.py --- test/test_requests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_requests.py b/test/test_requests.py index 67282b6f9..d064e080e 100644 --- a/test/test_requests.py +++ b/test/test_requests.py @@ -54,7 +54,7 @@ def test_internal_server_error(self): server_response = "500: Internal Server Error" with requests_mock.mock() as m: m.register_uri('GET', self.server.server_info.baseurl, status_code=500, text=server_response) - self.assertRaisesRegexp(InternalServerError, server_response, self.server.server_info.get) + self.assertRaisesRegex(InternalServerError, server_response, self.server.server_info.get) # Test that non-xml server errors are handled properly def test_non_xml_error(self): @@ -62,4 +62,4 @@ def test_non_xml_error(self): server_response = "this is not xml" with requests_mock.mock() as m: m.register_uri('GET', self.server.server_info.baseurl, status_code=499, text=server_response) - self.assertRaisesRegexp(NonXMLResponseError, server_response, self.server.server_info.get) + self.assertRaisesRegex(NonXMLResponseError, server_response, self.server.server_info.get) From ce0f6c4b727a9d0c009f421560f87aa96369f2c6 Mon Sep 17 00:00:00 2001 From: Russell Hay Date: Fri, 8 Nov 2019 15:19:47 -0800 Subject: [PATCH 04/17] fixes deprecated warning in test_datasource.py --- test/test_datasource.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_datasource.py b/test/test_datasource.py index fdf3c2e51..e5c07f3b4 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -178,8 +178,8 @@ def test_update_connection(self): new_connection = self.server.datasources.update_connection(single_datasource, connection) self.assertEqual(connection.id, new_connection.id) self.assertEqual(connection.connection_type, new_connection.connection_type) - self.assertEquals('bar', new_connection.server_address) - self.assertEquals('9876', new_connection.server_port) + self.assertEqual('bar', new_connection.server_address) + self.assertEqual('9876', new_connection.server_port) self.assertEqual('foo', new_connection.username) def test_populate_permissions(self): @@ -355,6 +355,6 @@ def test_synchronous_publish_timeout_error(self): new_datasource = TSC.DatasourceItem(project_id='') publish_mode = self.server.PublishMode.CreateNew - self.assertRaisesRegexp(InternalServerError, 'Please use asynchronous publishing to avoid timeouts.', + self.assertRaisesRegex(InternalServerError, 'Please use asynchronous publishing to avoid timeouts.', self.server.datasources.publish, new_datasource, asset('SampleDS.tds'), publish_mode) From ead9e1ab080202054cbbd31a9f0e6ccd72dd7584 Mon Sep 17 00:00:00 2001 From: Russell Hay Date: Fri, 8 Nov 2019 15:22:32 -0800 Subject: [PATCH 05/17] fixed deprecated warning in test_workbook --- test/test_workbook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_workbook.py b/test/test_workbook.py index 0317ba115..45bff0ffe 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -497,5 +497,5 @@ def test_synchronous_publish_timeout_error(self): new_workbook = TSC.WorkbookItem(project_id='') publish_mode = self.server.PublishMode.CreateNew - self.assertRaisesRegexp(InternalServerError, 'Please use asynchronous publishing to avoid timeouts', + self.assertRaisesRegex(InternalServerError, 'Please use asynchronous publishing to avoid timeouts', self.server.workbooks.publish, new_workbook, asset('SampleWB.twbx'), publish_mode) From 8d6c5c401273f49463523d942061084c1e6ba7be Mon Sep 17 00:00:00 2001 From: Russell Hay Date: Fri, 8 Nov 2019 15:26:05 -0800 Subject: [PATCH 06/17] Fixing incorrect version for publish_async --- test/test_datasource.py | 4 +++- test/test_workbook.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/test_datasource.py b/test/test_datasource.py index e5c07f3b4..d19aeade1 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -230,9 +230,11 @@ def test_publish(self): self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', new_datasource.owner_id) def test_publish_async(self): + self.server.version="3.0" + baseurl = self.server.datasources.baseurl response_xml = read_xml_asset(PUBLISH_XML_ASYNC) with requests_mock.mock() as m: - m.post(self.baseurl, text=response_xml) + m.post(baseurl, text=response_xml) new_datasource = TSC.DatasourceItem('SampleDS', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') publish_mode = self.server.PublishMode.CreateNew diff --git a/test/test_workbook.py b/test/test_workbook.py index 45bff0ffe..cc47eebb3 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -409,10 +409,12 @@ def test_publish(self): self.assertEqual('RESTAPISample_0/sheets/GDPpercapita', new_workbook.views[0].content_url) def test_publish_async(self): + self.server.version = '3.0' + baseurl = self.server.workbooks.baseurl with open(PUBLISH_ASYNC_XML, 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: - m.post(self.baseurl, text=response_xml) + m.post(baseurl, text=response_xml) new_workbook = TSC.WorkbookItem(name='Sample', show_tabs=False, From f413df733cee46acd2f31d3b4c224e1b55076ee3 Mon Sep 17 00:00:00 2001 From: Russell Hay Date: Fri, 8 Nov 2019 15:27:37 -0800 Subject: [PATCH 07/17] Fix deprecrated warning in test_regression_tests.py --- test/test_regression_tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_regression_tests.py b/test/test_regression_tests.py index 932e993de..281f3fbca 100644 --- a/test/test_regression_tests.py +++ b/test/test_regression_tests.py @@ -55,9 +55,9 @@ def test_make_download_path(self): has_file_path_folder = ('/root/folder/', 'file.ext') has_file_path_file = ('out', 'file.ext') - self.assertEquals('file.ext', make_download_path(*no_file_path)) - self.assertEquals('out.ext', make_download_path(*has_file_path_file)) + self.assertEqual('file.ext', make_download_path(*no_file_path)) + self.assertEqual('out.ext', make_download_path(*has_file_path_file)) with mock.patch('os.path.isdir') as mocked_isdir: mocked_isdir.return_value = True - self.assertEquals('/root/folder/file.ext', make_download_path(*has_file_path_folder)) + self.assertEqual('/root/folder/file.ext', make_download_path(*has_file_path_folder)) From e68e07e1975c87c48c058193a4e4bfbd49890c52 Mon Sep 17 00:00:00 2001 From: Russell Hay Date: Mon, 11 Nov 2019 09:00:42 -0800 Subject: [PATCH 08/17] initial working version --- samples/list.py | 16 ++---- tableauserverclient/__init__.py | 3 +- tableauserverclient/models/__init__.py | 2 + tableauserverclient/models/pagination_item.py | 9 +++ .../models/personal_access_token_auth.py | 3 + tableauserverclient/models/webhook_item.py | 54 ++++++++++++++++++ .../server/endpoint/__init__.py | 4 +- .../server/endpoint/auth_endpoint.py | 2 + .../server/endpoint/webhooks_endpoint.py | 52 ++++++++++++++++++ .../server/endpoint/workbooks_endpoint.py | 1 - tableauserverclient/server/request_factory.py | 25 ++++++++- tableauserverclient/server/server.py | 3 +- test/assets/webhook_create.xml | 12 ++++ test/assets/webhook_get.xml | 14 +++++ test/test_webhook.py | 55 +++++++++++++++++++ 15 files changed, 240 insertions(+), 15 deletions(-) create mode 100644 tableauserverclient/models/webhook_item.py create mode 100644 tableauserverclient/server/endpoint/webhooks_endpoint.py create mode 100644 test/assets/webhook_create.xml create mode 100644 test/assets/webhook_get.xml create mode 100644 test/test_webhook.py diff --git a/samples/list.py b/samples/list.py index 090d7dfdf..9b4e76b01 100644 --- a/samples/list.py +++ b/samples/list.py @@ -14,28 +14,23 @@ 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('--username', '-u', required=True, help='username to signin under') + parser.add_argument('--token', '-t', required=True, 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 - # 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.username, args.token, site_id=args.site) server = TSC.Server(args.server, use_server_version=True) with server.auth.sign_in(tableau_auth): endpoint = { @@ -44,6 +39,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): diff --git a/tableauserverclient/__init__.py b/tableauserverclient/__init__.py index eb647ed25..bb938c8fa 100644 --- a/tableauserverclient/__init__.py +++ b/tableauserverclient/__init__.py @@ -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 diff --git a/tableauserverclient/models/__init__.py b/tableauserverclient/models/__init__.py index a3517e13f..c0c64904d 100644 --- a/tableauserverclient/models/__init__.py +++ b/tableauserverclient/models/__init__.py @@ -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 \ No newline at end of file diff --git a/tableauserverclient/models/pagination_item.py b/tableauserverclient/models/pagination_item.py index 545679945..98d6b42f9 100644 --- a/tableauserverclient/models/pagination_item.py +++ b/tableauserverclient/models/pagination_item.py @@ -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 diff --git a/tableauserverclient/models/personal_access_token_auth.py b/tableauserverclient/models/personal_access_token_auth.py index c9b892cf6..875f68c48 100644 --- a/tableauserverclient/models/personal_access_token_auth.py +++ b/tableauserverclient/models/personal_access_token_auth.py @@ -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 "".format(self.token_name, self.personal_access_token) diff --git a/tableauserverclient/models/webhook_item.py b/tableauserverclient/models/webhook_item.py new file mode 100644 index 000000000..5f4930319 --- /dev/null +++ b/tableauserverclient/models/webhook_item.py @@ -0,0 +1,54 @@ +import xml.etree.ElementTree as ET +from .exceptions import UnpopulatedPropertyError +from .property_decorators import property_not_nullable, property_is_boolean, property_is_materialized_views_config +from .tag_item import TagItem +from .view_item import ViewItem +from .permissions_item import PermissionsRule +from ..datetime_helpers import parse_datetime +import copy + + +class WebhookItem(object): + def __init__(self): + self._id = None + self.name = None + self.url = None + self._event = None + + def _set_values(self, id, name, url): + if id is not None: + self._id = id + if name: + self.name = name + if url: + self.url = url + + @property + def id(self): return self._id + + @property + def event(self): return "event" + + @classmethod + def from_response(cls, resp, ns): + all_webhooks_items = list() + parsed_response = ET.fromstring(resp) + all_webhooks_xml = parsed_response.findall('.//t:webhook', namespaces=ns) + for webhook_xml in all_webhooks_xml: + (id, name, url) = cls._parse_element(webhook_xml, ns) + + webhook_item = cls() + webhook_item._set_values(id, name, url) + all_webhooks_items.append(webhook_item) + return all_webhooks_items + + @staticmethod + def _parse_element(webhook_xml, ns): + id = webhook_xml.get('id', None) + name = webhook_xml.get('name', None) + url = webhook_xml.get('.//t:webhook-destination-http[@url]') + + return id, name, url + + def __repr__(self): + return "".format(self.id, self.name, self.url, self.event) \ No newline at end of file diff --git a/tableauserverclient/server/endpoint/__init__.py b/tableauserverclient/server/endpoint/__init__.py index dbf501fe3..2808e7a6c 100644 --- a/tableauserverclient/server/endpoint/__init__.py +++ b/tableauserverclient/server/endpoint/__init__.py @@ -11,9 +11,11 @@ from .schedules_endpoint import Schedules from .server_info_endpoint import ServerInfo from .sites_endpoint import Sites +from .subscriptions_endpoint import Subscriptions from .tables_endpoint import Tables from .tasks_endpoint import Tasks from .users_endpoint import Users from .views_endpoint import Views +from .webhooks_endpoint import Webhooks from .workbooks_endpoint import Workbooks -from .subscriptions_endpoint import Subscriptions + diff --git a/tableauserverclient/server/endpoint/auth_endpoint.py b/tableauserverclient/server/endpoint/auth_endpoint.py index 10f4cb4db..ef66264ee 100644 --- a/tableauserverclient/server/endpoint/auth_endpoint.py +++ b/tableauserverclient/server/endpoint/auth_endpoint.py @@ -25,11 +25,13 @@ def baseurl(self): @api(version="2.0") def sign_in(self, auth_req): url = "{0}/{1}".format(self.baseurl, 'signin') + print(auth_req) signin_req = RequestFactory.Auth.signin_req(auth_req) server_response = self.parent_srv.session.post(url, data=signin_req, **self.parent_srv.http_options) self.parent_srv._namespace.detect(server_response.content) self._check_status(server_response) + print(server_response.content) parsed_response = ET.fromstring(server_response.content) site_id = parsed_response.find('.//t:site', namespaces=self.parent_srv.namespace).get('id', None) user_id = parsed_response.find('.//t:user', namespaces=self.parent_srv.namespace).get('id', None) diff --git a/tableauserverclient/server/endpoint/webhooks_endpoint.py b/tableauserverclient/server/endpoint/webhooks_endpoint.py new file mode 100644 index 000000000..c1d194614 --- /dev/null +++ b/tableauserverclient/server/endpoint/webhooks_endpoint.py @@ -0,0 +1,52 @@ +from .endpoint import Endpoint, api, parameter_added_in +from ...models import WebhookItem, PaginationItem +from .. import RequestFactory + +import logging +logger = logging.getLogger('tableau.endpoint.webhooks') + +class Webhooks(Endpoint): + def __init__(self, parent_srv): + super(Webhooks, self).__init__(parent_srv) + + @property + def baseurl(self): + return "{0}/sites/{1}/webhooks".format(self.parent_srv.baseurl, self.parent_srv.site_id) + + @api(version="3.6") + def get(self, req_options=None): + logger.info('Querying all Webhooks on site') + url = self.baseurl + server_response = self.get_request(url, req_options) + all_workbook_items = WebhookItem.from_response(server_response.content, self.parent_srv.namespace) + pagination_item = PaginationItem.from_single_page_list(all_workbook_items) + return all_workbook_items, pagination_item + + @api(version="3.6") + def get_by_id(self, webhook_id): + if not webhook_id: + error = "Webhook ID undefined." + raise ValueError(error) + logger.info('Querying single webhook (ID: {0})'.format(webhook_id)) + url = "{0}/{1}".format(self.baseurl, webhook_id) + server_response = self.get_request(url) + return WebhookItem.from_response(server_response.content, self.parent_srv.namespace)[0] + + @api(version="3.6") + def delete(self, webhook_id): + if not webhook_id: + error = "Webhook ID undefined." + raise ValueError(error) + url = "{0}/{1}".format(self.baseurl, webhook_id) + self.delete_request(url) + logger.info('Deleted single webhook (ID: {0})'.format(webhook_id)) + + @api(version="3.6") + def create(self, webhook_item): + url = self.baseurl + create_req = RequestFactory.Webhook.create_req(webhook_item) + server_response = self.post_request(url, create_req) + new_webhook = WebhookItem.from_response(server_response.content, self.parent_srv.namespace)[0] + + logger.info('Created new webhook (ID: {0})'.format(new_webhook.id)) + return new_webhook diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 80bc38a7c..a6a49fedf 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -1,6 +1,5 @@ from .endpoint import Endpoint, api, parameter_added_in from .exceptions import InternalServerError, MissingRequiredFieldError -from .endpoint import api, parameter_added_in, Endpoint from .permissions_endpoint import _PermissionsEndpoint from .exceptions import MissingRequiredFieldError from .fileuploads_endpoint import Fileuploads diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index ad484e6a8..9764892c4 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -53,13 +53,16 @@ def signin_req(self, auth_item): credentials_element = ET.SubElement(xml_request, 'credentials') for attribute_name, attribute_value in auth_item.credentials.items(): credentials_element.attrib[attribute_name] = attribute_value + print("CE: {}".format(ET.tostring(credentials_element))) site_element = ET.SubElement(credentials_element, 'site') site_element.attrib['contentUrl'] = auth_item.site_id + print("SE: {}".format(ET.tostring(site_element))) if auth_item.user_id_to_impersonate: user_element = ET.SubElement(credentials_element, 'user') user_element.attrib['id'] = auth_item.user_id_to_impersonate + print("UE: {}".format(ET.tostring(user_element))) return ET.tostring(xml_request) @@ -555,6 +558,24 @@ def empty_req(self, xml_request): pass +class WebhookRequest(object): + @_tsrequest_wrapped + def create_req(self, xml_request, webhook_item): + print(webhook_item) + webhook = ET.SubElement(xml_request, 'webhook') + webhook.attrib['name'] = webhook_item.name + + source = ET.SubElement(webhook, 'webhook-source') + # TODO: Add Event Type + + destination = ET.SubElement(webhook, 'webhook-destination') + post = ET.SubElement(destination, 'webhook-destination-http') + post.attrib['method'] = 'POST' + post.attrib['url'] = webhook_item.url + + return ET.tostring(xml_request) + + class RequestFactory(object): Auth = AuthRequest() Connection = Connection() @@ -569,9 +590,11 @@ class RequestFactory(object): Project = ProjectRequest() Schedule = ScheduleRequest() Site = SiteRequest() + Subscription = SubscriptionRequest() Table = TableRequest() Tag = TagRequest() Task = TaskRequest() User = UserRequest() Workbook = WorkbookRequest() - Subscription = SubscriptionRequest() + Webhook = WebhookRequest() + diff --git a/tableauserverclient/server/server.py b/tableauserverclient/server/server.py index b11f55d17..6c36482fd 100644 --- a/tableauserverclient/server/server.py +++ b/tableauserverclient/server/server.py @@ -4,7 +4,7 @@ from ..namespace import Namespace from .endpoint import Sites, Views, Users, Groups, Workbooks, Datasources, Projects, Auth, \ Schedules, ServerInfo, Tasks, ServerInfoEndpointNotFoundError, Subscriptions, Jobs, Metadata,\ - Databases, Tables, Flows + Databases, Tables, Flows, Webhooks from .endpoint.exceptions import EndpointUnavailableError, ServerInfoEndpointNotFoundError import requests @@ -55,6 +55,7 @@ def __init__(self, server_address, use_server_version=False): self.metadata = Metadata(self) self.databases = Databases(self) self.tables = Tables(self) + self.webhooks = Webhooks(self) self._namespace = Namespace() if use_server_version: diff --git a/test/assets/webhook_create.xml b/test/assets/webhook_create.xml new file mode 100644 index 000000000..24a5ca99b --- /dev/null +++ b/test/assets/webhook_create.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/test/assets/webhook_get.xml b/test/assets/webhook_get.xml new file mode 100644 index 000000000..adf74109a --- /dev/null +++ b/test/assets/webhook_get.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/test_webhook.py b/test/test_webhook.py new file mode 100644 index 000000000..2d10bf71b --- /dev/null +++ b/test/test_webhook.py @@ -0,0 +1,55 @@ +import unittest +import os +import requests_mock +import tableauserverclient as TSC + +from ._utils import read_xml_asset, read_xml_assets, asset + +TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') + +GET_XML = asset('webhook_get.xml') +CREATE_XML = asset('webhook_create.xml') + + +class WebhookTests(unittest.TestCase): + def setUp(self): + self.server = TSC.Server('http://test') + self.server.version = "3.6" + + # Fake signin + self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' + self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + + self.baseurl = self.server.webhooks.baseurl + + def test_get(self): + with open(GET_XML, 'rb') as f: + response_xml = f.read().decode('utf-8') + with requests_mock.mock() as m: + m.get(self.baseurl, text=response_xml) + webhooks, _ = self.server.webhooks.get() + + + + def test_get_before_signin(self): + self.server._auth_token = None + self.assertRaises(TSC.NotSignedInError, self.server.webhooks.get) + + def test_delete(self): + with requests_mock.mock() as m: + m.delete(self.baseurl + '/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', status_code=204) + self.server.webhooks.delete('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + + def test_delete_missing_id(self): + self.assertRaises(ValueError, self.server.webhooks.delete, '') + + def test_create(self): + with open(CREATE_XML, 'rb') as f: + response_xml = f.read().decode('utf-8') + with requests_mock.mock() as m: + m.post(self.baseurl, text=response_xml) + new_webhook = TSC.WebhookItem() + new_webhook.name = "Test Webhook" + new_webhook.url = "http://ifttt.com/maker-url" + + new_webhook = self.server.webhooks.create(new_webhook) From 1f4630afa95683dca2cc752eeaf92907f387c0ef Mon Sep 17 00:00:00 2001 From: Russell Hay Date: Mon, 11 Nov 2019 09:03:06 -0800 Subject: [PATCH 09/17] removing print statements --- samples/list.py | 4 ++-- tableauserverclient/server/endpoint/auth_endpoint.py | 2 -- tableauserverclient/server/request_factory.py | 3 --- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/samples/list.py b/samples/list.py index 9b4e76b01..88165cd81 100644 --- a/samples/list.py +++ b/samples/list.py @@ -15,7 +15,7 @@ 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="", help='site to log into, do not specify for default site') - parser.add_argument('--username', '-u', required=True, help='username to signin under') + parser.add_argument('--token-name', '-n', required=True, help='username to signin under') parser.add_argument('--token', '-t', required=True, help='personal access token for logging in') parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', @@ -30,7 +30,7 @@ def main(): logging.basicConfig(level=logging_level) # SIGN IN - tableau_auth = TSC.PersonalAccessTokenAuth(args.username, args.token, site_id=args.site) + tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token, site_id=args.site) server = TSC.Server(args.server, use_server_version=True) with server.auth.sign_in(tableau_auth): endpoint = { diff --git a/tableauserverclient/server/endpoint/auth_endpoint.py b/tableauserverclient/server/endpoint/auth_endpoint.py index ef66264ee..10f4cb4db 100644 --- a/tableauserverclient/server/endpoint/auth_endpoint.py +++ b/tableauserverclient/server/endpoint/auth_endpoint.py @@ -25,13 +25,11 @@ def baseurl(self): @api(version="2.0") def sign_in(self, auth_req): url = "{0}/{1}".format(self.baseurl, 'signin') - print(auth_req) signin_req = RequestFactory.Auth.signin_req(auth_req) server_response = self.parent_srv.session.post(url, data=signin_req, **self.parent_srv.http_options) self.parent_srv._namespace.detect(server_response.content) self._check_status(server_response) - print(server_response.content) parsed_response = ET.fromstring(server_response.content) site_id = parsed_response.find('.//t:site', namespaces=self.parent_srv.namespace).get('id', None) user_id = parsed_response.find('.//t:user', namespaces=self.parent_srv.namespace).get('id', None) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 9764892c4..f35ccf172 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -53,16 +53,13 @@ def signin_req(self, auth_item): credentials_element = ET.SubElement(xml_request, 'credentials') for attribute_name, attribute_value in auth_item.credentials.items(): credentials_element.attrib[attribute_name] = attribute_value - print("CE: {}".format(ET.tostring(credentials_element))) site_element = ET.SubElement(credentials_element, 'site') site_element.attrib['contentUrl'] = auth_item.site_id - print("SE: {}".format(ET.tostring(site_element))) if auth_item.user_id_to_impersonate: user_element = ET.SubElement(credentials_element, 'user') user_element.attrib['id'] = auth_item.user_id_to_impersonate - print("UE: {}".format(ET.tostring(user_element))) return ET.tostring(xml_request) From f4549e02aea4171fc862c707410bf0d75df47344 Mon Sep 17 00:00:00 2001 From: Russell Hay Date: Mon, 11 Nov 2019 09:12:55 -0800 Subject: [PATCH 10/17] pep8 fixes in test --- test/test_datasource.py | 6 +++--- test/test_webhook.py | 2 -- test/test_workbook.py | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/test/test_datasource.py b/test/test_datasource.py index d19aeade1..c90cf4601 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -230,7 +230,7 @@ def test_publish(self): self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', new_datasource.owner_id) def test_publish_async(self): - self.server.version="3.0" + self.server.version = "3.0" baseurl = self.server.datasources.baseurl response_xml = read_xml_asset(PUBLISH_XML_ASYNC) with requests_mock.mock() as m: @@ -358,5 +358,5 @@ def test_synchronous_publish_timeout_error(self): publish_mode = self.server.PublishMode.CreateNew self.assertRaisesRegex(InternalServerError, 'Please use asynchronous publishing to avoid timeouts.', - self.server.datasources.publish, new_datasource, - asset('SampleDS.tds'), publish_mode) + self.server.datasources.publish, new_datasource, + asset('SampleDS.tds'), publish_mode) diff --git a/test/test_webhook.py b/test/test_webhook.py index 2d10bf71b..ef040f391 100644 --- a/test/test_webhook.py +++ b/test/test_webhook.py @@ -29,8 +29,6 @@ def test_get(self): m.get(self.baseurl, text=response_xml) webhooks, _ = self.server.webhooks.get() - - def test_get_before_signin(self): self.server._auth_token = None self.assertRaises(TSC.NotSignedInError, self.server.webhooks.get) diff --git a/test/test_workbook.py b/test/test_workbook.py index cc47eebb3..714f28941 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -500,4 +500,4 @@ def test_synchronous_publish_timeout_error(self): publish_mode = self.server.PublishMode.CreateNew self.assertRaisesRegex(InternalServerError, 'Please use asynchronous publishing to avoid timeouts', - self.server.workbooks.publish, new_workbook, asset('SampleWB.twbx'), publish_mode) + self.server.workbooks.publish, new_workbook, asset('SampleWB.twbx'), publish_mode) From 906b079c1ac0f3dafe1b7bf0bbaef563a880bcf5 Mon Sep 17 00:00:00 2001 From: Russell Hay Date: Mon, 11 Nov 2019 09:14:28 -0800 Subject: [PATCH 11/17] fixing pep8 failures in tableauserverclient --- tableauserverclient/models/__init__.py | 2 +- tableauserverclient/models/webhook_item.py | 2 +- tableauserverclient/server/endpoint/__init__.py | 1 - tableauserverclient/server/endpoint/webhooks_endpoint.py | 1 + tableauserverclient/server/request_factory.py | 1 - 5 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tableauserverclient/models/__init__.py b/tableauserverclient/models/__init__.py index c0c64904d..172877060 100644 --- a/tableauserverclient/models/__init__.py +++ b/tableauserverclient/models/__init__.py @@ -24,4 +24,4 @@ from .subscription_item import SubscriptionItem from .permissions_item import PermissionsRule, Permission from .webhook_item import WebhookItem -from .personal_access_token_auth import PersonalAccessTokenAuth \ No newline at end of file +from .personal_access_token_auth import PersonalAccessTokenAuth diff --git a/tableauserverclient/models/webhook_item.py b/tableauserverclient/models/webhook_item.py index 5f4930319..adefba25f 100644 --- a/tableauserverclient/models/webhook_item.py +++ b/tableauserverclient/models/webhook_item.py @@ -51,4 +51,4 @@ def _parse_element(webhook_xml, ns): return id, name, url def __repr__(self): - return "".format(self.id, self.name, self.url, self.event) \ No newline at end of file + return "".format(self.id, self.name, self.url, self.event) diff --git a/tableauserverclient/server/endpoint/__init__.py b/tableauserverclient/server/endpoint/__init__.py index 2808e7a6c..34c45a89a 100644 --- a/tableauserverclient/server/endpoint/__init__.py +++ b/tableauserverclient/server/endpoint/__init__.py @@ -18,4 +18,3 @@ from .views_endpoint import Views from .webhooks_endpoint import Webhooks from .workbooks_endpoint import Workbooks - diff --git a/tableauserverclient/server/endpoint/webhooks_endpoint.py b/tableauserverclient/server/endpoint/webhooks_endpoint.py index c1d194614..30f3bf565 100644 --- a/tableauserverclient/server/endpoint/webhooks_endpoint.py +++ b/tableauserverclient/server/endpoint/webhooks_endpoint.py @@ -5,6 +5,7 @@ import logging logger = logging.getLogger('tableau.endpoint.webhooks') + class Webhooks(Endpoint): def __init__(self, parent_srv): super(Webhooks, self).__init__(parent_srv) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index f35ccf172..ddd99a0fe 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -594,4 +594,3 @@ class RequestFactory(object): User = UserRequest() Workbook = WorkbookRequest() Webhook = WebhookRequest() - From 5786451b37211727a369cb1a38588318a84224d9 Mon Sep 17 00:00:00 2001 From: Russell Hay Date: Mon, 11 Nov 2019 09:55:58 -0800 Subject: [PATCH 12/17] Make token optional if it's set in the environment --- samples/list.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/samples/list.py b/samples/list.py index 88165cd81..e103eb862 100644 --- a/samples/list.py +++ b/samples/list.py @@ -7,6 +7,8 @@ import argparse import getpass import logging +import os +import sys import tableauserverclient as TSC @@ -16,7 +18,7 @@ def main(): 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') - parser.add_argument('--token', '-t', required=True, help='personal access token for logging in') + 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)') @@ -24,13 +26,17 @@ def main(): parser.add_argument('resource_type', choices=['workbook', 'datasource', 'project', 'view', 'job', 'webhooks']) args = parser.parse_args() + 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.PersonalAccessTokenAuth(args.token_name, args.token, site_id=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 = { From c85d6f30828f5c165043b06415500382e72afa76 Mon Sep 17 00:00:00 2001 From: Russell Hay Date: Mon, 11 Nov 2019 14:09:34 -0800 Subject: [PATCH 13/17] read events properly --- tableauserverclient/models/webhook_item.py | 48 ++++++++++++++++--- tableauserverclient/server/request_factory.py | 1 - test/assets/webhook_get.xml | 4 +- test/test_webhook.py | 11 +++++ 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/tableauserverclient/models/webhook_item.py b/tableauserverclient/models/webhook_item.py index adefba25f..71810cd3b 100644 --- a/tableauserverclient/models/webhook_item.py +++ b/tableauserverclient/models/webhook_item.py @@ -5,7 +5,16 @@ from .view_item import ViewItem from .permissions_item import PermissionsRule from ..datetime_helpers import parse_datetime -import copy +import re + + +NAMESPACE_RE = re.compile(r'^{.*}') + + +def _parse_event(events): + event = events[0] + # Strip out the namespace from the tag name + return NAMESPACE_RE.sub('', event.tag) class WebhookItem(object): @@ -14,20 +23,32 @@ def __init__(self): self.name = None self.url = None self._event = None + self.owner_id = None - def _set_values(self, id, name, url): + def _set_values(self, id, name, url, event, owner_id): if id is not None: self._id = id if name: self.name = name if url: self.url = url + if event: + self._event = event + if owner_id: + self.owner_id = owner_id @property def id(self): return self._id @property - def event(self): return "event" + def event(self): + if self._event: + return self._event.replace("webhook-source-event-", "") + return None + + @event.setter + def event(self, value): + self._event = "webhook-source-event-{}".format(value) @classmethod def from_response(cls, resp, ns): @@ -35,10 +56,10 @@ def from_response(cls, resp, ns): parsed_response = ET.fromstring(resp) all_webhooks_xml = parsed_response.findall('.//t:webhook', namespaces=ns) for webhook_xml in all_webhooks_xml: - (id, name, url) = cls._parse_element(webhook_xml, ns) + values = cls._parse_element(webhook_xml, ns) webhook_item = cls() - webhook_item._set_values(id, name, url) + webhook_item._set_values(*values) all_webhooks_items.append(webhook_item) return all_webhooks_items @@ -46,9 +67,22 @@ def from_response(cls, resp, ns): def _parse_element(webhook_xml, ns): id = webhook_xml.get('id', None) name = webhook_xml.get('name', None) - url = webhook_xml.get('.//t:webhook-destination-http[@url]') - return id, name, url + url = None + url_tag = webhook_xml.find('.//t:webhook-destination-http', namespaces=ns) + if url_tag is not None: + url = url_tag.get('url', None) + + event = webhook_xml.findall('.//t:webhook-source/*', namespaces=ns) + if event is not None and len(event) > 0: + event = _parse_event(event) + + owner_id = None + owner_tag = webhook_xml.find('.//t:owner', namespaces=ns) + if owner_tag is not None: + owner_id = owner_tag.get('id', None) + + return id, name, url, event, owner_id def __repr__(self): return "".format(self.id, self.name, self.url, self.event) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index ddd99a0fe..a216a9117 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -558,7 +558,6 @@ def empty_req(self, xml_request): class WebhookRequest(object): @_tsrequest_wrapped def create_req(self, xml_request, webhook_item): - print(webhook_item) webhook = ET.SubElement(xml_request, 'webhook') webhook.attrib['name'] = webhook_item.name diff --git a/test/assets/webhook_get.xml b/test/assets/webhook_get.xml index adf74109a..7d527fc00 100644 --- a/test/assets/webhook_get.xml +++ b/test/assets/webhook_get.xml @@ -3,7 +3,7 @@ - + @@ -11,4 +11,4 @@ - \ No newline at end of file + diff --git a/test/test_webhook.py b/test/test_webhook.py index ef040f391..c42fe350a 100644 --- a/test/test_webhook.py +++ b/test/test_webhook.py @@ -28,6 +28,14 @@ def test_get(self): with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) webhooks, _ = self.server.webhooks.get() + self.assertEqual(len(webhooks), 1) + webhook = webhooks[0] + + self.assertEqual(webhook.url, "url") + self.assertEqual(webhook.event, "datasource-created") + self.assertEqual(webhook.owner_id, "webhook_owner_luid") + self.assertEqual(webhook.name, "webhook-name") + self.assertEqual(webhook.id, "webhook-id") def test_get_before_signin(self): self.server._auth_token = None @@ -49,5 +57,8 @@ def test_create(self): new_webhook = TSC.WebhookItem() new_webhook.name = "Test Webhook" new_webhook.url = "http://ifttt.com/maker-url" + new_webhook.event = "webhook-source-event-datasource-created" new_webhook = self.server.webhooks.create(new_webhook) + + self.assertNotEqual(new_webhook.id, None) From e608b8b94568bbe1358caec6f5d6a71b708326c9 Mon Sep 17 00:00:00 2001 From: Russell Hay Date: Mon, 11 Nov 2019 14:10:32 -0800 Subject: [PATCH 14/17] read events properly --- tableauserverclient/server/request_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index a216a9117..ec0a4e784 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -562,7 +562,7 @@ def create_req(self, xml_request, webhook_item): webhook.attrib['name'] = webhook_item.name source = ET.SubElement(webhook, 'webhook-source') - # TODO: Add Event Type + event = ET.SubElement(source, webhook._event) destination = ET.SubElement(webhook, 'webhook-destination') post = ET.SubElement(destination, 'webhook-destination-http') From 6bc137180f9a1b59a6f4590f5ccdaf17164538da Mon Sep 17 00:00:00 2001 From: Russell Hay Date: Mon, 11 Nov 2019 14:33:56 -0800 Subject: [PATCH 15/17] fix request generation --- tableauserverclient/models/webhook_item.py | 2 +- tableauserverclient/server/__init__.py | 2 +- tableauserverclient/server/request_factory.py | 2 +- test/assets/webhook_create_request.xml | 1 + test/test_webhook.py | 13 +++++++++++++ 5 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 test/assets/webhook_create_request.xml diff --git a/tableauserverclient/models/webhook_item.py b/tableauserverclient/models/webhook_item.py index 71810cd3b..17c6ddcec 100644 --- a/tableauserverclient/models/webhook_item.py +++ b/tableauserverclient/models/webhook_item.py @@ -33,7 +33,7 @@ def _set_values(self, id, name, url, event, owner_id): if url: self.url = url if event: - self._event = event + self.event = event if owner_id: self.owner_id = owner_id diff --git a/tableauserverclient/server/__init__.py b/tableauserverclient/server/__init__.py index a76fd3246..f382d0dba 100644 --- a/tableauserverclient/server/__init__.py +++ b/tableauserverclient/server/__init__.py @@ -5,7 +5,7 @@ from .. import ConnectionItem, DatasourceItem, DatabaseItem, JobItem, BackgroundJobItem, \ GroupItem, PaginationItem, ProjectItem, ScheduleItem, SiteItem, TableauAuth,\ UserItem, ViewItem, WorkbookItem, TableItem, TaskItem, SubscriptionItem, \ - PermissionsRule, Permission, ColumnItem, FlowItem + PermissionsRule, Permission, ColumnItem, FlowItem, WebhookItem from .endpoint import Auth, Datasources, Endpoint, Groups, Projects, Schedules, \ Sites, Tables, Users, Views, Workbooks, Subscriptions, ServerResponseError, \ MissingRequiredFieldError, Flows diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index ec0a4e784..8001a1e6c 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -562,7 +562,7 @@ def create_req(self, xml_request, webhook_item): webhook.attrib['name'] = webhook_item.name source = ET.SubElement(webhook, 'webhook-source') - event = ET.SubElement(source, webhook._event) + event = ET.SubElement(source, webhook_item._event) destination = ET.SubElement(webhook, 'webhook-destination') post = ET.SubElement(destination, 'webhook-destination-http') diff --git a/test/assets/webhook_create_request.xml b/test/assets/webhook_create_request.xml new file mode 100644 index 000000000..0578c2c48 --- /dev/null +++ b/test/assets/webhook_create_request.xml @@ -0,0 +1 @@ + diff --git a/test/test_webhook.py b/test/test_webhook.py index c42fe350a..3f96652db 100644 --- a/test/test_webhook.py +++ b/test/test_webhook.py @@ -2,6 +2,7 @@ import os import requests_mock import tableauserverclient as TSC +from tableauserverclient.server import RequestFactory, WebhookItem from ._utils import read_xml_asset, read_xml_assets, asset @@ -9,6 +10,7 @@ GET_XML = asset('webhook_get.xml') CREATE_XML = asset('webhook_create.xml') +CREATE_REQUEST_XML = asset('webhook_create_request.xml') class WebhookTests(unittest.TestCase): @@ -62,3 +64,14 @@ def test_create(self): new_webhook = self.server.webhooks.create(new_webhook) self.assertNotEqual(new_webhook.id, None) + + def test_request_factory(self): + with open(CREATE_REQUEST_XML, 'rb') as f: + webhook_request_expected = f.read().decode('utf-8') + + webhook_item = WebhookItem() + webhook_item._set_values("webhook-id", "webhook-name", "url", "api-event-name", + None) + webhook_request_actual = '{}\n'.format(RequestFactory.Webhook.create_req(webhook_item).decode('utf-8')) + self.maxDiff = None + self.assertEqual(webhook_request_expected, webhook_request_actual) From 61d6d8b9dbaf61fdd8639bd1d9a37260f8f6486a Mon Sep 17 00:00:00 2001 From: Russell Hay Date: Mon, 11 Nov 2019 14:38:28 -0800 Subject: [PATCH 16/17] fix pep8 error --- test/test_webhook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_webhook.py b/test/test_webhook.py index 3f96652db..bdf25bb19 100644 --- a/test/test_webhook.py +++ b/test/test_webhook.py @@ -71,7 +71,7 @@ def test_request_factory(self): webhook_item = WebhookItem() webhook_item._set_values("webhook-id", "webhook-name", "url", "api-event-name", - None) + None) webhook_request_actual = '{}\n'.format(RequestFactory.Webhook.create_req(webhook_item).decode('utf-8')) self.maxDiff = None self.assertEqual(webhook_request_expected, webhook_request_actual) From 302263e4f11ce7ccac4cd355ee077d2a2953441b Mon Sep 17 00:00:00 2001 From: Russell Hay Date: Tue, 12 Nov 2019 09:58:54 -0800 Subject: [PATCH 17/17] Tyler's feedback --- tableauserverclient/models/webhook_item.py | 3 ++- tableauserverclient/server/endpoint/webhooks_endpoint.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tableauserverclient/models/webhook_item.py b/tableauserverclient/models/webhook_item.py index 17c6ddcec..4b1a350ee 100644 --- a/tableauserverclient/models/webhook_item.py +++ b/tableauserverclient/models/webhook_item.py @@ -38,7 +38,8 @@ def _set_values(self, id, name, url, event, owner_id): self.owner_id = owner_id @property - def id(self): return self._id + def id(self): + return self._id @property def event(self): diff --git a/tableauserverclient/server/endpoint/webhooks_endpoint.py b/tableauserverclient/server/endpoint/webhooks_endpoint.py index 30f3bf565..c1e188982 100644 --- a/tableauserverclient/server/endpoint/webhooks_endpoint.py +++ b/tableauserverclient/server/endpoint/webhooks_endpoint.py @@ -19,9 +19,9 @@ def get(self, req_options=None): logger.info('Querying all Webhooks on site') url = self.baseurl server_response = self.get_request(url, req_options) - all_workbook_items = WebhookItem.from_response(server_response.content, self.parent_srv.namespace) - pagination_item = PaginationItem.from_single_page_list(all_workbook_items) - return all_workbook_items, pagination_item + all_webhook_items = WebhookItem.from_response(server_response.content, self.parent_srv.namespace) + pagination_item = PaginationItem.from_single_page_list(all_webhook_items) + return all_webhook_items, pagination_item @api(version="3.6") def get_by_id(self, webhook_id):