Skip to content

Commit 314c9d4

Browse files
Fix W-21968404: Make authentication_type optional in update_connections (#1778)
1 parent ec0614e commit 314c9d4

8 files changed

Lines changed: 142 additions & 14 deletions

samples/update_connections_auth.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ def main():
2222
# Resource-specific
2323
parser.add_argument("resource_type", choices=["workbook", "datasource"])
2424
parser.add_argument("resource_id")
25-
parser.add_argument("datasource_username")
26-
parser.add_argument("authentication_type")
27-
parser.add_argument("--datasource_password", default=None, help="Datasource password (optional)")
25+
parser.add_argument("--datasource_username", help="Datasource username (optional)")
26+
parser.add_argument("--authentication_type", help="Authentication type (optional)")
27+
parser.add_argument("--datasource_password", help="Datasource password (optional)")
2828
parser.add_argument(
2929
"--embed_password", default="true", choices=["true", "false"], help="Embed password (default: true)"
3030
)

tableauserverclient/server/endpoint/datasources_endpoint.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -379,14 +379,17 @@ def update_connections(
379379
self,
380380
datasource_item: DatasourceItem,
381381
connection_luids: Iterable[str],
382-
authentication_type: str,
382+
authentication_type: Optional[str] = None,
383383
username: Optional[str] = None,
384384
password: Optional[str] = None,
385385
embed_password: Optional[bool] = None,
386386
) -> list[ConnectionItem]:
387387
"""
388388
Bulk updates one or more datasource connections by LUID.
389389
390+
This method allows updating authentication type, credentials, and other
391+
connection properties for multiple connections at once.
392+
390393
Parameters
391394
----------
392395
datasource_item : DatasourceItem
@@ -395,8 +398,9 @@ def update_connections(
395398
connection_luids : Iterable of str
396399
The connection LUIDs to update.
397400
398-
authentication_type : str
399-
The authentication type to use (e.g., 'auth-keypair').
401+
authentication_type : str, optional
402+
The authentication type to use (e.g., 'auth-keypair', 'AD Service Principal').
403+
If not provided, the existing authentication type is preserved.
400404
401405
username : str, optional
402406
The username to set.

tableauserverclient/server/endpoint/workbooks_endpoint.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,13 +341,16 @@ def update_connections(
341341
self,
342342
workbook_item: WorkbookItem,
343343
connection_luids: Iterable[str],
344-
authentication_type: str,
344+
authentication_type: Optional[str] = None,
345345
username: Optional[str] = None,
346346
password: Optional[str] = None,
347347
embed_password: Optional[bool] = None,
348348
) -> list[ConnectionItem]:
349349
"""
350-
Bulk updates one or more workbook connections by LUID, including authenticationType, username, password, and embedPassword.
350+
Bulk updates one or more workbook connections by LUID.
351+
352+
This method allows updating authentication type, credentials, and other
353+
connection properties for multiple connections at once.
351354
352355
Parameters
353356
----------
@@ -357,8 +360,9 @@ def update_connections(
357360
connection_luids : Iterable of str
358361
The connection LUIDs to update.
359362
360-
authentication_type : str
361-
The authentication type to use (e.g., 'AD Service Principal').
363+
authentication_type : str, optional
364+
The authentication type to use (e.g., 'AD Service Principal', 'auth-keypair').
365+
If not provided, the existing authentication type is preserved.
362366
363367
username : str, optional
364368
The username to set (e.g., client ID for keypair auth).

tableauserverclient/server/request_factory.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ def update_connections_req(
254254
self,
255255
element: ET.Element,
256256
connection_luids: Iterable[str],
257-
authentication_type: str,
257+
authentication_type: Optional[str] = None,
258258
username: Optional[str] = None,
259259
password: Optional[str] = None,
260260
embed_password: Optional[bool] = None,
@@ -264,7 +264,8 @@ def update_connections_req(
264264
ET.SubElement(conn_luids_elem, "connectionLUID").text = luid
265265

266266
connection_elem = ET.SubElement(element, "connection")
267-
connection_elem.set("authenticationType", authentication_type)
267+
if authentication_type is not None:
268+
connection_elem.set("authenticationType", authentication_type)
268269

269270
if username is not None:
270271
connection_elem.set("userName", username)
@@ -1172,7 +1173,7 @@ def update_connections_req(
11721173
self,
11731174
element: ET.Element,
11741175
connection_luids: Iterable[str],
1175-
authentication_type: str,
1176+
authentication_type: Optional[str] = None,
11761177
username: Optional[str] = None,
11771178
password: Optional[str] = None,
11781179
embed_password: Optional[bool] = None,
@@ -1182,7 +1183,8 @@ def update_connections_req(
11821183
ET.SubElement(conn_luids_elem, "connectionLUID").text = luid
11831184

11841185
connection_elem = ET.SubElement(element, "connection")
1185-
connection_elem.set("authenticationType", authentication_type)
1186+
if authentication_type is not None:
1187+
connection_elem.set("authenticationType", authentication_type)
11861188

11871189
if username is not None:
11881190
connection_elem.set("userName", username)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<tsResponse xmlns="http://tableau.com/api"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://tableau.com/api https://help.tableau.com/samples/en-us/rest_api/ts-api_3_25.xsd">
5+
<connections>
6+
<connection id="be786ae0-d2bf-4a4b-9b34-e2de8d2d4488"
7+
type="sqlserver"
8+
serverAddress="updated-server"
9+
serverPort="1433"
10+
userName="user1"
11+
embedPassword="true"
12+
authenticationType="UsernamePassword" />
13+
<connection id="a1b2c3d4-e5f6-7a8b-9c0d-123456789abc"
14+
type="sqlserver"
15+
serverAddress="updated-server"
16+
serverPort="1433"
17+
userName="user1"
18+
embedPassword="true"
19+
authenticationType="UsernamePassword" />
20+
</connections>
21+
</tsResponse>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<tsResponse xmlns="http://tableau.com/api"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://tableau.com/api https://help.tableau.com/samples/en-us/rest_api/ts-api_3_25.xsd">
5+
<connections>
6+
<connection id="abc12345-def6-7890-gh12-ijklmnopqrst"
7+
type="sqlserver"
8+
serverAddress="updated-db-host"
9+
serverPort="1433"
10+
userName="svc-client"
11+
embedPassword="true"
12+
authenticationType="UsernamePassword" />
13+
<connection id="1234abcd-5678-efgh-ijkl-0987654321mn"
14+
type="sqlserver"
15+
serverAddress="updated-db-host"
16+
serverPort="1433"
17+
userName="svc-client"
18+
embedPassword="true"
19+
authenticationType="UsernamePassword" />
20+
</connections>
21+
</tsResponse>

test/test_datasource.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
UPDATE_HYPER_DATA_XML = TEST_ASSET_DIR / "datasource_data_update.xml"
3636
UPDATE_CONNECTION_XML = TEST_ASSET_DIR / "datasource_connection_update.xml"
3737
UPDATE_CONNECTIONS_XML = TEST_ASSET_DIR / "datasource_connections_update.xml"
38+
UPDATE_CONNECTIONS_NO_AUTH_XML = TEST_ASSET_DIR / "datasource_connections_update_no_auth.xml"
3839

3940

4041
@pytest.fixture(scope="function")
@@ -276,6 +277,44 @@ def test_update_connections(server) -> None:
276277
assert "auth-keypair" == connection_items[0].auth_type
277278

278279

280+
def test_update_connections_without_auth_type(server) -> None:
281+
"""Test that update_connections works when authentication_type is not provided."""
282+
populate_xml = POPULATE_CONNECTIONS_XML.read_text()
283+
response_xml = UPDATE_CONNECTIONS_NO_AUTH_XML.read_text()
284+
285+
with requests_mock.Mocker() as m:
286+
287+
datasource_id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
288+
connection_luids = ["be786ae0-d2bf-4a4b-9b34-e2de8d2d4488", "a1b2c3d4-e5f6-7a8b-9c0d-123456789abc"]
289+
290+
datasource = TSC.DatasourceItem(datasource_id)
291+
datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
292+
datasource.owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
293+
server.version = "3.26"
294+
295+
m.get(
296+
"http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/datasources/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections",
297+
text=populate_xml,
298+
)
299+
m.put(
300+
"http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/datasources/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections",
301+
text=response_xml,
302+
)
303+
304+
# Update connections without specifying authentication_type
305+
connection_items = server.datasources.update_connections(
306+
datasource_item=datasource,
307+
connection_luids=connection_luids,
308+
username="user1",
309+
embed_password=True,
310+
)
311+
updated_ids = [conn.id for conn in connection_items]
312+
313+
assert updated_ids == connection_luids
314+
# Verify that the auth type from the response is preserved (UsernamePassword)
315+
assert connection_items[0].auth_type == "UsernamePassword"
316+
317+
279318
def test_populate_permissions(server) -> None:
280319
response_xml = POPULATE_PERMISSIONS_XML.read_text()
281320
with requests_mock.mock() as m:

test/test_workbook.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
UPDATE_XML = TEST_ASSET_DIR / "workbook_update.xml"
4040
UPDATE_PERMISSIONS = TEST_ASSET_DIR / "workbook_update_permissions.xml"
4141
UPDATE_CONNECTIONS_XML = TEST_ASSET_DIR / "workbook_update_connections.xml"
42+
UPDATE_CONNECTIONS_NO_AUTH_XML = TEST_ASSET_DIR / "workbook_update_connections_no_auth.xml"
4243

4344

4445
@pytest.fixture(scope="function")
@@ -1047,6 +1048,42 @@ def test_update_workbook_connections(server: TSC.Server) -> None:
10471048
assert "AD Service Principal" == connection_items[0].auth_type
10481049

10491050

1051+
def test_update_workbook_connections_without_auth_type(server: TSC.Server) -> None:
1052+
"""Test that update_connections works when authentication_type is not provided."""
1053+
populate_xml = POPULATE_CONNECTIONS_XML.read_text()
1054+
response_xml = UPDATE_CONNECTIONS_NO_AUTH_XML.read_text()
1055+
1056+
with requests_mock.Mocker() as m:
1057+
workbook_id = "1a2b3c4d-5e6f-7a8b-9c0d-112233445566"
1058+
connection_luids = ["abc12345-def6-7890-gh12-ijklmnopqrst", "1234abcd-5678-efgh-ijkl-0987654321mn"]
1059+
1060+
workbook = TSC.WorkbookItem(workbook_id)
1061+
workbook._id = workbook_id
1062+
server.version = "3.26"
1063+
url = f"{server.baseurl}/{workbook_id}/connections"
1064+
m.get(
1065+
"http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks/1a2b3c4d-5e6f-7a8b-9c0d-112233445566/connections",
1066+
text=populate_xml,
1067+
)
1068+
m.put(
1069+
"http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks/1a2b3c4d-5e6f-7a8b-9c0d-112233445566/connections",
1070+
text=response_xml,
1071+
)
1072+
1073+
# Update connections without specifying authentication_type
1074+
connection_items = server.workbooks.update_connections(
1075+
workbook_item=workbook,
1076+
connection_luids=connection_luids,
1077+
username="user1",
1078+
embed_password=True,
1079+
)
1080+
updated_ids = [conn.id for conn in connection_items]
1081+
1082+
assert updated_ids == connection_luids
1083+
# Verify that the auth type from the response is preserved (UsernamePassword)
1084+
assert connection_items[0].auth_type == "UsernamePassword"
1085+
1086+
10501087
def test_get_workbook_all_fields(server: TSC.Server) -> None:
10511088
server.version = "3.21"
10521089
baseurl = server.workbooks.baseurl

0 commit comments

Comments
 (0)