Skip to content

Commit 88985fe

Browse files
committed
Updated TSC with new API's
1 parent 5e49f38 commit 88985fe

File tree

9 files changed

+388
-2
lines changed

9 files changed

+388
-2
lines changed

samples/update_connection_auth.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import argparse
2+
import logging
3+
import tableauserverclient as TSC
4+
5+
6+
def main():
7+
parser = argparse.ArgumentParser(description="Update a single connection on a datasource or workbook to embed credentials")
8+
9+
# Common options
10+
parser.add_argument("--server", "-s", help="Server address", required=True)
11+
parser.add_argument("--site", "-S", help="Site name", required=True)
12+
parser.add_argument("--token-name", "-p", help="Personal access token name", required=True)
13+
parser.add_argument("--token-value", "-v", help="Personal access token value", required=True)
14+
parser.add_argument(
15+
"--logging-level", "-l",
16+
choices=["debug", "info", "error"],
17+
default="error",
18+
help="Logging level (default: error)",
19+
)
20+
21+
# Resource and connection details
22+
parser.add_argument("resource_type", choices=["workbook", "datasource"])
23+
parser.add_argument("resource_id", help="Workbook or datasource ID")
24+
parser.add_argument("connection_id", help="Connection ID to update")
25+
parser.add_argument("datasource_username", help="Username to set for the connection")
26+
parser.add_argument("datasource_password", help="Password to set for the connection")
27+
parser.add_argument("authentication_type", help="Authentication type")
28+
29+
args = parser.parse_args()
30+
31+
# Logging setup
32+
logging_level = getattr(logging, args.logging_level.upper())
33+
logging.basicConfig(level=logging_level)
34+
35+
tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site)
36+
server = TSC.Server(args.server, use_server_version=True)
37+
38+
with server.auth.sign_in(tableau_auth):
39+
endpoint = {
40+
"workbook": server.workbooks,
41+
"datasource": server.datasources
42+
}.get(args.resource_type)
43+
44+
update_function = endpoint.update_connection
45+
resource = endpoint.get_by_id(args.resource_id)
46+
endpoint.populate_connections(resource)
47+
48+
connections = [conn for conn in resource.connections if conn.id == args.connection_id]
49+
assert len(connections) == 1, f"Connection ID '{args.connection_id}' not found."
50+
51+
connection = connections[0]
52+
connection.username = args.datasource_username
53+
connection.password = args.datasource_password
54+
connection.authentication_type = args.authentication_type
55+
connection.embed_password = True
56+
57+
updated_connection = update_function(resource, connection)
58+
print(f"Updated connection: {updated_connection.__dict__}")
59+
60+
61+
if __name__ == "__main__":
62+
main()

samples/update_connections_auth.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import argparse
2+
import logging
3+
import tableauserverclient as TSC
4+
5+
6+
def main():
7+
parser = argparse.ArgumentParser(description="Bulk update all workbook or datasource connections")
8+
9+
# Common options
10+
parser.add_argument("--server", "-s", help="Server address", required=True)
11+
parser.add_argument("--site", "-S", help="Site name", required=True)
12+
parser.add_argument("--username", "-p", help="Personal access token name", required=True)
13+
parser.add_argument("--password", "-v", help="Personal access token value", required=True)
14+
parser.add_argument(
15+
"--logging-level",
16+
"-l",
17+
choices=["debug", "info", "error"],
18+
default="error",
19+
help="Logging level (default: error)",
20+
)
21+
22+
# Resource-specific
23+
parser.add_argument("resource_type", choices=["workbook", "datasource"])
24+
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)")
28+
parser.add_argument("--embed_password", default="true", choices=["true", "false"], help="Embed password (default: true)")
29+
30+
args = parser.parse_args()
31+
32+
# Set logging level
33+
logging_level = getattr(logging, args.logging_level.upper())
34+
logging.basicConfig(level=logging_level)
35+
36+
tableau_auth = TSC.TableauAuth(args.username, args.password, site_id=args.site)
37+
server = TSC.Server(args.server, use_server_version=True)
38+
39+
with server.auth.sign_in(tableau_auth):
40+
endpoint = {
41+
"workbook": server.workbooks,
42+
"datasource": server.datasources
43+
}.get(args.resource_type)
44+
45+
resource = endpoint.get_by_id(args.resource_id)
46+
endpoint.populate_connections(resource)
47+
48+
connection_luids = [conn.id for conn in resource.connections]
49+
embed_password = args.embed_password.lower() == "true"
50+
51+
# Call unified update_connections method
52+
updated_ids = endpoint.update_connections(
53+
resource,
54+
connection_luids=connection_luids,
55+
authentication_type=args.authentication_type,
56+
username=args.datasource_username,
57+
password=args.datasource_password,
58+
embed_password=embed_password
59+
)
60+
61+
print(f"Updated connections on {args.resource_type} {args.resource_id}: {updated_ids}")
62+
63+
64+
if __name__ == "__main__":
65+
main()

tableauserverclient/models/connection_item.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ class ConnectionItem:
4141
server_port: str
4242
The port used for the connection.
4343
44+
auth_type: str
45+
Specifies the type of authentication used by the connection.
46+
4447
connection_credentials: ConnectionCredentials
4548
The Connection Credentials object containing authentication details for
4649
the connection. Replaces username/password/embed_password when
@@ -59,6 +62,7 @@ def __init__(self):
5962
self.username: Optional[str] = None
6063
self.connection_credentials: Optional[ConnectionCredentials] = None
6164
self._query_tagging: Optional[bool] = None
65+
self._auth_type: Optional[str] = None
6266

