Skip to content

Commit d24c77e

Browse files
authored
Merge branch 'development' into jac/release-automation
2 parents a588d18 + 6f525ff commit d24c77e

File tree

121 files changed

+11646
-8286
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

121 files changed

+11646
-8286
lines changed

.github/workflows/publish-pypi.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
fetch-depth: 0
2323
- uses: actions/setup-python@v5
2424
with:
25-
python-version: 3.9
25+
python-version: 3.13
2626
- name: Build dist files
2727
run: |
2828
python -m pip install --upgrade pip

.github/workflows/run-tests.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
fail-fast: false
1414
matrix:
1515
os: [ubuntu-latest, macos-latest, windows-latest]
16-
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
16+
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14', '3.14t']
1717

1818
runs-on: ${{ matrix.os }}
1919

@@ -38,6 +38,7 @@ jobs:
3838
uses: actions/setup-python@v5
3939
with:
4040
python-version: ${{ matrix.python-version }}
41+
allow-prereleases: ${{ matrix.allow-prereleases || false }}
4142

4243
- name: Install dependencies
4344
run: |
@@ -47,7 +48,7 @@ jobs:
4748
- name: Test with pytest
4849
if: always()
4950
run: |
50-
pytest test
51+
pytest test -n auto
5152
5253
- name: Test build
5354
if: always()

CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#ECCN:Open Source
2+
#GUSINFO:Open Source,Open Source Workflow

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ dependencies = [
1515
'defusedxml>=0.7.1', # latest as at 7/31/23
1616
'packaging>=23.1', # latest as at 7/31/23
1717
'requests>=2.32', # latest as at 7/31/23
18-
'urllib3>=2.2.2,<3',
18+
'urllib3>=2.6.0,<3',
1919
'typing_extensions>=4.0',
2020
]
2121
requires-python = ">=3.9"
@@ -32,8 +32,8 @@ classifiers = [
3232
repository = "https://github.com/tableau/server-client-python"
3333

3434
[project.optional-dependencies]
35-
test = ["black==24.8", "build", "mypy==1.4", "pytest>=7.0", "pytest-cov", "pytest-subtests",
36-
"requests-mock>=1.0,<2.0"]
35+
test = ["black==24.10", "build", "mypy==1.4", "pytest>=7.0", "pytest-cov", "pytest-subtests",
36+
"pytest-xdist", "requests-mock>=1.0,<2.0", "types-requests>=2.32.4.20250913"]
3737

3838
[tool.setuptools.packages.find]
3939
where = ["tableauserverclient", "tableauserverclient.helpers", "tableauserverclient.models", "tableauserverclient.server", "tableauserverclient.server.endpoint"]

samples/create_user.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
####
2+
# This script demonstrates how to create a user using the Tableau
3+
# Server Client.
4+
#
5+
# To run the script, you must have installed Python 3.7 or later.
6+
####
7+
8+
9+
import argparse
10+
import logging
11+
import os
12+
import sys
13+
from typing import Sequence
14+
15+
import tableauserverclient as TSC
16+
17+
18+
def parse_args(args: Sequence[str] | None) -> argparse.Namespace:
19+
"""
20+
Parse command line parameters
21+
"""
22+
if args is None:
23+
args = sys.argv[1:]
24+
parser = argparse.ArgumentParser(description="Creates a sample user group.")
25+
# Common options; please keep those in sync across all samples
26+
parser.add_argument("--server", "-s", help="server address")
27+
parser.add_argument("--site", "-S", help="site name")
28+
parser.add_argument("--token-name", "-p", help="name of the personal access token used to sign into the server")
29+
parser.add_argument("--token-value", "-v", help="value of the personal access token used to sign into the server")
30+
parser.add_argument(
31+
"--logging-level",
32+
"-l",
33+
choices=["debug", "info", "error"],
34+
default="error",
35+
help="desired logging level (set to error by default)",
36+
)
37+
# Options specific to this sample
38+
# This sample has no additional options, yet. If you add some, please add them here
39+
parser.add_argument("--role", "-r", help="Site Role for the new user", default="Unlicensed")
40+
parser.add_argument(
41+
"--user",
42+
"-u",
43+
help="Username for the new user. If using active directory, it should be in the format of SAMAccountName@FullyQualifiedDomainName",
44+
)
45+
parser.add_argument(
46+
"--email", "-e", help="Email address of the new user. If using active directory, this field is optional."
47+
)
48+
49+
return parser.parse_args(args)
50+
51+
52+
def main():
53+
args = parse_args(None)
54+
55+
# Set logging level based on user input, or error by default
56+
logging_level = getattr(logging, args.logging_level.upper())
57+
logging.basicConfig(level=logging_level)
58+
59+
tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site)
60+
server = TSC.Server(args.server, use_server_version=True, http_options={"verify": False})
61+
with server.auth.sign_in(tableau_auth):
62+
# this code shows 2 different error codes for common mistakes
63+
# 400013: Invalid site role
64+
# 409000: user already exists on site
65+
66+
user = TSC.UserItem(args.user, args.role)
67+
if args.email:
68+
user.email = args.email
69+
user = server.users.add(user)
70+
71+
72+
if __name__ == "__main__":
73+
main()
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
####
2+
# This script demonstrates how to use the metadata API to query information on a published data source
3+
#
4+
# To run the script, you must have installed Python 3.7 or later.
5+
####
6+
7+
import argparse
8+
import logging
9+
from pprint import pprint
10+
11+
import tableauserverclient as TSC
12+
13+
14+
def main():
15+
parser = argparse.ArgumentParser(description="Use the metadata API to get information on a published data source.")
16+
# Common options; please keep those in sync across all samples
17+
parser.add_argument("--server", "-s", help="server address")
18+
parser.add_argument("--site", "-S", help="site name")
19+
parser.add_argument("--token-name", "-n", help="name of the personal access token used to sign into the server")
20+
parser.add_argument("--token-value", "-v", help="value of the personal access token used to sign into the server")
21+
parser.add_argument(
22+
"--logging-level",
23+
"-l",
24+
choices=["debug", "info", "error"],
25+
default="error",
26+
help="desired logging level (set to error by default)",
27+
)
28+
# Options specific to this sample
29+
parser.add_argument(
30+
"datasource_name",
31+
nargs="?",
32+
help="The name of the published datasource. If not present, we query all data sources.",
33+
)
34+
35+
args = parser.parse_args()
36+
37+
# Set logging level based on user input, or error by default
38+
logging_level = getattr(logging, args.logging_level.upper())
39+
logging.basicConfig(level=logging_level)
40+
41+
# Sign in to server
42+
tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site)
43+
server = TSC.Server(args.server, use_server_version=True)
44+
with server.auth.sign_in(tableau_auth):
45+
# Execute the query
46+
result = server.metadata.query(
47+
"""
48+
# Query must declare that it accepts first and afterToken variables
49+
query paged($first:Int, $afterToken:String) {
50+
workbooksConnection(first: $first, after:$afterToken) {
51+
nodes {
52+
luid
53+
name
54+
projectName
55+
description
56+
}
57+
totalCount
58+
pageInfo {
59+
endCursor
60+
hasNextPage
61+
}
62+
}
63+
}
64+
""",
65+
# "first" adjusts the page size. Here we set it to 5 to demonstrate pagination.
66+
# Set it to a higher number to reduce the number of pages. Including
67+
# first and afterToken is optional, and if not included, TSC will
68+
# use its default page size of 100.
69+
variables={"first": 5, "afterToken": None},
70+
)
71+
72+
# Multiple pages are captured in result["pages"]. Each page contains
73+
# the result of one execution of the query above.
74+
for page in result["pages"]:
75+
# Display warnings/errors (if any)
76+
if page.get("errors"):
77+
print("### Errors/Warnings:")
78+
pprint(result["errors"])
79+
80+
# Print the results
81+
if result.get("data"):
82+
print("### Results:")
83+
pprint(result["data"]["workbooksConnection"]["nodes"])
84+
85+
86+
if __name__ == "__main__":
87+
main()

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

samples/update_datasource_data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def main():
7676
print("Waiting for job...")
7777
# `wait_for_job` will throw if the job isn't executed successfully
7878
job = server.jobs.wait_for_job(job)
79-
print("Job finished succesfully")
79+
print("Job finished successfully")
8080

8181

8282
if __name__ == "__main__":

0 commit comments

Comments
 (0)