From ad7962276aff8ffe3232973ea9add34b05f080a9 Mon Sep 17 00:00:00 2001 From: Mariano Teixeira Date: Mon, 9 Oct 2017 14:25:11 -0700 Subject: [PATCH 1/9] Adding multi-ds support to sample code --- .../server/endpoint/datasources_endpoint.py | 6 ++-- tableauserverclient/server/request_factory.py | 34 ++++++++++++------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 3d4c070fb..e26c3f4ae 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -116,7 +116,7 @@ def update(self, datasource_item): # Publish datasource @api(version="2.0") - def publish(self, datasource_item, file_path, mode, connection_credentials=None): + def publish(self, datasource_item, file_path, mode, connections=None): if not os.path.isfile(file_path): error = "File path does not lead to an existing file." raise IOError(error) @@ -145,7 +145,7 @@ def publish(self, datasource_item, file_path, mode, connection_credentials=None) upload_session_id = Fileuploads.upload_chunks(self.parent_srv, file_path) url = "{0}&uploadSessionId={1}".format(url, upload_session_id) xml_request, content_type = RequestFactory.Datasource.publish_req_chunked(datasource_item, - connection_credentials) + connections) else: logger.info('Publishing {0} to server'.format(filename)) with open(file_path, 'rb') as f: @@ -153,7 +153,7 @@ def publish(self, datasource_item, file_path, mode, connection_credentials=None) xml_request, content_type = RequestFactory.Datasource.publish_req(datasource_item, filename, file_contents, - connection_credentials) + connections) server_response = self.post_request(url, xml_request, content_type) new_datasource = DatasourceItem.from_response(server_response.content)[0] logger.info('Published {0} (ID: {1})'.format(filename, new_datasource.id)) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 439f517cb..2776f132e 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -278,7 +278,7 @@ def add_req(self, user_item): class WorkbookRequest(object): - def _generate_xml(self, workbook_item, connection_credentials=None): + def _generate_xml(self, workbook_item, connections=None): xml_request = ET.Element('tsRequest') workbook_element = ET.SubElement(xml_request, 'workbook') workbook_element.attrib['name'] = workbook_item.name @@ -286,14 +286,22 @@ def _generate_xml(self, workbook_item, connection_credentials=None): workbook_element.attrib['showTabs'] = str(workbook_item.show_tabs).lower() project_element = ET.SubElement(workbook_element, 'project') project_element.attrib['id'] = workbook_item.project_id - if connection_credentials: - credentials_element = ET.SubElement(workbook_element, 'connectionCredentials') - credentials_element.attrib['name'] = connection_credentials.name - credentials_element.attrib['password'] = connection_credentials.password - credentials_element.attrib['embed'] = 'true' if connection_credentials.embed else 'false' - - if connection_credentials.oauth: - credentials_element.attrib['oAuth'] = 'true' + if connections is not None: + connections_element = ET.SubElement(workbook_element, 'connections') + for connection in connections: + if connection: + connection_element = ET.SubElement(connections_element, 'connection') + connection_element.attrib['serverAddress'] = connection.server_address + if connection.server_port: + connection_element.attrib['serverPort'] = connection.server_port + if connection.connection_credentials: + connection_credentials = connection.connection_credentials + credentials_element = ET.SubElement(connection_element, 'connectionCredentials') + credentials_element.attrib['name'] = connection_credentials.name + credentials_element.attrib['password'] = connection_credentials.password + credentials_element.attrib['embed'] = 'true' if connection_credentials.embed else 'false' + if connection_credentials.oauth: + credentials_element.attrib['oAuth'] = 'true' return ET.tostring(xml_request) def update_req(self, workbook_item): @@ -309,15 +317,15 @@ def update_req(self, workbook_item): owner_element.attrib['id'] = workbook_item.owner_id return ET.tostring(xml_request) - def publish_req(self, workbook_item, filename, file_contents, connection_credentials=None): - xml_request = self._generate_xml(workbook_item, connection_credentials) + def publish_req(self, workbook_item, filename, file_contents, connections=None): + xml_request = self._generate_xml(workbook_item, connections) parts = {'request_payload': ('', xml_request, 'text/xml'), 'tableau_workbook': (filename, file_contents, 'application/octet-stream')} return _add_multipart(parts) - def publish_req_chunked(self, workbook_item, connection_credentials=None): - xml_request = self._generate_xml(workbook_item, connection_credentials) + def publish_req_chunked(self, workbook_item, connections=None): + xml_request = self._generate_xml(workbook_item, connections) parts = {'request_payload': ('', xml_request, 'text/xml')} return _add_multipart(parts) From 1d4e08f4c3d51ed3191a633e053632ac3e3a8c6d Mon Sep 17 00:00:00 2001 From: Mariano Teixeira Date: Mon, 4 Dec 2017 12:39:51 -0800 Subject: [PATCH 2/9] Saving changes to multids branch --- samples/publish_workbook.py | 16 +++++++++++++++- tableauserverclient/models/connection_item.py | 1 + .../server/endpoint/workbooks_endpoint.py | 13 ++++++++++--- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/samples/publish_workbook.py b/samples/publish_workbook.py index 37d66d2dc..33c3a430e 100644 --- a/samples/publish_workbook.py +++ b/samples/publish_workbook.py @@ -19,6 +19,7 @@ import logging import tableauserverclient as TSC +from tableauserverclient import ConnectionCredentials, ConnectionItem def main(): @@ -49,11 +50,24 @@ def main(): # Step 2: Get all the projects on server, then look for the default one. all_projects, pagination_item = server.projects.get() default_project = next((project for project in all_projects if project.is_default()), None) + connection2 = ConnectionItem() + connection2.server_address = "db2.test.tsi.lan" + connection2.server_port = "50000" + connection2.connection_credentials = ConnectionCredentials("test", "p@ssw0rd", True) + connection1 = ConnectionItem() + connection1.server_address = "mssql.test.tsi.lan" + connection1.connection_credentials = ConnectionCredentials("test", "password", True) + all_connections = list() + all_connections.append(connection1) + all_connections.append(connection2) # Step 3: If default project is found, form a new workbook item and publish. if default_project is not None: new_workbook = TSC.WorkbookItem(default_project.id) - new_workbook = server.workbooks.publish(new_workbook, args.filepath, overwrite_true) + new_workbook = server.workbooks.publish(new_workbook, + args.filepath, + overwrite_true, + connections=all_connections) print("Workbook published. ID: {0}".format(new_workbook.id)) else: error = "The default project could not be found." diff --git a/tableauserverclient/models/connection_item.py b/tableauserverclient/models/connection_item.py index 8f2141b76..a40c4a3a1 100644 --- a/tableauserverclient/models/connection_item.py +++ b/tableauserverclient/models/connection_item.py @@ -13,6 +13,7 @@ def __init__(self): self.server_address = None self.server_port = None self.username = None + self.connection_credentials = None @property def datasource_id(self): diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 2f64790bc..88cec7cc6 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -145,7 +145,14 @@ def populate_preview_image(self, workbook_item): # Publishes workbook. Chunking method if file over 64MB @api(version="2.0") - def publish(self, workbook_item, file_path, mode, connection_credentials=None): + # @parameter_added_in(connections='2.8') + def publish(self, workbook_item, file_path, mode, connection_credentials=None, connections=None): + + if connection_credentials is not None: + import warnings + warnings.warn("connection_credendials is being deprecated. Use connections instead, see http://...", + DeprecationWarning) + if not os.path.isfile(file_path): error = "File path does not lead to an existing file." raise IOError(error) @@ -177,7 +184,7 @@ def publish(self, workbook_item, file_path, mode, connection_credentials=None): upload_session_id = Fileuploads.upload_chunks(self.parent_srv, file_path) url = "{0}&uploadSessionId={1}".format(url, upload_session_id) xml_request, content_type = RequestFactory.Workbook.publish_req_chunked(workbook_item, - connection_credentials) + connections=connections) else: logger.info('Publishing {0} to server'.format(filename)) with open(file_path, 'rb') as f: @@ -185,7 +192,7 @@ def publish(self, workbook_item, file_path, mode, connection_credentials=None): xml_request, content_type = RequestFactory.Workbook.publish_req(workbook_item, filename, file_contents, - connection_credentials) + connections=connections) server_response = self.post_request(url, xml_request, content_type) new_workbook = WorkbookItem.from_response(server_response.content)[0] logger.info('Published {0} (ID: {1})'.format(filename, new_workbook.id)) From d200bcbf5c027e54d815355bdc5db4ce9da9260a Mon Sep 17 00:00:00 2001 From: Tyler Doyle Date: Sat, 10 Mar 2018 14:59:47 -0800 Subject: [PATCH 3/9] checkpoint, both single and multi are working --- .../server/endpoint/endpoint.py | 5 +- .../server/endpoint/workbooks_endpoint.py | 4 +- tableauserverclient/server/request_factory.py | 56 +++++++++++++------ 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/tableauserverclient/server/endpoint/endpoint.py b/tableauserverclient/server/endpoint/endpoint.py index e78b2e0cd..93d912914 100644 --- a/tableauserverclient/server/endpoint/endpoint.py +++ b/tableauserverclient/server/endpoint/endpoint.py @@ -32,11 +32,12 @@ def _safe_to_log(server_response): '''Checks if the server_response content is not xml (eg binary image or zip) and and replaces it with a constant ''' - ALLOWED_CONTENT_TYPES = ('application/xml',) + ALLOWED_CONTENT_TYPES = ('application/xml', 'application/xml;charset=utf-8') if server_response.headers.get('Content-Type', None) not in ALLOWED_CONTENT_TYPES: + print(server_response.headers) return '[Truncated File Contents]' else: - return server_response.content + return server_response.content[:300] def _make_request(self, method, url, content=None, request_object=None, auth_token=None, content_type=None, parameters=None): diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index a9bf3122f..47895cba4 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -246,7 +246,9 @@ def publish(self, workbook_item, file_path, mode, connection_credentials=None, c xml_request, content_type = RequestFactory.Workbook.publish_req(workbook_item, filename, file_contents, - connections=connections) + connections=connections, + connection_credentials=connection_credentials) + logger.debug('Request xml: {0} '.format(xml_request[:1000])) server_response = self.post_request(url, xml_request, content_type) new_workbook = WorkbookItem.from_response(server_response.content, self.parent_srv.namespace)[0] logger.info('Published {0} (ID: {1})'.format(filename, new_workbook.id)) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 78798e0ec..c1647674c 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -313,7 +313,7 @@ def add_req(self, user_item): class WorkbookRequest(object): - def _generate_xml(self, workbook_item, connections=None): + def _generate_xml(self, workbook_item, connections=None, connection_credentials=None): xml_request = ET.Element('tsRequest') workbook_element = ET.SubElement(xml_request, 'workbook') workbook_element.attrib['name'] = workbook_item.name @@ -321,24 +321,44 @@ def _generate_xml(self, workbook_item, connections=None): workbook_element.attrib['showTabs'] = str(workbook_item.show_tabs).lower() project_element = ET.SubElement(workbook_element, 'project') project_element.attrib['id'] = workbook_item.project_id + + if connection_credentials is not None: + if connections is not Nont: + raise RuntimeError("Can't have both set.") + + import warnings + warnings.warn('Probably not a good idea') + + connections = [connection_credentials] + if connections is not None: - connections_element = ET.SubElement(workbook_element, 'connections') - for connection in connections: - if connection: - connection_element = ET.SubElement(connections_element, 'connection') - connection_element.attrib['serverAddress'] = connection.server_address - if connection.server_port: - connection_element.attrib['serverPort'] = connection.server_port - if connection.connection_credentials: - connection_credentials = connection.connection_credentials - credentials_element = ET.SubElement(connection_element, 'connectionCredentials') - credentials_element.attrib['name'] = connection_credentials.name - credentials_element.attrib['password'] = connection_credentials.password - credentials_element.attrib['embed'] = 'true' if connection_credentials.embed else 'false' - if connection_credentials.oauth: - credentials_element.attrib['oAuth'] = 'true' + + if len(connections) == 1: # If this is only one, use the back-compact safe bare connCreds + self._make_credentials_element(workbook_element, connection_credentials) + + else: + connections_element = ET.SubElement(workbook_element, 'connections') + for connection in connections: + self._make_connections_element(connections_element, connection) return ET.tostring(xml_request) + def _make_connections_element(self, connections_element, connection): + connection_element = ET.SubElement(connections_element, 'connection') + connection_element.attrib['serverAddress'] = connection.server_address + if connection.server_port: + connection_element.attrib['serverPort'] = connection.server_port + if connection.connection_credentials: + connection_credentials = connection.connection_credentials + self._make_credentials_element(connection_element, connection_credentials) + + def _make_credentials_element(self, parent_element, connection_credentials): + credentials_element = ET.SubElement(parent_element, 'connectionCredentials') + credentials_element.attrib['name'] = connection_credentials.name + credentials_element.attrib['password'] = connection_credentials.password + credentials_element.attrib['embed'] = 'true' if connection_credentials.embed else 'false' + if connection_credentials.oauth: + credentials_element.attrib['oAuth'] = 'true' + def update_req(self, workbook_item): xml_request = ET.Element('tsRequest') workbook_element = ET.SubElement(xml_request, 'workbook') @@ -352,8 +372,8 @@ def update_req(self, workbook_item): owner_element.attrib['id'] = workbook_item.owner_id return ET.tostring(xml_request) - def publish_req(self, workbook_item, filename, file_contents, connections=None): - xml_request = self._generate_xml(workbook_item, connections) + def publish_req(self, workbook_item, filename, file_contents, connection_credentials=None, connections=None): + xml_request = self._generate_xml(workbook_item, connections, connection_credentials) parts = {'request_payload': ('', xml_request, 'text/xml'), 'tableau_workbook': (filename, file_contents, 'application/octet-stream')} From 4eae2506832b74b6a01ca8085796cf94a236f7c4 Mon Sep 17 00:00:00 2001 From: Tyler Doyle Date: Sat, 10 Mar 2018 15:43:42 -0800 Subject: [PATCH 4/9] 2nd checkpoint, decided to not eb smart and just throw exceptions --- .../server/endpoint/endpoint.py | 1 - .../server/endpoint/workbooks_endpoint.py | 7 ++-- tableauserverclient/server/request_factory.py | 33 ++++++++----------- 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/tableauserverclient/server/endpoint/endpoint.py b/tableauserverclient/server/endpoint/endpoint.py index 93d912914..01c378a54 100644 --- a/tableauserverclient/server/endpoint/endpoint.py +++ b/tableauserverclient/server/endpoint/endpoint.py @@ -34,7 +34,6 @@ def _safe_to_log(server_response): ''' ALLOWED_CONTENT_TYPES = ('application/xml', 'application/xml;charset=utf-8') if server_response.headers.get('Content-Type', None) not in ALLOWED_CONTENT_TYPES: - print(server_response.headers) return '[Truncated File Contents]' else: return server_response.content[:300] diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 47895cba4..bbb8a66a3 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -199,7 +199,7 @@ def _get_wb_preview_image(self, workbook_item): # Publishes workbook. Chunking method if file over 64MB @api(version="2.0") - # @parameter_added_in(connections='2.8') + @parameter_added_in(connections='2.8') def publish(self, workbook_item, file_path, mode, connection_credentials=None, connections=None): if connection_credentials is not None: @@ -243,11 +243,12 @@ def publish(self, workbook_item, file_path, mode, connection_credentials=None, c logger.info('Publishing {0} to server'.format(filename)) with open(file_path, 'rb') as f: file_contents = f.read() + conn_creds = connection_credentials xml_request, content_type = RequestFactory.Workbook.publish_req(workbook_item, filename, file_contents, - connections=connections, - connection_credentials=connection_credentials) + connection_credentials=conn_creds, + connections=connections) logger.debug('Request xml: {0} '.format(xml_request[:1000])) server_response = self.post_request(url, xml_request, content_type) new_workbook = WorkbookItem.from_response(server_response.content, self.parent_srv.namespace)[0] diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index c1647674c..245e1e7cf 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -313,7 +313,7 @@ def add_req(self, user_item): class WorkbookRequest(object): - def _generate_xml(self, workbook_item, connections=None, connection_credentials=None): + def _generate_xml(self, workbook_item, connection_credentials=None, connections=None): xml_request = ET.Element('tsRequest') workbook_element = ET.SubElement(xml_request, 'workbook') workbook_element.attrib['name'] = workbook_item.name @@ -322,36 +322,28 @@ def _generate_xml(self, workbook_item, connections=None, connection_credentials= project_element = ET.SubElement(workbook_element, 'project') project_element.attrib['id'] = workbook_item.project_id - if connection_credentials is not None: - if connections is not Nont: - raise RuntimeError("Can't have both set.") + if connection_credentials is not None and connections is not None: + raise RuntimeError('You cannot set both `connections` and `connection_credentials`') - import warnings - warnings.warn('Probably not a good idea') - - connections = [connection_credentials] + if connection_credentials is not None: + self._add_credentials_element(workbook_element, connection_credentials) if connections is not None: - - if len(connections) == 1: # If this is only one, use the back-compact safe bare connCreds - self._make_credentials_element(workbook_element, connection_credentials) - - else: - connections_element = ET.SubElement(workbook_element, 'connections') - for connection in connections: - self._make_connections_element(connections_element, connection) + connections_element = ET.SubElement(workbook_element, 'connections') + for connection in connections: + self._add_connections_element(connections_element, connection) return ET.tostring(xml_request) - def _make_connections_element(self, connections_element, connection): + def _add_connections_element(self, connections_element, connection): connection_element = ET.SubElement(connections_element, 'connection') connection_element.attrib['serverAddress'] = connection.server_address if connection.server_port: connection_element.attrib['serverPort'] = connection.server_port if connection.connection_credentials: connection_credentials = connection.connection_credentials - self._make_credentials_element(connection_element, connection_credentials) + self._add_credentials_element(connection_element, connection_credentials) - def _make_credentials_element(self, parent_element, connection_credentials): + def _add_credentials_element(self, parent_element, connection_credentials): credentials_element = ET.SubElement(parent_element, 'connectionCredentials') credentials_element.attrib['name'] = connection_credentials.name credentials_element.attrib['password'] = connection_credentials.password @@ -373,7 +365,8 @@ def update_req(self, workbook_item): return ET.tostring(xml_request) def publish_req(self, workbook_item, filename, file_contents, connection_credentials=None, connections=None): - xml_request = self._generate_xml(workbook_item, connections, connection_credentials) + xml_request = self._generate_xml(workbook_item, connection_credentials=connection_credentials, + connections=connections) parts = {'request_payload': ('', xml_request, 'text/xml'), 'tableau_workbook': (filename, file_contents, 'application/octet-stream')} From 3a610bf9cb01c6615640d329668e6dcc997c996e Mon Sep 17 00:00:00 2001 From: Tyler Doyle Date: Sat, 10 Mar 2018 16:09:38 -0800 Subject: [PATCH 5/9] New checkpoint, add data sources and chunked requests -- untested --- .../server/endpoint/datasources_endpoint.py | 5 +- .../server/endpoint/workbooks_endpoint.py | 2 + tableauserverclient/server/request_factory.py | 84 +++++++++++-------- 3 files changed, 56 insertions(+), 35 deletions(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index f9c83d4c0..8705c477a 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -151,7 +151,8 @@ def refresh(self, datasource_item): # Publish datasource @api(version="2.0") - def publish(self, datasource_item, file_path, mode, connections=None): + @parameter_added_in(connections="2.8") + def publish(self, datasource_item, file_path, mode, connection_credentials=None, connections=None): if not os.path.isfile(file_path): error = "File path does not lead to an existing file." raise IOError(error) @@ -180,6 +181,7 @@ def publish(self, datasource_item, file_path, mode, connections=None): upload_session_id = Fileuploads.upload_chunks(self.parent_srv, file_path) url = "{0}&uploadSessionId={1}".format(url, upload_session_id) xml_request, content_type = RequestFactory.Datasource.publish_req_chunked(datasource_item, + connection_credentials, connections) else: logger.info('Publishing {0} to server'.format(filename)) @@ -188,6 +190,7 @@ def publish(self, datasource_item, file_path, mode, connections=None): xml_request, content_type = RequestFactory.Datasource.publish_req(datasource_item, filename, file_contents, + connection_credentials, connections) server_response = self.post_request(url, xml_request, content_type) new_datasource = DatasourceItem.from_response(server_response.content, self.parent_srv.namespace)[0] diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index bbb8a66a3..ad79d28da 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -237,7 +237,9 @@ def publish(self, workbook_item, file_path, mode, connection_credentials=None, c logger.info('Publishing {0} to server with chunking method (workbook over 64MB)'.format(filename)) upload_session_id = Fileuploads.upload_chunks(self.parent_srv, file_path) url = "{0}&uploadSessionId={1}".format(url, upload_session_id) + conn_creds = connection_credentials xml_request, content_type = RequestFactory.Workbook.publish_req_chunked(workbook_item, + connection_credentials=conn_creds, connections=connections) else: logger.info('Publishing {0} to server'.format(filename)) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 245e1e7cf..5f0e5681e 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -25,6 +25,25 @@ def wrapper(self, *args, **kwargs): return wrapper +def _add_connections_element(connections_element, connection): + connection_element = ET.SubElement(connections_element, 'connection') + connection_element.attrib['serverAddress'] = connection.server_address + if connection.server_port: + connection_element.attrib['serverPort'] = connection.server_port + if connection.connection_credentials: + connection_credentials = connection.connection_credentials + self._add_credentials_element(connection_element, connection_credentials) + + +def _add_credentials_element(parent_element, connection_credentials): + credentials_element = ET.SubElement(parent_element, 'connectionCredentials') + credentials_element.attrib['name'] = connection_credentials.name + credentials_element.attrib['password'] = connection_credentials.password + credentials_element.attrib['embed'] = 'true' if connection_credentials.embed else 'false' + if connection_credentials.oauth: + credentials_element.attrib['oAuth'] = 'true' + + class AuthRequest(object): def signin_req(self, auth_item): xml_request = ET.Element('tsRequest') @@ -40,20 +59,31 @@ def signin_req(self, auth_item): class DatasourceRequest(object): - def _generate_xml(self, datasource_item, connection_credentials=None): + def _generate_xml(self, datasource_item, connection_credentials=None, connections=None): xml_request = ET.Element('tsRequest') datasource_element = ET.SubElement(xml_request, 'datasource') datasource_element.attrib['name'] = datasource_item.name project_element = ET.SubElement(datasource_element, 'project') project_element.attrib['id'] = datasource_item.project_id - if connection_credentials: - credentials_element = ET.SubElement(datasource_element, 'connectionCredentials') - credentials_element.attrib['name'] = connection_credentials.name - credentials_element.attrib['password'] = connection_credentials.password - credentials_element.attrib['embed'] = 'true' if connection_credentials.embed else 'false' - - if connection_credentials.oauth: - credentials_element.attrib['oAuth'] = 'true' + # if connection_credentials: + # credentials_element = ET.SubElement(datasource_element, 'connectionCredentials') + # credentials_element.attrib['name'] = connection_credentials.name + # credentials_element.attrib['password'] = connection_credentials.password + # credentials_element.attrib['embed'] = 'true' if connection_credentials.embed else 'false' + + # if connection_credentials.oauth: + # credentials_element.attrib['oAuth'] = 'true' + # return ET.tostring(xml_request) + if connection_credentials is not None and connections is not None: + raise RuntimeError('You cannot set both `connections` and `connection_credentials`') + + if connection_credentials is not None: + _add_credentials_element(datasource_element, connection_credentials) + + if connections is not None: + connections_element = ET.SubElement(datasource_element, 'connections') + for connection in connections: + _add_connections_element(connections_element, connection) return ET.tostring(xml_request) def update_req(self, datasource_item): @@ -73,15 +103,15 @@ def update_req(self, datasource_item): return ET.tostring(xml_request) - def publish_req(self, datasource_item, filename, file_contents, connection_credentials=None): - xml_request = self._generate_xml(datasource_item, connection_credentials) + def publish_req(self, datasource_item, filename, file_contents, connection_credentials=None, connections=None): + xml_request = self._generate_xml(datasource_item, connection_credentials, connections) parts = {'request_payload': ('', xml_request, 'text/xml'), 'tableau_datasource': (filename, file_contents, 'application/octet-stream')} return _add_multipart(parts) def publish_req_chunked(self, datasource_item, connection_credentials=None): - xml_request = self._generate_xml(datasource_item, connection_credentials) + xml_request = self._generate_xml(datasource_item, connection_credentials, connections) parts = {'request_payload': ('', xml_request, 'text/xml')} return _add_multipart(parts) @@ -325,32 +355,15 @@ def _generate_xml(self, workbook_item, connection_credentials=None, connections= if connection_credentials is not None and connections is not None: raise RuntimeError('You cannot set both `connections` and `connection_credentials`') - if connection_credentials is not None: - self._add_credentials_element(workbook_element, connection_credentials) + if connection_credentials is not None: + _add_credentials_element(workbook_element, connection_credentials) if connections is not None: connections_element = ET.SubElement(workbook_element, 'connections') for connection in connections: - self._add_connections_element(connections_element, connection) + _add_connections_element(connections_element, connection) return ET.tostring(xml_request) - def _add_connections_element(self, connections_element, connection): - connection_element = ET.SubElement(connections_element, 'connection') - connection_element.attrib['serverAddress'] = connection.server_address - if connection.server_port: - connection_element.attrib['serverPort'] = connection.server_port - if connection.connection_credentials: - connection_credentials = connection.connection_credentials - self._add_credentials_element(connection_element, connection_credentials) - - def _add_credentials_element(self, parent_element, connection_credentials): - credentials_element = ET.SubElement(parent_element, 'connectionCredentials') - credentials_element.attrib['name'] = connection_credentials.name - credentials_element.attrib['password'] = connection_credentials.password - credentials_element.attrib['embed'] = 'true' if connection_credentials.embed else 'false' - if connection_credentials.oauth: - credentials_element.attrib['oAuth'] = 'true' - def update_req(self, workbook_item): xml_request = ET.Element('tsRequest') workbook_element = ET.SubElement(xml_request, 'workbook') @@ -365,7 +378,8 @@ def update_req(self, workbook_item): return ET.tostring(xml_request) def publish_req(self, workbook_item, filename, file_contents, connection_credentials=None, connections=None): - xml_request = self._generate_xml(workbook_item, connection_credentials=connection_credentials, + xml_request = self._generate_xml(workbook_item, + connection_credentials=connection_credentials, connections=connections) parts = {'request_payload': ('', xml_request, 'text/xml'), @@ -373,7 +387,9 @@ def publish_req(self, workbook_item, filename, file_contents, connection_credent return _add_multipart(parts) def publish_req_chunked(self, workbook_item, connections=None): - xml_request = self._generate_xml(workbook_item, connections) + xml_request = self._generate_xml(workbook_item, + connection_credentials=connection_credentials, + connections=connections) parts = {'request_payload': ('', xml_request, 'text/xml')} return _add_multipart(parts) From 9260e49969d426c8342cd6245331bf13641aa3cc Mon Sep 17 00:00:00 2001 From: Tyler Doyle Date: Sun, 11 Mar 2018 00:45:52 -0800 Subject: [PATCH 6/9] Update tests after lots of fiddling --- .../models/connection_credentials.py | 12 +++++ tableauserverclient/models/connection_item.py | 30 ++++++++++++ tableauserverclient/server/request_factory.py | 12 +---- test/test_datasource.py | 42 +++++++++++++++++ test/test_workbook.py | 46 +++++++++++++++++++ 5 files changed, 132 insertions(+), 10 deletions(-) diff --git a/tableauserverclient/models/connection_credentials.py b/tableauserverclient/models/connection_credentials.py index 8c3a77925..c883a515a 100644 --- a/tableauserverclient/models/connection_credentials.py +++ b/tableauserverclient/models/connection_credentials.py @@ -32,3 +32,15 @@ def oauth(self): @property_is_boolean def oauth(self, value): self._oauth = value + + @classmethod + def from_xml_element(cls, parsed_response, ns): + connection_creds_xml = parsed_response.find('.//t:connectionCredentials', namespaces=ns) + + name = connection_creds_xml.get('name', None) + password = connection_creds_xml.get('password', None) + embed = connection_creds_xml.get('embed', None) + oAuth = connection_creds_xml.get('oAuth', None) + + connection_creds = cls(name, password, embed, oAuth) + return connection_creds diff --git a/tableauserverclient/models/connection_item.py b/tableauserverclient/models/connection_item.py index b9dd1b720..894cabe62 100644 --- a/tableauserverclient/models/connection_item.py +++ b/tableauserverclient/models/connection_item.py @@ -1,4 +1,5 @@ import xml.etree.ElementTree as ET +from .connection_credentials import ConnectionCredentials class ConnectionItem(object): @@ -52,3 +53,32 @@ def from_response(cls, resp, ns): connection_item._datasource_name = datasource_elem.get('name', None) all_connection_items.append(connection_item) return all_connection_items + + @classmethod + def from_xml_element(cls, parsed_response, ns): + ''' + + + + + + + + + ''' + all_connection_items = list() + all_connection_xml = parsed_response.findall('.//t:connection', namespaces=ns) + + for connection_xml in all_connection_xml: + connection_item = cls() + + connection_item.server_address = connection_xml.get('serverAddress', None) + connection_item.server_port = connection_xml.get('serverPort', None) + + connection_credentials = connection_xml.find('.//t:connectionCredentials', namespaces=ns) + + if connection_credentials is not None: + + connection_item.connection_credentials = ConnectionCredentials.from_xml_element(connection_credentials) + + return all_connection_items diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 5f0e5681e..5f8b153dc 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -32,7 +32,7 @@ def _add_connections_element(connections_element, connection): connection_element.attrib['serverPort'] = connection.server_port if connection.connection_credentials: connection_credentials = connection.connection_credentials - self._add_credentials_element(connection_element, connection_credentials) + _add_credentials_element(connection_element, connection_credentials) def _add_credentials_element(parent_element, connection_credentials): @@ -65,15 +65,7 @@ def _generate_xml(self, datasource_item, connection_credentials=None, connection datasource_element.attrib['name'] = datasource_item.name project_element = ET.SubElement(datasource_element, 'project') project_element.attrib['id'] = datasource_item.project_id - # if connection_credentials: - # credentials_element = ET.SubElement(datasource_element, 'connectionCredentials') - # credentials_element.attrib['name'] = connection_credentials.name - # credentials_element.attrib['password'] = connection_credentials.password - # credentials_element.attrib['embed'] = 'true' if connection_credentials.embed else 'false' - - # if connection_credentials.oauth: - # credentials_element.attrib['oAuth'] = 'true' - # return ET.tostring(xml_request) + if connection_credentials is not None and connections is not None: raise RuntimeError('You cannot set both `connections` and `connection_credentials`') diff --git a/test/test_datasource.py b/test/test_datasource.py index ff1546d62..ba2310345 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -1,8 +1,10 @@ import unittest import os import requests_mock +import xml.etree.ElementTree as ET import tableauserverclient as TSC from tableauserverclient.datetime_helpers import format_datetime +from tableauserverclient.server.request_factory import RequestFactory from ._utils import read_xml_asset, read_xml_assets, asset ADD_TAGS_XML = 'datasource_add_tags.xml' @@ -241,3 +243,43 @@ def test_publish_invalid_file_type(self): new_datasource = TSC.DatasourceItem('test', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') self.assertRaises(ValueError, self.server.datasources.publish, new_datasource, asset('SampleWB.twbx'), self.server.PublishMode.Append) + + def test_publish_multi_connection(self): + new_datasource = TSC.DatasourceItem(name='Sample', project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + connection1 = TSC.ConnectionItem() + connection1.server_address = 'mysql.test.com' + connection1.connection_credentials = TSC.ConnectionCredentials('test', 'secret', True) + connection2 = TSC.ConnectionItem() + connection2.server_address = 'pgsql.test.com' + connection2.connection_credentials = TSC.ConnectionCredentials('test', 'secret', True) + + response = RequestFactory.Datasource._generate_xml(new_datasource, connections=[connection1, connection2]) + connection_results = ET.fromstring(response).findall('.//connection') # Can't use ConnectionItem parser due to xml namespace problems + + self.assertEqual(connection_results[0].get('serverAddress', None), 'mysql.test.com') + self.assertEqual(connection_results[0].find('connectionCredentials').get('name', None), 'test') + self.assertEqual(connection_results[1].get('serverAddress', None), 'pgsql.test.com') + self.assertEqual(connection_results[1].find('connectionCredentials').get('password', None), 'secret') + + def test_publish_single_connection(self): + new_datasource = TSC.DatasourceItem(name='Sample', project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + connection_creds = TSC.ConnectionCredentials('test', 'secret', True) + + response = RequestFactory.Datasource._generate_xml(new_datasource, connection_credentials=connection_creds) + credentials = ET.fromstring(response).findall('.//connectionCredentials') # Can't use ConnectionItem parser due to xml namespace problems + self.assertEqual(len(credentials), 1) + self.assertEqual(credentials[0].get('name', None), 'test') + self.assertEqual(credentials[0].get('password', None), 'secret') + self.assertEqual(credentials[0].get('embed', None), 'true') + + def test_credentials_and_multi_connect_raises_exception(self): + new_datasource = TSC.DatasourceItem(name='Sample', project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + + connection_creds = TSC.ConnectionCredentials('test', 'secret', True) + + connection1 = TSC.ConnectionItem() + connection1.server_address = 'mysql.test.com' + connection1.connection_credentials = TSC.ConnectionCredentials('test', 'secret', True) + + with self.assertRaises(RuntimeError): + response = RequestFactory.Datasource._generate_xml(new_datasource, connection_credentials=connection_creds, connections=[connection1]) \ No newline at end of file diff --git a/test/test_workbook.py b/test/test_workbook.py index 8c36f0229..c39302ecb 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -2,7 +2,10 @@ import os import requests_mock import tableauserverclient as TSC +import xml.etree.ElementTree as ET + from tableauserverclient.datetime_helpers import format_datetime +from tableauserverclient.server.request_factory import RequestFactory TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') @@ -314,3 +317,46 @@ def test_publish_invalid_file_type(self): self.assertRaises(ValueError, self.server.workbooks.publish, new_workbook, os.path.join(TEST_ASSET_DIR, 'SampleDS.tds'), self.server.PublishMode.CreateNew) + + def test_publish_multi_connection(self): + new_workbook = TSC.WorkbookItem(name='Sample', show_tabs=False, + project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + connection1 = TSC.ConnectionItem() + connection1.server_address = 'mysql.test.com' + connection1.connection_credentials = TSC.ConnectionCredentials('test', 'secret', True) + connection2 = TSC.ConnectionItem() + connection2.server_address = 'pgsql.test.com' + connection2.connection_credentials = TSC.ConnectionCredentials('test', 'secret', True) + + response = RequestFactory.Workbook._generate_xml(new_workbook, connections=[connection1, connection2]) + connection_results = ET.fromstring(response).findall('.//connection') # Can't use ConnectionItem parser due to xml namespace problems + + self.assertEqual(connection_results[0].get('serverAddress', None), 'mysql.test.com') + self.assertEqual(connection_results[0].find('connectionCredentials').get('name', None), 'test') + self.assertEqual(connection_results[1].get('serverAddress', None), 'pgsql.test.com') + self.assertEqual(connection_results[1].find('connectionCredentials').get('password', None), 'secret') + + def test_publish_single_connection(self): + new_workbook = TSC.WorkbookItem(name='Sample', show_tabs=False, + project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + connection_creds = TSC.ConnectionCredentials('test', 'secret', True) + + response = RequestFactory.Workbook._generate_xml(new_workbook, connection_credentials=connection_creds) + credentials = ET.fromstring(response).findall('.//connectionCredentials') # Can't use ConnectionItem parser due to xml namespace problems + self.assertEqual(len(credentials), 1) + self.assertEqual(credentials[0].get('name', None), 'test') + self.assertEqual(credentials[0].get('password', None), 'secret') + self.assertEqual(credentials[0].get('embed', None), 'true') + + def test_credentials_and_multi_connect_raises_exception(self): + new_workbook = TSC.WorkbookItem(name='Sample', show_tabs=False, + project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + + connection_creds = TSC.ConnectionCredentials('test', 'secret', True) + + connection1 = TSC.ConnectionItem() + connection1.server_address = 'mysql.test.com' + connection1.connection_credentials = TSC.ConnectionCredentials('test', 'secret', True) + + with self.assertRaises(RuntimeError): + response = RequestFactory.Workbook._generate_xml(new_workbook, connection_credentials=connection_creds, connections=[connection1]) From 93ed3b318f5fb6d0ba5f583bfa91b9e49018c30e Mon Sep 17 00:00:00 2001 From: Tyler Doyle Date: Sun, 11 Mar 2018 18:36:10 -0700 Subject: [PATCH 7/9] Fixup test linting --- test/test_datasource.py | 19 ++++++++++++------- test/test_workbook.py | 18 +++++++++++------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/test/test_datasource.py b/test/test_datasource.py index ba2310345..112c698d0 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -254,8 +254,9 @@ def test_publish_multi_connection(self): connection2.connection_credentials = TSC.ConnectionCredentials('test', 'secret', True) response = RequestFactory.Datasource._generate_xml(new_datasource, connections=[connection1, connection2]) - connection_results = ET.fromstring(response).findall('.//connection') # Can't use ConnectionItem parser due to xml namespace problems - + # Can't use ConnectionItem parser due to xml namespace problems + connection_results = ET.fromstring(response).findall('.//connection') + self.assertEqual(connection_results[0].get('serverAddress', None), 'mysql.test.com') self.assertEqual(connection_results[0].find('connectionCredentials').get('name', None), 'test') self.assertEqual(connection_results[1].get('serverAddress', None), 'pgsql.test.com') @@ -266,7 +267,9 @@ def test_publish_single_connection(self): connection_creds = TSC.ConnectionCredentials('test', 'secret', True) response = RequestFactory.Datasource._generate_xml(new_datasource, connection_credentials=connection_creds) - credentials = ET.fromstring(response).findall('.//connectionCredentials') # Can't use ConnectionItem parser due to xml namespace problems + # Can't use ConnectionItem parser due to xml namespace problems + credentials = ET.fromstring(response).findall('.//connectionCredentials') + self.assertEqual(len(credentials), 1) self.assertEqual(credentials[0].get('name', None), 'test') self.assertEqual(credentials[0].get('password', None), 'secret') @@ -274,12 +277,14 @@ def test_publish_single_connection(self): def test_credentials_and_multi_connect_raises_exception(self): new_datasource = TSC.DatasourceItem(name='Sample', project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') - + connection_creds = TSC.ConnectionCredentials('test', 'secret', True) - + connection1 = TSC.ConnectionItem() connection1.server_address = 'mysql.test.com' connection1.connection_credentials = TSC.ConnectionCredentials('test', 'secret', True) - + with self.assertRaises(RuntimeError): - response = RequestFactory.Datasource._generate_xml(new_datasource, connection_credentials=connection_creds, connections=[connection1]) \ No newline at end of file + response = RequestFactory.Datasource._generate_xml(new_datasource, + connection_credentials=connection_creds, + connections=[connection1]) diff --git a/test/test_workbook.py b/test/test_workbook.py index c39302ecb..2e2e45396 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -329,8 +329,9 @@ def test_publish_multi_connection(self): connection2.connection_credentials = TSC.ConnectionCredentials('test', 'secret', True) response = RequestFactory.Workbook._generate_xml(new_workbook, connections=[connection1, connection2]) - connection_results = ET.fromstring(response).findall('.//connection') # Can't use ConnectionItem parser due to xml namespace problems - + # Can't use ConnectionItem parser due to xml namespace problems + connection_results = ET.fromstring(response).findall('.//connection') + self.assertEqual(connection_results[0].get('serverAddress', None), 'mysql.test.com') self.assertEqual(connection_results[0].find('connectionCredentials').get('name', None), 'test') self.assertEqual(connection_results[1].get('serverAddress', None), 'pgsql.test.com') @@ -342,7 +343,8 @@ def test_publish_single_connection(self): connection_creds = TSC.ConnectionCredentials('test', 'secret', True) response = RequestFactory.Workbook._generate_xml(new_workbook, connection_credentials=connection_creds) - credentials = ET.fromstring(response).findall('.//connectionCredentials') # Can't use ConnectionItem parser due to xml namespace problems + # Can't use ConnectionItem parser due to xml namespace problems + credentials = ET.fromstring(response).findall('.//connectionCredentials') self.assertEqual(len(credentials), 1) self.assertEqual(credentials[0].get('name', None), 'test') self.assertEqual(credentials[0].get('password', None), 'secret') @@ -351,12 +353,14 @@ def test_publish_single_connection(self): def test_credentials_and_multi_connect_raises_exception(self): new_workbook = TSC.WorkbookItem(name='Sample', show_tabs=False, project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') - + connection_creds = TSC.ConnectionCredentials('test', 'secret', True) - + connection1 = TSC.ConnectionItem() connection1.server_address = 'mysql.test.com' connection1.connection_credentials = TSC.ConnectionCredentials('test', 'secret', True) - + with self.assertRaises(RuntimeError): - response = RequestFactory.Workbook._generate_xml(new_workbook, connection_credentials=connection_creds, connections=[connection1]) + response = RequestFactory.Workbook._generate_xml(new_workbook, + connection_credentials=connection_creds, + connections=[connection1]) From ec65b353a39c05404e48ab9ca633a22e4f415c7b Mon Sep 17 00:00:00 2001 From: Tyler Doyle Date: Thu, 19 Apr 2018 15:09:31 -0700 Subject: [PATCH 8/9] final touchup --- samples/publish_workbook.py | 13 ++++++++----- .../server/endpoint/datasources_endpoint.py | 2 +- tableauserverclient/server/endpoint/endpoint.py | 2 +- .../server/endpoint/workbooks_endpoint.py | 3 ++- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/samples/publish_workbook.py b/samples/publish_workbook.py index 33c3a430e..6798a2106 100644 --- a/samples/publish_workbook.py +++ b/samples/publish_workbook.py @@ -50,13 +50,16 @@ def main(): # Step 2: Get all the projects on server, then look for the default one. all_projects, pagination_item = server.projects.get() default_project = next((project for project in all_projects if project.is_default()), None) - connection2 = ConnectionItem() - connection2.server_address = "db2.test.tsi.lan" - connection2.server_port = "50000" - connection2.connection_credentials = ConnectionCredentials("test", "p@ssw0rd", True) + connection1 = ConnectionItem() - connection1.server_address = "mssql.test.tsi.lan" + connection1.server_address = "mssql.test.com" connection1.connection_credentials = ConnectionCredentials("test", "password", True) + + connection2 = ConnectionItem() + connection2.server_address = "postgres.test.com" + connection2.server_port = "5432" + connection2.connection_credentials = ConnectionCredentials("test", "password", True) + all_connections = list() all_connections.append(connection1) all_connections.append(connection2) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 8705c477a..5e986f91c 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -151,7 +151,7 @@ def refresh(self, datasource_item): # Publish datasource @api(version="2.0") - @parameter_added_in(connections="2.8") + @parameter_added_in(connections="99.99") def publish(self, datasource_item, file_path, mode, connection_credentials=None, connections=None): if not os.path.isfile(file_path): error = "File path does not lead to an existing file." diff --git a/tableauserverclient/server/endpoint/endpoint.py b/tableauserverclient/server/endpoint/endpoint.py index 01c378a54..a19c32acd 100644 --- a/tableauserverclient/server/endpoint/endpoint.py +++ b/tableauserverclient/server/endpoint/endpoint.py @@ -36,7 +36,7 @@ def _safe_to_log(server_response): if server_response.headers.get('Content-Type', None) not in ALLOWED_CONTENT_TYPES: return '[Truncated File Contents]' else: - return server_response.content[:300] + return server_response.content def _make_request(self, method, url, content=None, request_object=None, auth_token=None, content_type=None, parameters=None): diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index ad79d28da..24b8913d7 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -204,7 +204,8 @@ def publish(self, workbook_item, file_path, mode, connection_credentials=None, c if connection_credentials is not None: import warnings - warnings.warn("connection_credendials is being deprecated. Use connections instead, see http://...", + warnings.warn("connection_credentials is being deprecated. Use connections instead, " + \ + "see https://onlinehelp.tableau.com/current/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Publish_Workbook", DeprecationWarning) if not os.path.isfile(file_path): From 5b9046c0b5b40cfc5419e927c56cc6099fa2436a Mon Sep 17 00:00:00 2001 From: Tyler Doyle Date: Thu, 19 Apr 2018 15:14:16 -0700 Subject: [PATCH 9/9] style fix --- tableauserverclient/server/endpoint/workbooks_endpoint.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 24b8913d7..537e3ec81 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -204,8 +204,7 @@ def publish(self, workbook_item, file_path, mode, connection_credentials=None, c if connection_credentials is not None: import warnings - warnings.warn("connection_credentials is being deprecated. Use connections instead, " + \ - "see https://onlinehelp.tableau.com/current/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm#Publish_Workbook", + warnings.warn("connection_credentials is being deprecated. Use connections instead", DeprecationWarning) if not os.path.isfile(file_path):