From 88985fe979a6b3968c76a0e6c16477d3b0376c9f Mon Sep 17 00:00:00 2001 From: "SFDC\\vchavatapalli" Date: Mon, 14 Jul 2025 21:07:55 -0700 Subject: [PATCH 01/11] Updated TSC with new API's --- samples/update_connection_auth.py | 62 +++++++++++++++++ samples/update_connections_auth.py | 65 ++++++++++++++++++ tableauserverclient/models/connection_item.py | 12 +++- .../server/endpoint/datasources_endpoint.py | 66 +++++++++++++++++++ .../server/endpoint/workbooks_endpoint.py | 66 +++++++++++++++++++ test/assets/datasource_connections_update.xml | 21 ++++++ test/assets/workbook_update_connections.xml | 21 ++++++ test/test_datasource.py | 41 ++++++++++++ test/test_workbook.py | 36 +++++++++- 9 files changed, 388 insertions(+), 2 deletions(-) create mode 100644 samples/update_connection_auth.py create mode 100644 samples/update_connections_auth.py create mode 100644 test/assets/datasource_connections_update.xml create mode 100644 test/assets/workbook_update_connections.xml diff --git a/samples/update_connection_auth.py b/samples/update_connection_auth.py new file mode 100644 index 00000000..c5ccd54d --- /dev/null +++ b/samples/update_connection_auth.py @@ -0,0 +1,62 @@ +import argparse +import logging +import tableauserverclient as TSC + + +def main(): + parser = argparse.ArgumentParser(description="Update a single connection on a datasource or workbook to embed credentials") + + # Common options + parser.add_argument("--server", "-s", help="Server address", required=True) + parser.add_argument("--site", "-S", help="Site name", required=True) + parser.add_argument("--token-name", "-p", help="Personal access token name", required=True) + parser.add_argument("--token-value", "-v", help="Personal access token value", required=True) + parser.add_argument( + "--logging-level", "-l", + choices=["debug", "info", "error"], + default="error", + help="Logging level (default: error)", + ) + + # Resource and connection details + parser.add_argument("resource_type", choices=["workbook", "datasource"]) + parser.add_argument("resource_id", help="Workbook or datasource ID") + parser.add_argument("connection_id", help="Connection ID to update") + parser.add_argument("datasource_username", help="Username to set for the connection") + parser.add_argument("datasource_password", help="Password to set for the connection") + parser.add_argument("authentication_type", help="Authentication type") + + args = parser.parse_args() + + # Logging setup + logging_level = getattr(logging, args.logging_level.upper()) + logging.basicConfig(level=logging_level) + + tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site) + server = TSC.Server(args.server, use_server_version=True) + + with server.auth.sign_in(tableau_auth): + endpoint = { + "workbook": server.workbooks, + "datasource": server.datasources + }.get(args.resource_type) + + update_function = endpoint.update_connection + resource = endpoint.get_by_id(args.resource_id) + endpoint.populate_connections(resource) + + connections = [conn for conn in resource.connections if conn.id == args.connection_id] + assert len(connections) == 1, f"Connection ID '{args.connection_id}' not found." + + connection = connections[0] + connection.username = args.datasource_username + connection.password = args.datasource_password + connection.authentication_type = args.authentication_type + connection.embed_password = True + + updated_connection = update_function(resource, connection) + print(f"Updated connection: {updated_connection.__dict__}") + + +if __name__ == "__main__": + main() diff --git a/samples/update_connections_auth.py b/samples/update_connections_auth.py new file mode 100644 index 00000000..563ca898 --- /dev/null +++ b/samples/update_connections_auth.py @@ -0,0 +1,65 @@ +import argparse +import logging +import tableauserverclient as TSC + + +def main(): + parser = argparse.ArgumentParser(description="Bulk update all workbook or datasource connections") + + # Common options + parser.add_argument("--server", "-s", help="Server address", required=True) + parser.add_argument("--site", "-S", help="Site name", required=True) + parser.add_argument("--username", "-p", help="Personal access token name", required=True) + parser.add_argument("--password", "-v", help="Personal access token value", required=True) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="Logging level (default: error)", + ) + + # Resource-specific + parser.add_argument("resource_type", choices=["workbook", "datasource"]) + parser.add_argument("resource_id") + parser.add_argument("datasource_username") + parser.add_argument("authentication_type") + parser.add_argument("--datasource_password", default=None, help="Datasource password (optional)") + parser.add_argument("--embed_password", default="true", choices=["true", "false"], help="Embed password (default: true)") + + args = parser.parse_args() + + # Set logging level + logging_level = getattr(logging, args.logging_level.upper()) + logging.basicConfig(level=logging_level) + + tableau_auth = TSC.TableauAuth(args.username, args.password, site_id=args.site) + server = TSC.Server(args.server, use_server_version=True) + + with server.auth.sign_in(tableau_auth): + endpoint = { + "workbook": server.workbooks, + "datasource": server.datasources + }.get(args.resource_type) + + resource = endpoint.get_by_id(args.resource_id) + endpoint.populate_connections(resource) + + connection_luids = [conn.id for conn in resource.connections] + embed_password = args.embed_password.lower() == "true" + + # Call unified update_connections method + updated_ids = endpoint.update_connections( + resource, + connection_luids=connection_luids, + authentication_type=args.authentication_type, + username=args.datasource_username, + password=args.datasource_password, + embed_password=embed_password + ) + + print(f"Updated connections on {args.resource_type} {args.resource_id}: {updated_ids}") + + +if __name__ == "__main__": + main() diff --git a/tableauserverclient/models/connection_item.py b/tableauserverclient/models/connection_item.py index 6a8244fb..5282bb6a 100644 --- a/tableauserverclient/models/connection_item.py +++ b/tableauserverclient/models/connection_item.py @@ -41,6 +41,9 @@ class ConnectionItem: server_port: str The port used for the connection. + auth_type: str + Specifies the type of authentication used by the connection. + connection_credentials: ConnectionCredentials The Connection Credentials object containing authentication details for the connection. Replaces username/password/embed_password when @@ -59,6 +62,7 @@ def __init__(self): self.username: Optional[str] = None self.connection_credentials: Optional[ConnectionCredentials] = None self._query_tagging: Optional[bool] = None + self._auth_type: Optional[str] = None @property def datasource_id(self) -> Optional[str]: @@ -80,6 +84,10 @@ def connection_type(self) -> Optional[str]: def query_tagging(self) -> Optional[bool]: return self._query_tagging + @property + def auth_type(self) -> Optional[str]: + return self._auth_type + @query_tagging.setter @property_is_boolean def query_tagging(self, value: Optional[bool]): @@ -92,7 +100,7 @@ def query_tagging(self, value: Optional[bool]): self._query_tagging = value def __repr__(self): - return "".format( + return "".format( **self.__dict__ ) @@ -112,6 +120,7 @@ def from_response(cls, resp, ns) -> list["ConnectionItem"]: connection_item._query_tagging = ( string_to_bool(s) if (s := connection_xml.get("queryTagging", None)) else None ) + connection_item._auth_type = connection_xml.get("authenticationType", None) datasource_elem = connection_xml.find(".//t:datasource", namespaces=ns) if datasource_elem is not None: connection_item._datasource_id = datasource_elem.get("id", None) @@ -139,6 +148,7 @@ def from_xml_element(cls, parsed_response, ns) -> list["ConnectionItem"]: connection_item.server_address = connection_xml.get("serverAddress", None) connection_item.server_port = connection_xml.get("serverPort", None) + connection_item._auth_type = connection_xml.get("authenticationType", None) connection_credentials = connection_xml.find(".//t:connectionCredentials", namespaces=ns) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 16844697..0f489a18 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -319,6 +319,72 @@ def update_connection( logger.info(f"Updated datasource item (ID: {datasource_item.id} & connection item {connection_item.id}") return connection + @api(version="3.26") + def update_connections( + self, datasource_item: DatasourceItem, connection_luids: list[str], authentication_type: str, username: Optional[str] = None, password: Optional[str] = None, embed_password: Optional[bool] = None + ) -> list[str]: + """ + Bulk updates one or more datasource connections by LUID. + + Parameters + ---------- + datasource_item : DatasourceItem + The datasource item containing the connections. + + connection_luids : list of str + The connection LUIDs to update. + + authentication_type : str + The authentication type to use (e.g., 'auth-keypair'). + + username : str, optional + The username to set. + + password : str, optional + The password or secret to set. + + embed_password : bool, optional + Whether to embed the password. + + Returns + ------- + list of str + The connection LUIDs that were updated. + """ + from xml.etree.ElementTree import Element, SubElement, tostring + + url = f"{self.baseurl}/{datasource_item.id}/connections" + print("Method URL:", url) + + ts_request = Element("tsRequest") + + # + conn_luids_elem = SubElement(ts_request, "connectionLuids") + for luid in connection_luids: + SubElement(conn_luids_elem, "connectionLuid").text = luid + + # + connection_elem = SubElement(ts_request, "connection") + connection_elem.set("authenticationType", authentication_type) + + if username: + connection_elem.set("userName", username) + + if password: + connection_elem.set("password", password) + + if embed_password is not None: + connection_elem.set("embedPassword", str(embed_password).lower()) + + request_body = tostring(ts_request) + + response = self.put_request(url, request_body) + + logger.info( + f"Updated connections for datasource {datasource_item.id}: {', '.join(connection_luids)}" + ) + return connection_luids + @api(version="2.8") def refresh(self, datasource_item: DatasourceItem, incremental: bool = False) -> JobItem: """ diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index bf4088b9..d7a32027 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -325,6 +325,72 @@ def update_connection(self, workbook_item: WorkbookItem, connection_item: Connec logger.info(f"Updated workbook item (ID: {workbook_item.id} & connection item {connection_item.id})") return connection + # Update workbook_connections + @api(version="3.26") + def update_connections(self, workbook_item: WorkbookItem, connection_luids: list[str], authentication_type: str, username: Optional[str] = None, password: Optional[str] = None, embed_password: Optional[bool] = None + ) -> list[str]: + """ + Bulk updates one or more workbook connections by LUID, including authenticationType, username, password, and embedPassword. + + Parameters + ---------- + workbook_item : WorkbookItem + The workbook item containing the connections. + + connection_luids : list of str + The connection LUIDs to update. + + authentication_type : str + The authentication type to use (e.g., 'AD Service Principal'). + + username : str, optional + The username to set (e.g., client ID for keypair auth). + + password : str, optional + The password or secret to set. + + embed_password : bool, optional + Whether to embed the password. + + Returns + ------- + list of str + The connection LUIDs that were updated. + """ + from xml.etree.ElementTree import Element, SubElement, tostring + + url = f"{self.baseurl}/{workbook_item.id}/connections" + + ts_request = Element("tsRequest") + + # + conn_luids_elem = SubElement(ts_request, "connectionLuids") + for luid in connection_luids: + SubElement(conn_luids_elem, "connectionLuid").text = luid + + # + connection_elem = SubElement(ts_request, "connection") + connection_elem.set("authenticationType", authentication_type) + + if username: + connection_elem.set("userName", username) + + if password: + connection_elem.set("password", password) + + if embed_password is not None: + connection_elem.set("embedPassword", str(embed_password).lower()) + + request_body = tostring(ts_request) + + # Send request + response = self.put_request(url, request_body) + + logger.info( + f"Updated connections for workbook {workbook_item.id}: {', '.join(connection_luids)}" + ) + return connection_luids + # Download workbook contents with option of passing in filepath @api(version="2.0") @parameter_added_in(no_extract="2.5") diff --git a/test/assets/datasource_connections_update.xml b/test/assets/datasource_connections_update.xml new file mode 100644 index 00000000..5cc8ac00 --- /dev/null +++ b/test/assets/datasource_connections_update.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/test/assets/workbook_update_connections.xml b/test/assets/workbook_update_connections.xml new file mode 100644 index 00000000..1e9b3342 --- /dev/null +++ b/test/assets/workbook_update_connections.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/test/test_datasource.py b/test/test_datasource.py index a604ba8b..05cbfff5 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -30,6 +30,7 @@ UPDATE_XML = "datasource_update.xml" UPDATE_HYPER_DATA_XML = "datasource_data_update.xml" UPDATE_CONNECTION_XML = "datasource_connection_update.xml" +UPDATE_CONNECTIONS_XML = "datasource_connections_update.xml" class DatasourceTests(unittest.TestCase): @@ -217,6 +218,46 @@ def test_update_connection(self) -> None: self.assertEqual("9876", new_connection.server_port) self.assertEqual("foo", new_connection.username) + def test_update_connections(self) -> None: + populate_xml, response_xml = read_xml_assets( + POPULATE_CONNECTIONS_XML, + UPDATE_CONNECTIONS_XML + ) + + with requests_mock.Mocker() as m: + + datasource_id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" + connection_luids = [ + "be786ae0-d2bf-4a4b-9b34-e2de8d2d4488", + "a1b2c3d4-e5f6-7a8b-9c0d-123456789abc" + ] + + datasource = TSC.DatasourceItem(datasource_id) + datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" + datasource.owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794" + self.server.version = "3.26" + + url = f"{self.server.baseurl}/{datasource.id}/connections" + m.get("http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/datasources/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections", text=populate_xml) + m.put("http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/datasources/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections", text=response_xml) + + + + print("BASEURL:", self.server.baseurl) + print("Calling PUT on:", f"{self.server.baseurl}/{datasource.id}/connections") + + updated_luids = self.server.datasources.update_connections( + datasource_item=datasource, + connection_luids=connection_luids, + authentication_type="auth-keypair", + username="testuser", + password="testpass", + embed_password=True + ) + + self.assertEqual(updated_luids, connection_luids) + + def test_populate_permissions(self) -> None: with open(asset(POPULATE_PERMISSIONS_XML), "rb") as f: response_xml = f.read().decode("utf-8") diff --git a/test/test_workbook.py b/test/test_workbook.py index 84afd7fc..ff6f423f 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -14,7 +14,7 @@ from tableauserverclient.models import UserItem, GroupItem, PermissionsRule from tableauserverclient.server.endpoint.exceptions import InternalServerError, UnsupportedAttributeError from tableauserverclient.server.request_factory import RequestFactory -from ._utils import asset +from ._utils import read_xml_asset, read_xml_assets, asset TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") @@ -39,6 +39,7 @@ REVISION_XML = os.path.join(TEST_ASSET_DIR, "workbook_revision.xml") UPDATE_XML = os.path.join(TEST_ASSET_DIR, "workbook_update.xml") UPDATE_PERMISSIONS = os.path.join(TEST_ASSET_DIR, "workbook_update_permissions.xml") +UPDATE_CONNECTIONS_XML = os.path.join(TEST_ASSET_DIR, "workbook_update_connections.xml") class WorkbookTests(unittest.TestCase): @@ -980,6 +981,39 @@ def test_odata_connection(self) -> None: assert xml_connection is not None self.assertEqual(xml_connection.get("serverAddress"), url) + def test_update_workbook_connections(self) -> None: + populate_xml, response_xml = read_xml_assets( + POPULATE_CONNECTIONS_XML, + UPDATE_CONNECTIONS_XML + ) + + + with requests_mock.Mocker() as m: + workbook_id = "1a2b3c4d-5e6f-7a8b-9c0d-112233445566" + connection_luids = [ + "abc12345-def6-7890-gh12-ijklmnopqrst", + "1234abcd-5678-efgh-ijkl-0987654321mn" + ] + + workbook = TSC.WorkbookItem(workbook_id) + workbook._id = workbook_id + self.server.version = "3.26" + url = f"{self.server.baseurl}/{workbook_id}/connections" + m.get("http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks/1a2b3c4d-5e6f-7a8b-9c0d-112233445566/connections", text=populate_xml) + m.put("http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks/1a2b3c4d-5e6f-7a8b-9c0d-112233445566/connections", text=response_xml) + + + updated_luids = self.server.workbooks.update_connections( + workbook_item=workbook, + connection_luids=connection_luids, + authentication_type="AD Service Principal", + username="svc-client", + password="secret-token", + embed_password=True + ) + + self.assertEqual(updated_luids, connection_luids) + def test_get_workbook_all_fields(self) -> None: self.server.version = "3.21" baseurl = self.server.workbooks.baseurl From 746b34502df44ff62b11089003938d4fee5cf8d2 Mon Sep 17 00:00:00 2001 From: Jordan Woods <13803242+jorwoods@users.noreply.github.com> Date: Sat, 19 Jul 2025 21:57:46 -0500 Subject: [PATCH 02/11] chore: refactor XML payload into RequestFactory Also correct the type hints to clarify that it accepts any Iterable. --- tableauserverclient/models/connection_item.py | 12 ++- .../server/endpoint/datasources_endpoint.py | 48 ++++------ .../server/endpoint/workbooks_endpoint.py | 94 +++++++++---------- tableauserverclient/server/request_factory.py | 52 ++++++++++ test/test_datasource.py | 25 +++-- test/test_workbook.py | 24 +++-- 6 files changed, 142 insertions(+), 113 deletions(-) diff --git a/tableauserverclient/models/connection_item.py b/tableauserverclient/models/connection_item.py index 5282bb6a..3e8c6d29 100644 --- a/tableauserverclient/models/connection_item.py +++ b/tableauserverclient/models/connection_item.py @@ -84,10 +84,6 @@ def connection_type(self) -> Optional[str]: def query_tagging(self) -> Optional[bool]: return self._query_tagging - @property - def auth_type(self) -> Optional[str]: - return self._auth_type - @query_tagging.setter @property_is_boolean def query_tagging(self, value: Optional[bool]): @@ -99,6 +95,14 @@ def query_tagging(self, value: Optional[bool]): return self._query_tagging = value + @property + def auth_type(self) -> Optional[str]: + return self._auth_type + + @auth_type.setter + def auth_type(self, value: Optional[str]): + self._auth_type = value + def __repr__(self): return "".format( **self.__dict__ diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 0f489a18..7494a405 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -321,8 +321,14 @@ def update_connection( @api(version="3.26") def update_connections( - self, datasource_item: DatasourceItem, connection_luids: list[str], authentication_type: str, username: Optional[str] = None, password: Optional[str] = None, embed_password: Optional[bool] = None - ) -> list[str]: + self, + datasource_item: DatasourceItem, + connection_luids: Iterable[str], + authentication_type: str, + username: Optional[str] = None, + password: Optional[str] = None, + embed_password: Optional[bool] = None, + ) -> Iterable[str]: """ Bulk updates one or more datasource connections by LUID. @@ -331,7 +337,7 @@ def update_connections( datasource_item : DatasourceItem The datasource item containing the connections. - connection_luids : list of str + connection_luids : Iterable of str The connection LUIDs to update. authentication_type : str @@ -348,41 +354,23 @@ def update_connections( Returns ------- - list of str + Iterable of str The connection LUIDs that were updated. """ - from xml.etree.ElementTree import Element, SubElement, tostring url = f"{self.baseurl}/{datasource_item.id}/connections" print("Method URL:", url) - ts_request = Element("tsRequest") - - # - conn_luids_elem = SubElement(ts_request, "connectionLuids") - for luid in connection_luids: - SubElement(conn_luids_elem, "connectionLuid").text = luid - - # - connection_elem = SubElement(ts_request, "connection") - connection_elem.set("authenticationType", authentication_type) - - if username: - connection_elem.set("userName", username) - - if password: - connection_elem.set("password", password) - - if embed_password is not None: - connection_elem.set("embedPassword", str(embed_password).lower()) - - request_body = tostring(ts_request) - + request_body = RequestFactory.Datasource.update_connections_req( + connection_luids=connection_luids, + authentication_type=authentication_type, + username=username, + password=password, + embed_password=embed_password, + ) response = self.put_request(url, request_body) - logger.info( - f"Updated connections for datasource {datasource_item.id}: {', '.join(connection_luids)}" - ) + logger.info(f"Updated connections for datasource {datasource_item.id}: {', '.join(connection_luids)}") return connection_luids @api(version="2.8") diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index d7a32027..9afe0488 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -327,69 +327,59 @@ def update_connection(self, workbook_item: WorkbookItem, connection_item: Connec # Update workbook_connections @api(version="3.26") - def update_connections(self, workbook_item: WorkbookItem, connection_luids: list[str], authentication_type: str, username: Optional[str] = None, password: Optional[str] = None, embed_password: Optional[bool] = None - ) -> list[str]: - """ - Bulk updates one or more workbook connections by LUID, including authenticationType, username, password, and embedPassword. - - Parameters - ---------- - workbook_item : WorkbookItem - The workbook item containing the connections. - - connection_luids : list of str - The connection LUIDs to update. - - authentication_type : str - The authentication type to use (e.g., 'AD Service Principal'). - - username : str, optional - The username to set (e.g., client ID for keypair auth). - - password : str, optional - The password or secret to set. - - embed_password : bool, optional - Whether to embed the password. + def update_connections( + self, + workbook_item: WorkbookItem, + connection_luids: Iterable[str], + authentication_type: str, + username: Optional[str] = None, + password: Optional[str] = None, + embed_password: Optional[bool] = None, + ) -> Iterable[str]: + """ + Bulk updates one or more workbook connections by LUID, including authenticationType, username, password, and embedPassword. - Returns - ------- - list of str - The connection LUIDs that were updated. - """ - from xml.etree.ElementTree import Element, SubElement, tostring + Parameters + ---------- + workbook_item : WorkbookItem + The workbook item containing the connections. - url = f"{self.baseurl}/{workbook_item.id}/connections" + connection_luids : Iterable of str + The connection LUIDs to update. - ts_request = Element("tsRequest") + authentication_type : str + The authentication type to use (e.g., 'AD Service Principal'). - # - conn_luids_elem = SubElement(ts_request, "connectionLuids") - for luid in connection_luids: - SubElement(conn_luids_elem, "connectionLuid").text = luid + username : str, optional + The username to set (e.g., client ID for keypair auth). - # - connection_elem = SubElement(ts_request, "connection") - connection_elem.set("authenticationType", authentication_type) + password : str, optional + The password or secret to set. - if username: - connection_elem.set("userName", username) + embed_password : bool, optional + Whether to embed the password. - if password: - connection_elem.set("password", password) + Returns + ------- + Iterable of str + The connection LUIDs that were updated. + """ - if embed_password is not None: - connection_elem.set("embedPassword", str(embed_password).lower()) + url = f"{self.baseurl}/{workbook_item.id}/connections" - request_body = tostring(ts_request) + request_body = RequestFactory.Workbook.update_connections_req( + connection_luids, + authentication_type, + username=username, + password=password, + embed_password=embed_password, + ) - # Send request - response = self.put_request(url, request_body) + # Send request + response = self.put_request(url, request_body) - logger.info( - f"Updated connections for workbook {workbook_item.id}: {', '.join(connection_luids)}" - ) - return connection_luids + logger.info(f"Updated connections for workbook {workbook_item.id}: {', '.join(connection_luids)}") + return connection_luids # Download workbook contents with option of passing in filepath @api(version="2.0") diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index c898004f..45da6605 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -244,6 +244,32 @@ def publish_req_chunked(self, datasource_item, connection_credentials=None, conn parts = {"request_payload": ("", xml_request, "text/xml")} return _add_multipart(parts) + @_tsrequest_wrapped + def update_connections_req( + self, + element: ET.Element, + connection_luids: Iterable[str], + authentication_type: str, + username: Optional[str] = None, + password: Optional[str] = None, + embed_password: Optional[bool] = None, + ): + conn_luids_elem = ET.SubElement(element, "connectionLUIDs") + for luid in connection_luids: + ET.SubElement(conn_luids_elem, "connectionLUID").text = luid + + connection_elem = ET.SubElement(element, "connection") + connection_elem.set("authenticationType", authentication_type) + + if username is not None: + connection_elem.set("userName", username) + + if password is not None: + connection_elem.set("password", password) + + if embed_password is not None: + connection_elem.set("embedPassword", str(embed_password).lower()) + class DQWRequest: def add_req(self, dqw_item): @@ -1092,6 +1118,32 @@ def embedded_extract_req( if (id_ := datasource_item.id) is not None: datasource_element.attrib["id"] = id_ + @_tsrequest_wrapped + def update_connections_req( + self, + element: ET.Element, + connection_luids: Iterable[str], + authentication_type: str, + username: Optional[str] = None, + password: Optional[str] = None, + embed_password: Optional[bool] = None, + ): + conn_luids_elem = ET.SubElement(element, "connectionLUIDs") + for luid in connection_luids: + ET.SubElement(conn_luids_elem, "connectionLUID").text = luid + + connection_elem = ET.SubElement(element, "connection") + connection_elem.set("authenticationType", authentication_type) + + if username is not None: + connection_elem.set("userName", username) + + if password is not None: + connection_elem.set("password", password) + + if embed_password is not None: + connection_elem.set("embedPassword", str(embed_password).lower()) + class Connection: @_tsrequest_wrapped diff --git a/test/test_datasource.py b/test/test_datasource.py index 05cbfff5..a0953aaf 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -219,18 +219,12 @@ def test_update_connection(self) -> None: self.assertEqual("foo", new_connection.username) def test_update_connections(self) -> None: - populate_xml, response_xml = read_xml_assets( - POPULATE_CONNECTIONS_XML, - UPDATE_CONNECTIONS_XML - ) + populate_xml, response_xml = read_xml_assets(POPULATE_CONNECTIONS_XML, UPDATE_CONNECTIONS_XML) with requests_mock.Mocker() as m: datasource_id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" - connection_luids = [ - "be786ae0-d2bf-4a4b-9b34-e2de8d2d4488", - "a1b2c3d4-e5f6-7a8b-9c0d-123456789abc" - ] + connection_luids = ["be786ae0-d2bf-4a4b-9b34-e2de8d2d4488", "a1b2c3d4-e5f6-7a8b-9c0d-123456789abc"] datasource = TSC.DatasourceItem(datasource_id) datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" @@ -238,10 +232,14 @@ def test_update_connections(self) -> None: self.server.version = "3.26" url = f"{self.server.baseurl}/{datasource.id}/connections" - m.get("http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/datasources/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections", text=populate_xml) - m.put("http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/datasources/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections", text=response_xml) - - + m.get( + "http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/datasources/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections", + text=populate_xml, + ) + m.put( + "http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/datasources/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections", + text=response_xml, + ) print("BASEURL:", self.server.baseurl) print("Calling PUT on:", f"{self.server.baseurl}/{datasource.id}/connections") @@ -252,12 +250,11 @@ def test_update_connections(self) -> None: authentication_type="auth-keypair", username="testuser", password="testpass", - embed_password=True + embed_password=True, ) self.assertEqual(updated_luids, connection_luids) - def test_populate_permissions(self) -> None: with open(asset(POPULATE_PERMISSIONS_XML), "rb") as f: response_xml = f.read().decode("utf-8") diff --git a/test/test_workbook.py b/test/test_workbook.py index ff6f423f..cfcf70fe 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -982,26 +982,24 @@ def test_odata_connection(self) -> None: self.assertEqual(xml_connection.get("serverAddress"), url) def test_update_workbook_connections(self) -> None: - populate_xml, response_xml = read_xml_assets( - POPULATE_CONNECTIONS_XML, - UPDATE_CONNECTIONS_XML - ) - + populate_xml, response_xml = read_xml_assets(POPULATE_CONNECTIONS_XML, UPDATE_CONNECTIONS_XML) with requests_mock.Mocker() as m: workbook_id = "1a2b3c4d-5e6f-7a8b-9c0d-112233445566" - connection_luids = [ - "abc12345-def6-7890-gh12-ijklmnopqrst", - "1234abcd-5678-efgh-ijkl-0987654321mn" - ] + connection_luids = ["abc12345-def6-7890-gh12-ijklmnopqrst", "1234abcd-5678-efgh-ijkl-0987654321mn"] workbook = TSC.WorkbookItem(workbook_id) workbook._id = workbook_id self.server.version = "3.26" url = f"{self.server.baseurl}/{workbook_id}/connections" - m.get("http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks/1a2b3c4d-5e6f-7a8b-9c0d-112233445566/connections", text=populate_xml) - m.put("http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks/1a2b3c4d-5e6f-7a8b-9c0d-112233445566/connections", text=response_xml) - + m.get( + "http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks/1a2b3c4d-5e6f-7a8b-9c0d-112233445566/connections", + text=populate_xml, + ) + m.put( + "http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks/1a2b3c4d-5e6f-7a8b-9c0d-112233445566/connections", + text=response_xml, + ) updated_luids = self.server.workbooks.update_connections( workbook_item=workbook, @@ -1009,7 +1007,7 @@ def test_update_workbook_connections(self) -> None: authentication_type="AD Service Principal", username="svc-client", password="secret-token", - embed_password=True + embed_password=True, ) self.assertEqual(updated_luids, connection_luids) From 75f5f4cb733bd2ff8190b5475b548d68ffc3202a Mon Sep 17 00:00:00 2001 From: Jordan Woods <13803242+jorwoods@users.noreply.github.com> Date: Sat, 19 Jul 2025 22:03:54 -0500 Subject: [PATCH 03/11] style: black samples --- samples/update_connection_auth.py | 12 ++++++------ samples/update_connections_auth.py | 11 +++++------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/samples/update_connection_auth.py b/samples/update_connection_auth.py index c5ccd54d..661a5e27 100644 --- a/samples/update_connection_auth.py +++ b/samples/update_connection_auth.py @@ -4,7 +4,9 @@ def main(): - parser = argparse.ArgumentParser(description="Update a single connection on a datasource or workbook to embed credentials") + parser = argparse.ArgumentParser( + description="Update a single connection on a datasource or workbook to embed credentials" + ) # Common options parser.add_argument("--server", "-s", help="Server address", required=True) @@ -12,7 +14,8 @@ def main(): parser.add_argument("--token-name", "-p", help="Personal access token name", required=True) parser.add_argument("--token-value", "-v", help="Personal access token value", required=True) parser.add_argument( - "--logging-level", "-l", + "--logging-level", + "-l", choices=["debug", "info", "error"], default="error", help="Logging level (default: error)", @@ -36,10 +39,7 @@ def main(): server = TSC.Server(args.server, use_server_version=True) with server.auth.sign_in(tableau_auth): - endpoint = { - "workbook": server.workbooks, - "datasource": server.datasources - }.get(args.resource_type) + endpoint = {"workbook": server.workbooks, "datasource": server.datasources}.get(args.resource_type) update_function = endpoint.update_connection resource = endpoint.get_by_id(args.resource_id) diff --git a/samples/update_connections_auth.py b/samples/update_connections_auth.py index 563ca898..7aad64a6 100644 --- a/samples/update_connections_auth.py +++ b/samples/update_connections_auth.py @@ -25,7 +25,9 @@ def main(): parser.add_argument("datasource_username") parser.add_argument("authentication_type") parser.add_argument("--datasource_password", default=None, help="Datasource password (optional)") - parser.add_argument("--embed_password", default="true", choices=["true", "false"], help="Embed password (default: true)") + parser.add_argument( + "--embed_password", default="true", choices=["true", "false"], help="Embed password (default: true)" + ) args = parser.parse_args() @@ -37,10 +39,7 @@ def main(): server = TSC.Server(args.server, use_server_version=True) with server.auth.sign_in(tableau_auth): - endpoint = { - "workbook": server.workbooks, - "datasource": server.datasources - }.get(args.resource_type) + endpoint = {"workbook": server.workbooks, "datasource": server.datasources}.get(args.resource_type) resource = endpoint.get_by_id(args.resource_id) endpoint.populate_connections(resource) @@ -55,7 +54,7 @@ def main(): authentication_type=args.authentication_type, username=args.datasource_username, password=args.datasource_password, - embed_password=embed_password + embed_password=embed_password, ) print(f"Updated connections on {args.resource_type} {args.resource_id}: {updated_ids}") From 1fb57d5b2530ce236b0c26bf1be6b2757ebd3f45 Mon Sep 17 00:00:00 2001 From: "SFDC\\vchavatapalli" Date: Mon, 21 Jul 2025 11:01:35 -0700 Subject: [PATCH 04/11] Updated token name, value --- samples/update_connections_auth.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/update_connections_auth.py b/samples/update_connections_auth.py index 7aad64a6..d0acffcd 100644 --- a/samples/update_connections_auth.py +++ b/samples/update_connections_auth.py @@ -9,8 +9,8 @@ def main(): # Common options parser.add_argument("--server", "-s", help="Server address", required=True) parser.add_argument("--site", "-S", help="Site name", required=True) - parser.add_argument("--username", "-p", help="Personal access token name", required=True) - parser.add_argument("--password", "-v", help="Personal access token value", required=True) + parser.add_argument("--token-name", "-p", help="Personal access token name", required=True) + parser.add_argument("--token-value", "-v", help="Personal access token value", required=True) parser.add_argument( "--logging-level", "-l", @@ -35,7 +35,7 @@ def main(): logging_level = getattr(logging, args.logging_level.upper()) logging.basicConfig(level=logging_level) - tableau_auth = TSC.TableauAuth(args.username, args.password, site_id=args.site) + tableau_auth = TSC.TableauAuth(args.token_name, args.token_value, site_id=args.site) server = TSC.Server(args.server, use_server_version=True) with server.auth.sign_in(tableau_auth): From 9c2bec3cde4712914599bb43aeb0c7062976c2d2 Mon Sep 17 00:00:00 2001 From: "SFDC\\vchavatapalli" Date: Mon, 21 Jul 2025 11:06:25 -0700 Subject: [PATCH 05/11] Clean up --- tableauserverclient/server/endpoint/datasources_endpoint.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 7494a405..55f8ad1d 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -359,7 +359,6 @@ def update_connections( """ url = f"{self.baseurl}/{datasource_item.id}/connections" - print("Method URL:", url) request_body = RequestFactory.Datasource.update_connections_req( connection_luids=connection_luids, From a40d774c328db02f5547d9637f39c2b37790944e Mon Sep 17 00:00:00 2001 From: "SFDC\\vchavatapalli" Date: Mon, 21 Jul 2025 13:47:55 -0700 Subject: [PATCH 06/11] Added response parsing --- samples/update_connections_auth.py | 4 ++-- .../server/endpoint/datasources_endpoint.py | 8 +++++--- tableauserverclient/server/endpoint/workbooks_endpoint.py | 8 +++++--- test/assets/datasource_connections_update.xml | 6 +++--- test/assets/workbook_update_connections.xml | 6 +++--- test/test_datasource.py | 5 +++-- test/test_workbook.py | 5 +++-- 7 files changed, 24 insertions(+), 18 deletions(-) diff --git a/samples/update_connections_auth.py b/samples/update_connections_auth.py index d0acffcd..6ae27e33 100644 --- a/samples/update_connections_auth.py +++ b/samples/update_connections_auth.py @@ -48,7 +48,7 @@ def main(): embed_password = args.embed_password.lower() == "true" # Call unified update_connections method - updated_ids = endpoint.update_connections( + connection_items = endpoint.update_connections( resource, connection_luids=connection_luids, authentication_type=args.authentication_type, @@ -57,7 +57,7 @@ def main(): embed_password=embed_password, ) - print(f"Updated connections on {args.resource_type} {args.resource_id}: {updated_ids}") + print(f"Updated connections on {args.resource_type} {args.resource_id}: {connection_items}") if __name__ == "__main__": diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 55f8ad1d..bf6107dd 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -367,10 +367,12 @@ def update_connections( password=password, embed_password=embed_password, ) - response = self.put_request(url, request_body) + server_response = self.put_request(url, request_body) + connection_items = list(ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)) + updated_ids = [conn.id for conn in connection_items] - logger.info(f"Updated connections for datasource {datasource_item.id}: {', '.join(connection_luids)}") - return connection_luids + logger.info(f"Updated connections for datasource {datasource_item.id}: {', '.join(updated_ids)}") + return connection_items @api(version="2.8") def refresh(self, datasource_item: DatasourceItem, incremental: bool = False) -> JobItem: diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 9afe0488..feb4a5dd 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -376,10 +376,12 @@ def update_connections( ) # Send request - response = self.put_request(url, request_body) + server_response = self.put_request(url, request_body) + connection_items = list(ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)) + updated_ids = [conn.id for conn in connection_items] - logger.info(f"Updated connections for workbook {workbook_item.id}: {', '.join(connection_luids)}") - return connection_luids + logger.info(f"Updated connections for workbook {workbook_item.id}: {', '.join(updated_ids)}") + return connection_items # Download workbook contents with option of passing in filepath @api(version="2.0") diff --git a/test/assets/datasource_connections_update.xml b/test/assets/datasource_connections_update.xml index 5cc8ac00..d726aad2 100644 --- a/test/assets/datasource_connections_update.xml +++ b/test/assets/datasource_connections_update.xml @@ -1,7 +1,7 @@ + xsi:schemaLocation="http://tableau.com/api https://help.tableau.com/samples/en-us/rest_api/ts-api_3_25.xsd"> + authenticationType="auth-keypair" /> + authenticationType="auth-keypair" /> diff --git a/test/assets/workbook_update_connections.xml b/test/assets/workbook_update_connections.xml index 1e9b3342..ce6ca227 100644 --- a/test/assets/workbook_update_connections.xml +++ b/test/assets/workbook_update_connections.xml @@ -1,7 +1,7 @@ + xsi:schemaLocation="http://tableau.com/api https://help.tableau.com/samples/en-us/rest_api/ts-api_3_25.xsd"> + authenticationType="AD Service Principal" /> + authenticationType="AD Service Principal" /> diff --git a/test/test_datasource.py b/test/test_datasource.py index a0953aaf..5e7e9135 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -244,7 +244,7 @@ def test_update_connections(self) -> None: print("BASEURL:", self.server.baseurl) print("Calling PUT on:", f"{self.server.baseurl}/{datasource.id}/connections") - updated_luids = self.server.datasources.update_connections( + connection_items = self.server.datasources.update_connections( datasource_item=datasource, connection_luids=connection_luids, authentication_type="auth-keypair", @@ -252,8 +252,9 @@ def test_update_connections(self) -> None: password="testpass", embed_password=True, ) + updated_ids = [conn.id for conn in connection_items] - self.assertEqual(updated_luids, connection_luids) + self.assertEqual(updated_ids, connection_luids) def test_populate_permissions(self) -> None: with open(asset(POPULATE_PERMISSIONS_XML), "rb") as f: diff --git a/test/test_workbook.py b/test/test_workbook.py index cfcf70fe..f6c494f9 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -1001,7 +1001,7 @@ def test_update_workbook_connections(self) -> None: text=response_xml, ) - updated_luids = self.server.workbooks.update_connections( + connection_items = self.server.workbooks.update_connections( workbook_item=workbook, connection_luids=connection_luids, authentication_type="AD Service Principal", @@ -1009,8 +1009,9 @@ def test_update_workbook_connections(self) -> None: password="secret-token", embed_password=True, ) + updated_ids = [conn.id for conn in connection_items] - self.assertEqual(updated_luids, connection_luids) + self.assertEqual(updated_ids, connection_luids) def test_get_workbook_all_fields(self) -> None: self.server.version = "3.21" From b4075901ec515cb57c4f8580da279b1c585879bf Mon Sep 17 00:00:00 2001 From: "SFDC\\vchavatapalli" Date: Mon, 21 Jul 2025 14:06:06 -0700 Subject: [PATCH 07/11] Fixed issues --- tableauserverclient/server/endpoint/datasources_endpoint.py | 6 +++--- tableauserverclient/server/endpoint/workbooks_endpoint.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index bf6107dd..ba242c8e 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -328,7 +328,7 @@ def update_connections( username: Optional[str] = None, password: Optional[str] = None, embed_password: Optional[bool] = None, - ) -> Iterable[str]: + ) -> list[ConnectionItem]: """ Bulk updates one or more datasource connections by LUID. @@ -368,8 +368,8 @@ def update_connections( embed_password=embed_password, ) server_response = self.put_request(url, request_body) - connection_items = list(ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)) - updated_ids = [conn.id for conn in connection_items] + connection_items = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace) + updated_ids: list[str] = [conn.id for conn in connection_items] logger.info(f"Updated connections for datasource {datasource_item.id}: {', '.join(updated_ids)}") return connection_items diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index feb4a5dd..907d2d99 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -335,7 +335,7 @@ def update_connections( username: Optional[str] = None, password: Optional[str] = None, embed_password: Optional[bool] = None, - ) -> Iterable[str]: + ) -> list[ConnectionItem]: """ Bulk updates one or more workbook connections by LUID, including authenticationType, username, password, and embedPassword. @@ -377,8 +377,8 @@ def update_connections( # Send request server_response = self.put_request(url, request_body) - connection_items = list(ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)) - updated_ids = [conn.id for conn in connection_items] + connection_items = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace) + updated_ids: list[str] = [conn.id for conn in connection_items] logger.info(f"Updated connections for workbook {workbook_item.id}: {', '.join(updated_ids)}") return connection_items From dc92d17682f2a8291bd7fcef683e1863789f932f Mon Sep 17 00:00:00 2001 From: "SFDC\\vchavatapalli" Date: Tue, 22 Jul 2025 16:53:31 -0700 Subject: [PATCH 08/11] Minor fixes to request payloads --- samples/update_connection_auth.py | 2 +- tableauserverclient/server/request_factory.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/samples/update_connection_auth.py b/samples/update_connection_auth.py index 661a5e27..19134e60 100644 --- a/samples/update_connection_auth.py +++ b/samples/update_connection_auth.py @@ -51,7 +51,7 @@ def main(): connection = connections[0] connection.username = args.datasource_username connection.password = args.datasource_password - connection.authentication_type = args.authentication_type + connection.auth_type = args.authentication_type connection.embed_password = True updated_connection = update_function(resource, connection) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 45da6605..f17d579a 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -1162,6 +1162,8 @@ def update_req(self, xml_request: ET.Element, connection_item: "ConnectionItem") connection_element.attrib["userName"] = connection_item.username if connection_item.password is not None: connection_element.attrib["password"] = connection_item.password + if connection_item.auth_type is not None: + connection_element.attrib["authenticationType"] = connection_item.auth_type if connection_item.embed_password is not None: connection_element.attrib["embedPassword"] = str(connection_item.embed_password).lower() if connection_item.query_tagging is not None: From a75bb4092649a7764ef8d9cc236747970add414f Mon Sep 17 00:00:00 2001 From: "SFDC\\vchavatapalli" Date: Wed, 23 Jul 2025 10:33:17 -0700 Subject: [PATCH 09/11] Added assertions for test cases --- test/test_datasource.py | 1 + test/test_workbook.py | 1 + 2 files changed, 2 insertions(+) diff --git a/test/test_datasource.py b/test/test_datasource.py index 5e7e9135..1ec535ea 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -255,6 +255,7 @@ def test_update_connections(self) -> None: updated_ids = [conn.id for conn in connection_items] self.assertEqual(updated_ids, connection_luids) + self.assertEqual("auth-keypair",connection_items[0].auth_type) def test_populate_permissions(self) -> None: with open(asset(POPULATE_PERMISSIONS_XML), "rb") as f: diff --git a/test/test_workbook.py b/test/test_workbook.py index f6c494f9..a4242c21 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -1012,6 +1012,7 @@ def test_update_workbook_connections(self) -> None: updated_ids = [conn.id for conn in connection_items] self.assertEqual(updated_ids, connection_luids) + self.assertEqual("AD Service Principal", connection_items[0].auth_type) def test_get_workbook_all_fields(self) -> None: self.server.version = "3.21" From 9a1c675af5f68eabe836df16323d011217f87900 Mon Sep 17 00:00:00 2001 From: "SFDC\\vchavatapalli" Date: Wed, 23 Jul 2025 10:38:10 -0700 Subject: [PATCH 10/11] style fix --- test/test_datasource.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_datasource.py b/test/test_datasource.py index 1ec535ea..d36ddab7 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -255,7 +255,7 @@ def test_update_connections(self) -> None: updated_ids = [conn.id for conn in connection_items] self.assertEqual(updated_ids, connection_luids) - self.assertEqual("auth-keypair",connection_items[0].auth_type) + self.assertEqual("auth-keypair", connection_items[0].auth_type) def test_populate_permissions(self) -> None: with open(asset(POPULATE_PERMISSIONS_XML), "rb") as f: From b90473d49693564542af7629476e7c480f564a2f Mon Sep 17 00:00:00 2001 From: "SFDC\\vchavatapalli" Date: Thu, 24 Jul 2025 11:11:39 -0700 Subject: [PATCH 11/11] Fixed the login method --- samples/update_connections_auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/update_connections_auth.py b/samples/update_connections_auth.py index 6ae27e33..f0c8dd85 100644 --- a/samples/update_connections_auth.py +++ b/samples/update_connections_auth.py @@ -35,7 +35,7 @@ def main(): logging_level = getattr(logging, args.logging_level.upper()) logging.basicConfig(level=logging_level) - tableau_auth = TSC.TableauAuth(args.token_name, args.token_value, site_id=args.site) + tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site) server = TSC.Server(args.server, use_server_version=True) with server.auth.sign_in(tableau_auth):