Skip to content

Commit 746b345

Browse files
committed
chore: refactor XML payload into RequestFactory
Also correct the type hints to clarify that it accepts any Iterable.
1 parent 88985fe commit 746b345

File tree

6 files changed

+142
-113
lines changed

6 files changed

+142
-113
lines changed

tableauserverclient/models/connection_item.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,6 @@ def connection_type(self) -> Optional[str]:
8484
def query_tagging(self) -> Optional[bool]:
8585
return self._query_tagging
8686

87-
@property
88-
def auth_type(self) -> Optional[str]:
89-
return self._auth_type
90-
9187
@query_tagging.setter
9288
@property_is_boolean
9389
def query_tagging(self, value: Optional[bool]):
@@ -99,6 +95,14 @@ def query_tagging(self, value: Optional[bool]):
9995
return
10096
self._query_tagging = value
10197

98+
@property
99+
def auth_type(self) -> Optional[str]:
100+
return self._auth_type
101+
102+
@auth_type.setter
103+
def auth_type(self, value: Optional[str]):
104+
self._auth_type = value
105+
102106
def __repr__(self):
103107
return "<ConnectionItem#{_id} embed={embed_password} type={_connection_type} auth={_auth_type} username={username}>".format(
104108
**self.__dict__

tableauserverclient/server/endpoint/datasources_endpoint.py

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,14 @@ def update_connection(
321321

322322
@api(version="3.26")
323323
def update_connections(
324-
self, datasource_item: DatasourceItem, connection_luids: list[str], authentication_type: str, username: Optional[str] = None, password: Optional[str] = None, embed_password: Optional[bool] = None
325-
) -> list[str]:
324+
self,
325+
datasource_item: DatasourceItem,
326+
connection_luids: Iterable[str],
327+
authentication_type: str,
328+
username: Optional[str] = None,
329+
password: Optional[str] = None,
330+
embed_password: Optional[bool] = None,
331+
) -> Iterable[str]:
326332
"""
327333
Bulk updates one or more datasource connections by LUID.
328334
@@ -331,7 +337,7 @@ def update_connections(
331337
datasource_item : DatasourceItem
332338
The datasource item containing the connections.
333339
334-
connection_luids : list of str
340+
connection_luids : Iterable of str
335341
The connection LUIDs to update.
336342
337343
authentication_type : str
@@ -348,41 +354,23 @@ def update_connections(
348354
349355
Returns
350356
-------
351-
list of str
357+
Iterable of str
352358
The connection LUIDs that were updated.
353359
"""
354-
from xml.etree.ElementTree import Element, SubElement, tostring
355360

356361
url = f"{self.baseurl}/{datasource_item.id}/connections"
357362
print("Method URL:", url)
358363

359-
ts_request = Element("tsRequest")
360-
361-
# <connectionLuids>
362-
conn_luids_elem = SubElement(ts_request, "connectionLuids")
363-
for luid in connection_luids:
364-
SubElement(conn_luids_elem, "connectionLuid").text = luid
365-
366-
# <connection>
367-
connection_elem = SubElement(ts_request, "connection")
368-
connection_elem.set("authenticationType", authentication_type)
369-
370-
if username:
371-
connection_elem.set("userName", username)
372-
373-
if password:
374-
connection_elem.set("password", password)
375-
376-
if embed_password is not None:
377-
connection_elem.set("embedPassword", str(embed_password).lower())
378-
379-
request_body = tostring(ts_request)
380-
364+
request_body = RequestFactory.Datasource.update_connections_req(
365+
connection_luids=connection_luids,
366+
authentication_type=authentication_type,
367+
username=username,
368+
password=password,
369+
embed_password=embed_password,
370+
)
381371
response = self.put_request(url, request_body)
382372

383-
logger.info(
384-
f"Updated connections for datasource {datasource_item.id}: {', '.join(connection_luids)}"
385-
)
373+
logger.info(f"Updated connections for datasource {datasource_item.id}: {', '.join(connection_luids)}")
386374
return connection_luids
387375

388376
@api(version="2.8")

tableauserverclient/server/endpoint/workbooks_endpoint.py

Lines changed: 42 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -327,69 +327,59 @@ def update_connection(self, workbook_item: WorkbookItem, connection_item: Connec
327327

328328
# Update workbook_connections
329329
@api(version="3.26")
330-
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
331-
) -> list[str]:
332-
"""
333-
Bulk updates one or more workbook connections by LUID, including authenticationType, username, password, and embedPassword.
334-
335-
Parameters
336-
----------
337-
workbook_item : WorkbookItem
338-
The workbook item containing the connections.
339-
340-
connection_luids : list of str
341-
The connection LUIDs to update.
342-
343-
authentication_type : str
344-
The authentication type to use (e.g., 'AD Service Principal').
345-
346-
username : str, optional
347-
The username to set (e.g., client ID for keypair auth).
348-
349-
password : str, optional
350-
The password or secret to set.
351-
352-
embed_password : bool, optional
353-
Whether to embed the password.
330+
def update_connections(
331+
self,
332+
workbook_item: WorkbookItem,
333+
connection_luids: Iterable[str],
334+
authentication_type: str,
335+
username: Optional[str] = None,
336+
password: Optional[str] = None,
337+
embed_password: Optional[bool] = None,
338+
) -> Iterable[str]:
339+
"""
340+
Bulk updates one or more workbook connections by LUID, including authenticationType, username, password, and embedPassword.
354341
355-
Returns
356-
-------
357-
list of str
358-
The connection LUIDs that were updated.
359-
"""
360-
from xml.etree.ElementTree import Element, SubElement, tostring
342+
Parameters
343+
----------
344+
workbook_item : WorkbookItem
345+
The workbook item containing the connections.
361346
362-
url = f"{self.baseurl}/{workbook_item.id}/connections"
347+
connection_luids : Iterable of str
348+
The connection LUIDs to update.
363349
364-
ts_request = Element("tsRequest")
350+
authentication_type : str
351+
The authentication type to use (e.g., 'AD Service Principal').
365352
366-
# <connectionLuids>
367-
conn_luids_elem = SubElement(ts_request, "connectionLuids")
368-
for luid in connection_luids:
369-
SubElement(conn_luids_elem, "connectionLuid").text = luid
353+
username : str, optional
354+
The username to set (e.g., client ID for keypair auth).
370355
371-
# <connection>
372-
connection_elem = SubElement(ts_request, "connection")
373-
connection_elem.set("authenticationType", authentication_type)
356+
password : str, optional
357+
The password or secret to set.
374358
375-
if username:
376-
connection_elem.set("userName", username)
359+
embed_password : bool, optional
360+
Whether to embed the password.
377361
378-
if password:
379-
connection_elem.set("password", password)
362+
Returns
363+
-------
364+
Iterable of str
365+
The connection LUIDs that were updated.
366+
"""
380367

381-
if embed_password is not None:
382-
connection_elem.set("embedPassword", str(embed_password).lower())
368+
url = f"{self.baseurl}/{workbook_item.id}/connections"
383369

384-
request_body = tostring(ts_request)
370+
request_body = RequestFactory.Workbook.update_connections_req(
371+
connection_luids,
372+
authentication_type,
373+
username=username,
374+
password=password,
375+
embed_password=embed_password,
376+
)
385377

386-
# Send request
387-
response = self.put_request(url, request_body)
378+
# Send request
379+
response = self.put_request(url, request_body)
388380

389-
logger.info(
390-
f"Updated connections for workbook {workbook_item.id}: {', '.join(connection_luids)}"
391-
)
392-
return connection_luids
381+
logger.info(f"Updated connections for workbook {workbook_item.id}: {', '.join(connection_luids)}")
382+
return connection_luids
393383

394384
# Download workbook contents with option of passing in filepath
395385
@api(version="2.0")

tableauserverclient/server/request_factory.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,32 @@ def publish_req_chunked(self, datasource_item, connection_credentials=None, conn
244244
parts = {"request_payload": ("", xml_request, "text/xml")}
245245
return _add_multipart(parts)
246246

247+
@_tsrequest_wrapped
248+
def update_connections_req(
249+
self,
250+
element: ET.Element,
251+
connection_luids: Iterable[str],
252+
authentication_type: str,
253+
username: Optional[str] = None,
254+
password: Optional[str] = None,
255+
embed_password: Optional[bool] = None,
256+
):
257+
conn_luids_elem = ET.SubElement(element, "connectionLUIDs")
258+
for luid in connection_luids:
259+
ET.SubElement(conn_luids_elem, "connectionLUID").text = luid
260+
261+
connection_elem = ET.SubElement(element, "connection")
262+
connection_elem.set("authenticationType", authentication_type)
263+
264+
if username is not None:
265+
connection_elem.set("userName", username)
266+
267+
if password is not None:
268+
connection_elem.set("password", password)
269+
270+
if embed_password is not None:
271+
connection_elem.set("embedPassword", str(embed_password).lower())
272+
247273

248274
class DQWRequest:
249275
def add_req(self, dqw_item):
@@ -1092,6 +1118,32 @@ def embedded_extract_req(
10921118
if (id_ := datasource_item.id) is not None:
10931119
datasource_element.attrib["id"] = id_
10941120

1121+
@_tsrequest_wrapped
1122+
def update_connections_req(
1123+
self,
1124+
element: ET.Element,
1125+
connection_luids: Iterable[str],
1126+
authentication_type: str,
1127+
username: Optional[str] = None,
1128+
password: Optional[str] = None,
1129+
embed_password: Optional[bool] = None,
1130+
):
1131+
conn_luids_elem = ET.SubElement(element, "connectionLUIDs")
1132+
for luid in connection_luids:
1133+
ET.SubElement(conn_luids_elem, "connectionLUID").text = luid
1134+
1135+
connection_elem = ET.SubElement(element, "connection")
1136+
connection_elem.set("authenticationType", authentication_type)
1137+
1138+
if username is not None:
1139+
connection_elem.set("userName", username)
1140+
1141+
if password is not None:
1142+
connection_elem.set("password", password)
1143+
1144+
if embed_password is not None:
1145+
connection_elem.set("embedPassword", str(embed_password).lower())
1146+
10951147

10961148
class Connection:
10971149
@_tsrequest_wrapped

test/test_datasource.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -219,29 +219,27 @@ def test_update_connection(self) -> None:
219219
self.assertEqual("foo", new_connection.username)
220220

221221
def test_update_connections(self) -> None:
222-
populate_xml, response_xml = read_xml_assets(
223-
POPULATE_CONNECTIONS_XML,
224-
UPDATE_CONNECTIONS_XML
225-
)
222+
populate_xml, response_xml = read_xml_assets(POPULATE_CONNECTIONS_XML, UPDATE_CONNECTIONS_XML)
226223

227224
with requests_mock.Mocker() as m:
228225

229226
datasource_id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
230-
connection_luids = [
231-
"be786ae0-d2bf-4a4b-9b34-e2de8d2d4488",
232-
"a1b2c3d4-e5f6-7a8b-9c0d-123456789abc"
233-
]
227+
connection_luids = ["be786ae0-d2bf-4a4b-9b34-e2de8d2d4488", "a1b2c3d4-e5f6-7a8b-9c0d-123456789abc"]
234228

235229
datasource = TSC.DatasourceItem(datasource_id)
236230
datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
237231
datasource.owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
238232
self.server.version = "3.26"
239233

240234
url = f"{self.server.baseurl}/{datasource.id}/connections"
241-
m.get("http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/datasources/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections", text=populate_xml)
242-
m.put("http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/datasources/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections", text=response_xml)
243-
244-
235+
m.get(
236+
"http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/datasources/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections",
237+
text=populate_xml,
238+
)
239+
m.put(
240+
"http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/datasources/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections",
241+
text=response_xml,
242+
)
245243

246244
print("BASEURL:", self.server.baseurl)
247245
print("Calling PUT on:", f"{self.server.baseurl}/{datasource.id}/connections")
@@ -252,12 +250,11 @@ def test_update_connections(self) -> None:
252250
authentication_type="auth-keypair",
253251
username="testuser",
254252
password="testpass",
255-
embed_password=True
253+
embed_password=True,
256254
)
257255

258256
self.assertEqual(updated_luids, connection_luids)
259257

260-
261258
def test_populate_permissions(self) -> None:
262259
with open(asset(POPULATE_PERMISSIONS_XML), "rb") as f:
263260
response_xml = f.read().decode("utf-8")

test/test_workbook.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -982,34 +982,32 @@ def test_odata_connection(self) -> None:
982982
self.assertEqual(xml_connection.get("serverAddress"), url)
983983

984984
def test_update_workbook_connections(self) -> None:
985-
populate_xml, response_xml = read_xml_assets(
986-
POPULATE_CONNECTIONS_XML,
987-
UPDATE_CONNECTIONS_XML
988-
)
989-
985+
populate_xml, response_xml = read_xml_assets(POPULATE_CONNECTIONS_XML, UPDATE_CONNECTIONS_XML)
990986

991987
with requests_mock.Mocker() as m:
992988
workbook_id = "1a2b3c4d-5e6f-7a8b-9c0d-112233445566"
993-
connection_luids = [
994-
"abc12345-def6-7890-gh12-ijklmnopqrst",
995-
"1234abcd-5678-efgh-ijkl-0987654321mn"
996-
]
989+
connection_luids = ["abc12345-def6-7890-gh12-ijklmnopqrst", "1234abcd-5678-efgh-ijkl-0987654321mn"]
997990

998991
workbook = TSC.WorkbookItem(workbook_id)
999992
workbook._id = workbook_id
1000993
self.server.version = "3.26"
1001994
url = f"{self.server.baseurl}/{workbook_id}/connections"
1002-
m.get("http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks/1a2b3c4d-5e6f-7a8b-9c0d-112233445566/connections", text=populate_xml)
1003-
m.put("http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks/1a2b3c4d-5e6f-7a8b-9c0d-112233445566/connections", text=response_xml)
1004-
995+
m.get(
996+
"http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks/1a2b3c4d-5e6f-7a8b-9c0d-112233445566/connections",
997+
text=populate_xml,
998+
)
999+
m.put(
1000+
"http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks/1a2b3c4d-5e6f-7a8b-9c0d-112233445566/connections",
1001+
text=response_xml,
1002+
)
10051003

10061004
updated_luids = self.server.workbooks.update_connections(
10071005
workbook_item=workbook,
10081006
connection_luids=connection_luids,
10091007
authentication_type="AD Service Principal",
10101008
username="svc-client",
10111009
password="secret-token",
1012-
embed_password=True
1010+
embed_password=True,
10131011
)
10141012

10151013
self.assertEqual(updated_luids, connection_luids)

0 commit comments

Comments
 (0)