diff --git a/samples/update_connections_auth.py b/samples/update_connections_auth.py
index f0c8dd852..f99a866ef 100644
--- a/samples/update_connections_auth.py
+++ b/samples/update_connections_auth.py
@@ -22,9 +22,9 @@ def main():
# 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("--datasource_username", help="Datasource username (optional)")
+ parser.add_argument("--authentication_type", help="Authentication type (optional)")
+ parser.add_argument("--datasource_password", help="Datasource password (optional)")
parser.add_argument(
"--embed_password", default="true", choices=["true", "false"], help="Embed password (default: true)"
)
diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py
index 6a734f7b3..7608f908e 100644
--- a/tableauserverclient/server/endpoint/datasources_endpoint.py
+++ b/tableauserverclient/server/endpoint/datasources_endpoint.py
@@ -379,7 +379,7 @@ def update_connections(
self,
datasource_item: DatasourceItem,
connection_luids: Iterable[str],
- authentication_type: str,
+ authentication_type: Optional[str] = None,
username: Optional[str] = None,
password: Optional[str] = None,
embed_password: Optional[bool] = None,
@@ -387,6 +387,9 @@ def update_connections(
"""
Bulk updates one or more datasource connections by LUID.
+ This method allows updating authentication type, credentials, and other
+ connection properties for multiple connections at once.
+
Parameters
----------
datasource_item : DatasourceItem
@@ -395,8 +398,9 @@ def update_connections(
connection_luids : Iterable of str
The connection LUIDs to update.
- authentication_type : str
- The authentication type to use (e.g., 'auth-keypair').
+ authentication_type : str, optional
+ The authentication type to use (e.g., 'auth-keypair', 'AD Service Principal').
+ If not provided, the existing authentication type is preserved.
username : str, optional
The username to set.
diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py
index 218a4016f..7db0a3d67 100644
--- a/tableauserverclient/server/endpoint/workbooks_endpoint.py
+++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py
@@ -341,13 +341,16 @@ def update_connections(
self,
workbook_item: WorkbookItem,
connection_luids: Iterable[str],
- authentication_type: str,
+ authentication_type: Optional[str] = None,
username: Optional[str] = None,
password: Optional[str] = None,
embed_password: Optional[bool] = None,
) -> list[ConnectionItem]:
"""
- Bulk updates one or more workbook connections by LUID, including authenticationType, username, password, and embedPassword.
+ Bulk updates one or more workbook connections by LUID.
+
+ This method allows updating authentication type, credentials, and other
+ connection properties for multiple connections at once.
Parameters
----------
@@ -357,8 +360,9 @@ def update_connections(
connection_luids : Iterable of str
The connection LUIDs to update.
- authentication_type : str
- The authentication type to use (e.g., 'AD Service Principal').
+ authentication_type : str, optional
+ The authentication type to use (e.g., 'AD Service Principal', 'auth-keypair').
+ If not provided, the existing authentication type is preserved.
username : str, optional
The username to set (e.g., client ID for keypair auth).
diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py
index 57deb6e26..589b6beb4 100644
--- a/tableauserverclient/server/request_factory.py
+++ b/tableauserverclient/server/request_factory.py
@@ -254,7 +254,7 @@ def update_connections_req(
self,
element: ET.Element,
connection_luids: Iterable[str],
- authentication_type: str,
+ authentication_type: Optional[str] = None,
username: Optional[str] = None,
password: Optional[str] = None,
embed_password: Optional[bool] = None,
@@ -264,7 +264,8 @@ def update_connections_req(
ET.SubElement(conn_luids_elem, "connectionLUID").text = luid
connection_elem = ET.SubElement(element, "connection")
- connection_elem.set("authenticationType", authentication_type)
+ if authentication_type is not None:
+ connection_elem.set("authenticationType", authentication_type)
if username is not None:
connection_elem.set("userName", username)
@@ -1172,7 +1173,7 @@ def update_connections_req(
self,
element: ET.Element,
connection_luids: Iterable[str],
- authentication_type: str,
+ authentication_type: Optional[str] = None,
username: Optional[str] = None,
password: Optional[str] = None,
embed_password: Optional[bool] = None,
@@ -1182,7 +1183,8 @@ def update_connections_req(
ET.SubElement(conn_luids_elem, "connectionLUID").text = luid
connection_elem = ET.SubElement(element, "connection")
- connection_elem.set("authenticationType", authentication_type)
+ if authentication_type is not None:
+ connection_elem.set("authenticationType", authentication_type)
if username is not None:
connection_elem.set("userName", username)
diff --git a/test/assets/datasource_connections_update_no_auth.xml b/test/assets/datasource_connections_update_no_auth.xml
new file mode 100644
index 000000000..b9d1bf3f0
--- /dev/null
+++ b/test/assets/datasource_connections_update_no_auth.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/test/assets/workbook_update_connections_no_auth.xml b/test/assets/workbook_update_connections_no_auth.xml
new file mode 100644
index 000000000..21860fa06
--- /dev/null
+++ b/test/assets/workbook_update_connections_no_auth.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/test/test_datasource.py b/test/test_datasource.py
index 56eb11ab7..a0890f3a5 100644
--- a/test/test_datasource.py
+++ b/test/test_datasource.py
@@ -35,6 +35,7 @@
UPDATE_HYPER_DATA_XML = TEST_ASSET_DIR / "datasource_data_update.xml"
UPDATE_CONNECTION_XML = TEST_ASSET_DIR / "datasource_connection_update.xml"
UPDATE_CONNECTIONS_XML = TEST_ASSET_DIR / "datasource_connections_update.xml"
+UPDATE_CONNECTIONS_NO_AUTH_XML = TEST_ASSET_DIR / "datasource_connections_update_no_auth.xml"
@pytest.fixture(scope="function")
@@ -276,6 +277,44 @@ def test_update_connections(server) -> None:
assert "auth-keypair" == connection_items[0].auth_type
+def test_update_connections_without_auth_type(server) -> None:
+ """Test that update_connections works when authentication_type is not provided."""
+ populate_xml = POPULATE_CONNECTIONS_XML.read_text()
+ response_xml = UPDATE_CONNECTIONS_NO_AUTH_XML.read_text()
+
+ 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"
+ server.version = "3.26"
+
+ 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,
+ )
+
+ # Update connections without specifying authentication_type
+ connection_items = server.datasources.update_connections(
+ datasource_item=datasource,
+ connection_luids=connection_luids,
+ username="user1",
+ embed_password=True,
+ )
+ updated_ids = [conn.id for conn in connection_items]
+
+ assert updated_ids == connection_luids
+ # Verify that the auth type from the response is preserved (UsernamePassword)
+ assert connection_items[0].auth_type == "UsernamePassword"
+
+
def test_populate_permissions(server) -> None:
response_xml = POPULATE_PERMISSIONS_XML.read_text()
with requests_mock.mock() as m:
diff --git a/test/test_workbook.py b/test/test_workbook.py
index b210e8402..c5c4f6662 100644
--- a/test/test_workbook.py
+++ b/test/test_workbook.py
@@ -39,6 +39,7 @@
UPDATE_XML = TEST_ASSET_DIR / "workbook_update.xml"
UPDATE_PERMISSIONS = TEST_ASSET_DIR / "workbook_update_permissions.xml"
UPDATE_CONNECTIONS_XML = TEST_ASSET_DIR / "workbook_update_connections.xml"
+UPDATE_CONNECTIONS_NO_AUTH_XML = TEST_ASSET_DIR / "workbook_update_connections_no_auth.xml"
@pytest.fixture(scope="function")
@@ -1047,6 +1048,42 @@ def test_update_workbook_connections(server: TSC.Server) -> None:
assert "AD Service Principal" == connection_items[0].auth_type
+def test_update_workbook_connections_without_auth_type(server: TSC.Server) -> None:
+ """Test that update_connections works when authentication_type is not provided."""
+ populate_xml = POPULATE_CONNECTIONS_XML.read_text()
+ response_xml = UPDATE_CONNECTIONS_NO_AUTH_XML.read_text()
+
+ 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
+ server.version = "3.26"
+ url = f"{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,
+ )
+
+ # Update connections without specifying authentication_type
+ connection_items = server.workbooks.update_connections(
+ workbook_item=workbook,
+ connection_luids=connection_luids,
+ username="user1",
+ embed_password=True,
+ )
+ updated_ids = [conn.id for conn in connection_items]
+
+ assert updated_ids == connection_luids
+ # Verify that the auth type from the response is preserved (UsernamePassword)
+ assert connection_items[0].auth_type == "UsernamePassword"
+
+
def test_get_workbook_all_fields(server: TSC.Server) -> None:
server.version = "3.21"
baseurl = server.workbooks.baseurl