Skip to content

Commit 596e8d1

Browse files
authored
Merge branch 'development' into copilot/update-deprecated-cgi-module
2 parents 3ee4a92 + 6acd12f commit 596e8d1

9 files changed

Lines changed: 143 additions & 14 deletions

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ name="tableauserverclient"
88
dynamic = ["version"]
99
description='A Python module for working with the Tableau Server REST API.'
1010
authors = [{name="Tableau", email="github@tableau.com"}]
11+
license = "MIT"
1112
license-files = ["LICENSE"]
1213
readme = "README.md"
1314

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
@@ -378,14 +378,17 @@ def update_connections(
378378
self,
379379
datasource_item: DatasourceItem,
380380
connection_luids: Iterable[str],
381-
authentication_type: str,
381+
authentication_type: Optional[str] = None,
382382
username: Optional[str] = None,
383383
password: Optional[str] = None,
384384
embed_password: Optional[bool] = None,
385385
) -> list[ConnectionItem]:
386386
"""
387387
Bulk updates one or more datasource connections by LUID.
388388
389+
This method allows updating authentication type, credentials, and other
390+
connection properties for multiple connections at once.
391+
389392
Parameters
390393
----------
391394
datasource_item : DatasourceItem
@@ -394,8 +397,9 @@ def update_connections(
394397
connection_luids : Iterable of str
395398
The connection LUIDs to update.
396399
397-
authentication_type : str
398-
The authentication type to use (e.g., 'auth-keypair').
400+
authentication_type : str, optional
401+
The authentication type to use (e.g., 'auth-keypair', 'AD Service Principal').
402+
If not provided, the existing authentication type is preserved.
399403
400404
username : str, optional
401405
The username to set.

tableauserverclient/server/endpoint/workbooks_endpoint.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -340,13 +340,16 @@ def update_connections(
340340
self,
341341
workbook_item: WorkbookItem,
342342
connection_luids: Iterable[str],
343-
authentication_type: str,
343+
authentication_type: Optional[str] = None,
344344
username: Optional[str] = None,
345345
password: Optional[str] = None,
346346
embed_password: Optional[bool] = None,
347347
) -> list[ConnectionItem]:
348348
"""
349-
Bulk updates one or more workbook connections by LUID, including authenticationType, username, password, and embedPassword.
349+
Bulk updates one or more workbook connections by LUID.
350+
351+
This method allows updating authentication type, credentials, and other
352+
connection properties for multiple connections at once.
350353
351354
Parameters
352355
----------
@@ -356,8 +359,9 @@ def update_connections(
356359
connection_luids : Iterable of str
357360
The connection LUIDs to update.
358361
359-
authentication_type : str
360-
The authentication type to use (e.g., 'AD Service Principal').
362+
authentication_type : str, optional
363+
The authentication type to use (e.g., 'AD Service Principal', 'auth-keypair').
364+
If not provided, the existing authentication type is preserved.
361365
362366
username : str, optional
363367
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)