Skip to content

Commit 34ac954

Browse files
authored
release v0.39 (#1737)
* feat: New APIs: Update multiple connections in a single workbook/datasource (#1638) * feat: enable toggling attribute capture for a site (#1619) * feat: support OIDC endpoints (#1630) * feat: add WebAuthoringForFlows capability to Permission class (#1642) * feat: support collections in favorites (#1647) * feat: Add UAT (unified access token) support to JWT login (#1671) * feat: Update permissions_item.py --added ExtractRefresh attribute (#1617) (#1669) * feat: make refresh consistent between endpoints (#1665) * feat: make ResourceReference hashable (#1668) Closes #1666 * feat: delete view (#1712) * feat: batch create schedule (#1714) * feat: users csv import (#1409) * feat: implement users bulk_remove * feat: enable idp_configuration_id in bulk_add * feat: support extensions api (#1672) * feat: Add support for receiving "Customized Monthly" schedule intervals (#1670) * feat: implement #816: project.get_by_id (#1736) * fix: put special fields first (#1622) Closes #1620 * fix: virtual connections username (#1628) Closes #1626 * fix: add contentType to tags batch actions (#1643) * fix: datasource owner/project missing parsing (#1700) * fix: datasource description update and publish (#1682) * fix: windows decoding error * fix: assert on warning instead of ignore * fix: add workbook and view setter for custom view (#1730) * fix: handle parameters for view filters (#1633) * fix: handle parameters for view filters Closes #1632
1 parent b597886 commit 34ac954

File tree

131 files changed

+11738
-8385
lines changed

Some content is hidden

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

131 files changed

+11738
-8385
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
tableauserverclient/_version.py export-subst
2+
tableauserverclient/bin/_version.py export-subst

.github/workflows/publish-pypi.yml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
name: Publish to PyPi
22

3-
# This will publish a package to TestPyPi (and real Pypi if run on master) with a version
4-
# number generated by versioneer from the most recent tag looking like v____
5-
# TODO: maybe move this into the package job so all release-based actions are together
3+
# This will build a package with a version set by versioneer from the most recent tag matching v____
4+
# It will publish to TestPyPi, and to real Pypi *if* run on master where head has a release tag
5+
# For a live run, this should only need to be triggered by a newly published repo release.
6+
# This can also be run manually for testing
67
on:
8+
release:
9+
types: [published]
710
workflow_dispatch:
811
push:
912
tags:
@@ -19,11 +22,11 @@ jobs:
1922
fetch-depth: 0
2023
- uses: actions/setup-python@v5
2124
with:
22-
python-version: 3.9
25+
python-version: 3.13
2326
- name: Build dist files
2427
run: |
2528
python -m pip install --upgrade pip
26-
pip install -e .[test] build
29+
python -m pip install -e .[test] build
2730
python -m build
2831
git describe --tag --dirty --always
2932

.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

MANIFEST.in

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ include CONTRIBUTORS.md
44
include LICENSE
55
include LICENSE.versioneer
66
include README.md
7-
include tableauserverclient/_version.py
8-
include versioneer.py
97
recursive-include docs *.md
108
recursive-include samples *.py
119
recursive-include samples *.txt
@@ -18,5 +16,4 @@ recursive-include test *.png
1816
recursive-include test *.py
1917
recursive-include test *.xml
2018
recursive-include test *.tde
21-
global-include *.pyi
2219
global-include *.typed

publish.sh

Lines changed: 0 additions & 11 deletions
This file was deleted.

pyproject.toml

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[build-system]
2-
requires = ["setuptools>=75.0", "versioneer[toml]==0.29", "wheel"]
2+
requires = ["setuptools>=77.0", "versioneer[toml]==0.29", "wheel"]
33
build-backend = "setuptools.build_meta"
44

55
[project]
@@ -8,14 +8,14 @@ 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 = {file = "LICENSE"}
11+
license-files = ["LICENSE"]
1212
readme = "README.md"
1313

1414
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,13 @@ 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"]
37+
38+
[tool.setuptools.packages.find]
39+
where = ["tableauserverclient"]
40+
[tool.setuptools.dynamic]
41+
version = {attr = "versioneer.get_version"}
3742

3843
[tool.black]
3944
line-length = 120
@@ -61,5 +66,5 @@ addopts = "--junitxml=./test.junit.xml"
6166
VCS = "git"
6267
style = "pep440-pre"
6368
versionfile_source = "tableauserverclient/bin/_version.py"
64-
versionfile_build = "tableauserverclient/bin/_version.py"
69+
versionfile_build = "_version.py"
6570
tag_prefix = "v"

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()

0 commit comments

Comments
 (0)