6367
@property
6468
def datasource_id(self) -> Optional[str]:
@@ -80,6 +84,10 @@ def connection_type(self) -> Optional[str]:
8084
def query_tagging(self) -> Optional[bool]:
8185
return self._query_tagging
8286

87+
@property
88+
def auth_type(self) -> Optional[str]:
89+
return self._auth_type
90+
8391
@query_tagging.setter
8492
@property_is_boolean
8593
def query_tagging(self, value: Optional[bool]):
@@ -92,7 +100,7 @@ def query_tagging(self, value: Optional[bool]):
92100
self._query_tagging = value
93101

94102
def __repr__(self):
95-
return "<ConnectionItem#{_id} embed={embed_password} type={_connection_type} username={username}>".format(
103+
return "<ConnectionItem#{_id} embed={embed_password} type={_connection_type} auth={_auth_type} username={username}>".format(
96104
**self.__dict__
97105
)
98106

@@ -112,6 +120,7 @@ def from_response(cls, resp, ns) -> list["ConnectionItem"]:
112120
connection_item._query_tagging = (
113121
string_to_bool(s) if (s := connection_xml.get("queryTagging", None)) else None
114122
)
123+
connection_item._auth_type = connection_xml.get("authenticationType", None)
115124
datasource_elem = connection_xml.find(".//t:datasource", namespaces=ns)
116125
if datasource_elem is not None:
117126
connection_item._datasource_id = datasource_elem.get("id", None)
@@ -139,6 +148,7 @@ def from_xml_element(cls, parsed_response, ns) -> list["ConnectionItem"]:
139148

140149
connection_item.server_address = connection_xml.get("serverAddress", None)
141150
connection_item.server_port = connection_xml.get("serverPort", None)
151+
connection_item._auth_type = connection_xml.get("authenticationType", None)
142152

143153
connection_credentials = connection_xml.find(".//t:connectionCredentials", namespaces=ns)
144154

tableauserverclient/server/endpoint/datasources_endpoint.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,72 @@ def update_connection(
319319
logger.info(f"Updated datasource item (ID: {datasource_item.id} & connection item {connection_item.id}")
320320
return connection
321321

322+
@api(version="3.26")
323+
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]:
326+
"""
327+
Bulk updates one or more datasource connections by LUID.
328+
329+
Parameters
330+
----------
331+
datasource_item : DatasourceItem
332+
The datasource item containing the connections.
333+
334+
connection_luids : list of str
335+
The connection LUIDs to update.
336+
337+
authentication_type : str
338+
The authentication type to use (e.g., 'auth-keypair').
339+
340+
username : str, optional
341+
The username to set.
342+
343+
password : str, optional
344+
The password or secret to set.
345+
346+
embed_password : bool, optional
347+
Whether to embed the password.
348+
349+
Returns
350+
-------
351+
list of str
352+
The connection LUIDs that were updated.
353+
"""
354+
from xml.etree.ElementTree import Element, SubElement, tostring
355+
356+
url = f"{self.baseurl}/{datasource_item.id}/connections"
357+
print("Method URL:", url)
358+
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+
381+
response = self.put_request(url, request_body)
382+
383+
logger.info(
384+
f"Updated connections for datasource {datasource_item.id}: {', '.join(connection_luids)}"
385+
)
386+
return connection_luids
387+
322388
@api(version="2.8")
323389
def refresh(self, datasource_item: DatasourceItem, incremental: bool = False) -> JobItem:
324390
"""

tableauserverclient/server/endpoint/workbooks_endpoint.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,72 @@ def update_connection(self, workbook_item: WorkbookItem, connection_item: Connec
325325
logger.info(f"Updated workbook item (ID: {workbook_item.id} & connection item {connection_item.id})")
326326
return connection
327327

328+
# Update workbook_connections
329+
@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.
354+
355+
Returns
356+
-------
357+
list of str
358+
The connection LUIDs that were updated.
359+
"""
360+
from xml.etree.ElementTree import Element, SubElement, tostring
361+
362+
url = f"{self.baseurl}/{workbook_item.id}/connections"
363+
364+
ts_request = Element("tsRequest")
365+
366+
# <connectionLuids>
367+
conn_luids_elem = SubElement(ts_request, "connectionLuids")
368+
for luid in connection_luids:
369+
SubElement(conn_luids_elem, "connectionLuid").text = luid
370+
371+
# <connection>
372+
connection_elem = SubElement(ts_request, "connection")
373+
connection_elem.set("authenticationType", authentication_type)
374+
375+
if username:
376+
connection_elem.set("userName", username)
377+
378+
if password:
379+
connection_elem.set("password", password)
380+
381+
if embed_password is not None:
382+
connection_elem.set("embedPassword", str(embed_password).lower())
383+
384+
request_body = tostring(ts_request)
385+
386+
# Send request
387+
response = self.put_request(url, request_body)
388+
389+
logger.info(
390+
f"Updated connections for workbook {workbook_item.id}: {', '.join(connection_luids)}"
391+
)
392+
return connection_luids
393+
328394
# Download workbook contents with option of passing in filepath
329395
@api(version="2.0")
330396
@parameter_added_in(no_extract="2.5")
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 http://tableau.com/api/ts-api-3.26.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+
authentication="auth-keypair" />
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+
authentication="auth-keypair" />
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 http://tableau.com/api/ts-api-3.26.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+
authentication="AD Service Principal" />
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+
authentication="AD Service Principal" />
20+
</connections>
21+
</tsResponse>

0 commit comments

Comments
 (0)