Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
8abda4b
Fix typo in update_datasource_data.py
vitorhonna May 15, 2025
19cfec7
Merge branch 'tableau:master' into patch-2
vitorhonna May 15, 2025
5eca29c
Merge branch 'master' into patch-2
vitorhonna May 16, 2025
946d208
Merge pull request #1608 from vitorhonna/patch-2
jacalata May 22, 2025
4147c73
fix: repr for auth objects
jorwoods May 25, 2025
5e49f38
Merge pull request #1618 from jorwoods/jorwoods/pat_repr
jacalata May 28, 2025
88985fe
Updated TSC with new API's
vchavatapalli Jul 15, 2025
746b345
chore: refactor XML payload into RequestFactory
jorwoods Jul 20, 2025
75f5f4c
style: black samples
jorwoods Jul 20, 2025
8ee48d1
Merge pull request #1 from jorwoods/jorwoods/conns_req_fact
vchavatapalli Jul 21, 2025
1fb57d5
Updated token name, value
vchavatapalli Jul 21, 2025
9c2bec3
Clean up
vchavatapalli Jul 21, 2025
a40d774
Added response parsing
vchavatapalli Jul 21, 2025
b407590
Fixed issues
vchavatapalli Jul 21, 2025
d8922ed
New APIs: Update multiple connections in a single workbook/datasource…
vchavatapalli Jul 21, 2025
dc92d17
Minor fixes to request payloads
vchavatapalli Jul 22, 2025
1add816
merge with dev branch
vchavatapalli Jul 23, 2025
a75bb40
Added assertions for test cases
vchavatapalli Jul 23, 2025
9a1c675
style fix
vchavatapalli Jul 23, 2025
b90473d
Fixed the login method
vchavatapalli Jul 24, 2025
0f62138
Merge pull request #1640 from vchavatapalli/minor-fixes-on-update-con…
jacalata Jul 25, 2025
755ddec
feat: enable toggling attribute capture for a site (#1619)
jorwoods Aug 1, 2025
bca08ba
fix: put special fields first (#1622)
jorwoods Aug 1, 2025
61062dc
feat: support OIDC endpoints (#1630)
jorwoods Aug 1, 2025
e51369c
feat: SiteAuthConfiguration str and repr (#1641)
jorwoods Aug 1, 2025
a7af8b7
fix: virtual connections username (#1628)
jorwoods Aug 1, 2025
9b84601
fix: add contentType to tags batch actions (#1643)
jorwoods Aug 9, 2025
d065506
fix: add missing closing tags (#1644)
jorwoods Oct 9, 2025
e8aed24
ci: run tests against python 3.14 (#1660)
jorwoods Oct 16, 2025
a196281
chore: pytestify test_datasource_model (#1656)
jorwoods Oct 16, 2025
915f1af
chore: convert workbook tests to pytest (#1645)
jorwoods Oct 16, 2025
59eaebb
chore: convert test_auth to pytest (#1646)
jorwoods Oct 17, 2025
575a1ae
chore: make datasource typing more specific (#1649)
jorwoods Oct 17, 2025
7bd23f4
chore: embrace pytest in test_datasource (#1648)
jorwoods Oct 17, 2025
c31784a
chore: pytestify test_connection_ (#1650)
jorwoods Oct 17, 2025
4ef5b99
chore: pytestify test_custom_view (#1651)
jorwoods Oct 17, 2025
3c6e6e9
chore: pytestify test_data_freshness_policy (#1653)
jorwoods Oct 17, 2025
388d5eb
chore: pytestify test_data_acceleration_report (#1652)
jorwoods Oct 17, 2025
4becca6
chore: pytestify test_dataalert (#1654)
jorwoods Oct 17, 2025
81f80ca
chore: pytestify test_database (#1655)
jorwoods Oct 17, 2025
022e6f1
feat: add WebAuthoringForFlows capability to Permission class (#1642)
valsarajnivea Oct 17, 2025
fd187ba
feat: support collections in favorites (#1647)
jorwoods Oct 17, 2025
cba111a
Add UAT (unified access token) support to JWT login (#1671)
BereketBirbo Oct 22, 2025
857e1c8
fix: mypy issues (#1667)
jorwoods Oct 29, 2025
49ff1ae
Update permissions_item.py --added ExtractRefresh attribute (#1617) (…
jorwoods Oct 29, 2025
2cb03a8
feat: make refresh consistent between endpoints (#1665)
jorwoods Oct 29, 2025
4417beb
chore: pytestify test_endpoint (#1673)
jorwoods Oct 29, 2025
e34cfe7
feat: make ResourceReference hashable (#1668)
jorwoods Nov 10, 2025
7b9d73b
samples: metadata.paginated_query (#1663)
jorwoods Nov 10, 2025
e648cd4
sample: basic user creation (#1664)
jorwoods Nov 10, 2025
0fe4e2a
Add standard Salesforce CODEOWNERS file (#1694)
bcantoni Nov 14, 2025
0f706c6
fix: datasource owner/project missing parsing (#1700)
jorwoods Nov 20, 2025
e8890d6
fix: datasource description update and publish (#1682)
jorwoods Nov 21, 2025
39fbcac
chore: pytestify favorites (#1674)
jorwoods Dec 22, 2025
89bdc42
chore: pytestify filter (#1675)
jorwoods Dec 22, 2025
9c4322e
chore: pytestify flows (#1676)
jorwoods Dec 22, 2025
d4de5bd
chore: pytestify flowruns (#1677)
jorwoods Dec 22, 2025
82a5c50
chore: pytestify flowtask (#1678)
jorwoods Dec 22, 2025
20d738b
chore: pytestify groups (#1679)
jorwoods Dec 22, 2025
423a042
chore: pytestify models repr (#1684)
jorwoods Dec 22, 2025
5e75a72
chore: pytestify group_sets (#1685)
jorwoods Dec 22, 2025
97124be
chore: pytestify jobs (#1686)
jorwoods Dec 22, 2025
ef7b971
chore: pytestify linked_tasks (#1687)
jorwoods Dec 22, 2025
af27931
chore: pytestify metadata (#1688)
jorwoods Dec 22, 2025
27dedc4
chore: pytestify pager (#1690)
jorwoods Dec 22, 2025
455e208
chore: pytestify permission_rule (#1691)
jorwoods Dec 22, 2025
ea10537
chore: pytestify project (#1692)
jorwoods Dec 22, 2025
2882019
chore: pytestify regressions (#1693)
jorwoods Dec 22, 2025
0e652ed
chore: pytestify oidc (#1689)
jorwoods Dec 22, 2025
d61ff8f
fix: black ci errors (#1713)
jorwoods Dec 23, 2025
f3f1ddf
chore: pytestify users (#1717)
jorwoods Dec 23, 2025
4f38f0a
chore: pytestify request_option (#1695)
jorwoods Dec 23, 2025
07be840
chore: pytestify requests (#1696)
jorwoods Dec 23, 2025
bc9154b
chore: pytestify schedule (#1697)
jorwoods Dec 23, 2025
661b53d
chore: pytestify server info (#1698)
jorwoods Dec 23, 2025
4daa34e
chore: pytestify views (#1707)
jorwoods Dec 23, 2025
e2f02bc
chore: pytestify task (#1706)
jorwoods Dec 23, 2025
bb127e8
chore: pytestify auth model (#1705)
jorwoods Dec 23, 2025
ade2ca0
chore: pytestify table (#1703)
jorwoods Dec 23, 2025
fcc8c8e
chore: pytestify subscriptions (#1702)
jorwoods Dec 23, 2025
29bf79a
chore: pytestify sort (#1701)
jorwoods Dec 23, 2025
54c48b0
chore: pytestify wb_model (#1710)
jorwoods Dec 23, 2025
1021903
chore: pytestify webhooks (#1709)
jorwoods Dec 23, 2025
a4f0c8a
chore: pytestify virtual connections (#1708)
jorwoods Dec 23, 2025
108b1bc
chore: pytestify ssl_config (#1722)
jorwoods Dec 23, 2025
bc11d2e
chore: pytestify filesys helpers (#1721)
jorwoods Dec 23, 2025
912b4f1
chore: pytestify metrics (#1720)
jorwoods Dec 23, 2025
eaa1602
chore: pytestify http_requests (#1719)
jorwoods Dec 23, 2025
e51f43c
chore: pytestify test_site (#1699)
jorwoods Dec 23, 2025
b4ef4a8
chore: pytestify file uploads (#1715)
jorwoods Dec 23, 2025
fbe5a95
Update urllib (FOSSA) and black (#1723)
jacalata Dec 23, 2025
ba43c97
feat: delete view (#1712)
jorwoods Dec 23, 2025
1e116f3
feat: batch create schedule (#1714)
jorwoods Dec 23, 2025
fe97749
feat: users csv import (#1409)
jorwoods Dec 23, 2025
4d67996
Add pytest-xdist plugin to speed up tests (#1681)
bcantoni Dec 23, 2025
19aaa33
chore: pytestify sort (#1725)
jorwoods Dec 26, 2025
e6b0ba1
chore: pytestify task_request (#1726)
jorwoods Dec 26, 2025
bfc692c
chore: pytestify exponential_backoff (#1727)
jorwoods Dec 26, 2025
2461518
chore: remove unused unittest imports (#1728)
jorwoods Dec 27, 2025
5146c09
fix: add workbook and view setter for custom view (#1730)
jorwoods Jan 6, 2026
f66bd09
feat: support extensions api (#1672)
jorwoods Jan 8, 2026
24cf1ad
Add support for receiving "Customized Monthly" schedule intervals (#1…
bcantoni Jan 14, 2026
6f525ff
fix: handle parameters for view filters (#1633)
jorwoods Jan 14, 2026
d9f644d
Jac/release automation (#1613)
jacalata Jan 21, 2026
12f50c4
implement #816: project.get_by_id (#1736)
jacalata Jan 22, 2026
09809d0
Merge branch 'master' into development
jacalata Jan 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
tableauserverclient/_version.py export-subst
tableauserverclient/bin/_version.py export-subst
13 changes: 8 additions & 5 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
name: Publish to PyPi

# This will publish a package to TestPyPi (and real Pypi if run on master) with a version
# number generated by versioneer from the most recent tag looking like v____
# TODO: maybe move this into the package job so all release-based actions are together
# This will build a package with a version set by versioneer from the most recent tag matching v____
# It will publish to TestPyPi, and to real Pypi *if* run on master where head has a release tag
# For a live run, this should only need to be triggered by a newly published repo release.
# This can also be run manually for testing
on:
release:
types: [published]
workflow_dispatch:
push:
tags:
Expand All @@ -19,11 +22,11 @@ jobs:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: 3.9
python-version: 3.13
- name: Build dist files
run: |
python -m pip install --upgrade pip
pip install -e .[test] build
python -m pip install -e .[test] build
python -m build
git describe --tag --dirty --always
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14', '3.14t']

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

Expand All @@ -38,6 +38,7 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: ${{ matrix.allow-prereleases || false }}

- name: Install dependencies
run: |
Expand All @@ -47,7 +48,7 @@ jobs:
- name: Test with pytest
if: always()
run: |
pytest test
pytest test -n auto
- name: Test build
if: always()
Expand Down
2 changes: 2 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#ECCN:Open Source
#GUSINFO:Open Source,Open Source Workflow
3 changes: 0 additions & 3 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ include CONTRIBUTORS.md
include LICENSE
include LICENSE.versioneer
include README.md
include tableauserverclient/_version.py
include versioneer.py
recursive-include docs *.md
recursive-include samples *.py
recursive-include samples *.txt
Expand All @@ -18,5 +16,4 @@ recursive-include test *.png
recursive-include test *.py
recursive-include test *.xml
recursive-include test *.tde
global-include *.pyi
global-include *.typed
11 changes: 0 additions & 11 deletions publish.sh

This file was deleted.

18 changes: 12 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools>=75.0", "versioneer[toml]==0.29", "wheel"]
requires = ["setuptools>=77.0", "versioneer[toml]==0.29", "wheel"]
build-backend = "setuptools.build_meta"

[project]
Expand All @@ -8,14 +8,14 @@ name="tableauserverclient"
dynamic = ["version"]
description='A Python module for working with the Tableau Server REST API.'
authors = [{name="Tableau", email="github@tableau.com"}]
license = {file = "LICENSE"}
license-files = ["LICENSE"]
readme = "README.md"

dependencies = [
'defusedxml>=0.7.1', # latest as at 7/31/23
'packaging>=23.1', # latest as at 7/31/23
'requests>=2.32', # latest as at 7/31/23
'urllib3>=2.2.2,<3',
'urllib3>=2.6.0,<3',
'typing_extensions>=4.0',
]
requires-python = ">=3.9"
Expand All @@ -32,8 +32,14 @@ classifiers = [
repository = "https://github.com/tableau/server-client-python"

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

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

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The where parameter in [tool.setuptools.packages.find] should typically be set to the root directory (e.g., [\".\"]), not individual package paths. The current configuration may not correctly discover packages. Consider using where = [\".\"] with include to specify packages, or rely on automatic discovery.

Copilot uses AI. Check for mistakes.

[tool.setuptools.dynamic]
version = {attr = "versioneer.get_version"}

[tool.black]
line-length = 120
Expand Down Expand Up @@ -61,5 +67,5 @@ addopts = "--junitxml=./test.junit.xml"
VCS = "git"
style = "pep440-pre"
versionfile_source = "tableauserverclient/bin/_version.py"
versionfile_build = "tableauserverclient/bin/_version.py"
versionfile_build = "_version.py"
tag_prefix = "v"
73 changes: 73 additions & 0 deletions samples/create_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
####
# This script demonstrates how to create a user using the Tableau
# Server Client.
#
# To run the script, you must have installed Python 3.7 or later.
####


import argparse
import logging
import os
import sys
from typing import Sequence

import tableauserverclient as TSC


def parse_args(args: Sequence[str] | None) -> argparse.Namespace:
"""
Parse command line parameters
"""
if args is None:
args = sys.argv[1:]
parser = argparse.ArgumentParser(description="Creates a sample user group.")
# Common options; please keep those in sync across all samples
parser.add_argument("--server", "-s", help="server address")
parser.add_argument("--site", "-S", help="site name")
parser.add_argument("--token-name", "-p", help="name of the personal access token used to sign into the server")
parser.add_argument("--token-value", "-v", help="value of the personal access token used to sign into the server")
parser.add_argument(
"--logging-level",
"-l",
choices=["debug", "info", "error"],
default="error",
help="desired logging level (set to error by default)",
)
# Options specific to this sample
# This sample has no additional options, yet. If you add some, please add them here
parser.add_argument("--role", "-r", help="Site Role for the new user", default="Unlicensed")
parser.add_argument(
"--user",
"-u",
help="Username for the new user. If using active directory, it should be in the format of SAMAccountName@FullyQualifiedDomainName",
)
parser.add_argument(
"--email", "-e", help="Email address of the new user. If using active directory, this field is optional."
)

return parser.parse_args(args)


def main():
args = parse_args(None)

# Set logging level based on user input, or error by default
logging_level = getattr(logging, args.logging_level.upper())
logging.basicConfig(level=logging_level)

tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site)
server = TSC.Server(args.server, use_server_version=True, http_options={"verify": False})
with server.auth.sign_in(tableau_auth):
# this code shows 2 different error codes for common mistakes
# 400013: Invalid site role
# 409000: user already exists on site

user = TSC.UserItem(args.user, args.role)
if args.email:
user.email = args.email
user = server.users.add(user)


if __name__ == "__main__":
main()
87 changes: 87 additions & 0 deletions samples/metadata_paginated_query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
####
# This script demonstrates how to use the metadata API to query information on a published data source
#
# To run the script, you must have installed Python 3.7 or later.
####

import argparse
import logging
from pprint import pprint

import tableauserverclient as TSC


def main():
parser = argparse.ArgumentParser(description="Use the metadata API to get information on a published data source.")
# Common options; please keep those in sync across all samples
parser.add_argument("--server", "-s", help="server address")
parser.add_argument("--site", "-S", help="site name")
parser.add_argument("--token-name", "-n", help="name of the personal access token used to sign into the server")
parser.add_argument("--token-value", "-v", help="value of the personal access token used to sign into the server")
parser.add_argument(
"--logging-level",
"-l",
choices=["debug", "info", "error"],
default="error",
help="desired logging level (set to error by default)",
)
# Options specific to this sample
parser.add_argument(
"datasource_name",
nargs="?",
help="The name of the published datasource. If not present, we query all data sources.",
)

args = parser.parse_args()

# Set logging level based on user input, or error by default
logging_level = getattr(logging, args.logging_level.upper())
logging.basicConfig(level=logging_level)

# Sign in to server
tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site)
server = TSC.Server(args.server, use_server_version=True)
with server.auth.sign_in(tableau_auth):
# Execute the query
result = server.metadata.query(
"""
# Query must declare that it accepts first and afterToken variables
query paged($first:Int, $afterToken:String) {
workbooksConnection(first: $first, after:$afterToken) {
nodes {
luid
name
projectName
description
}
totalCount
pageInfo {
endCursor
hasNextPage
}
}
}
""",
# "first" adjusts the page size. Here we set it to 5 to demonstrate pagination.
# Set it to a higher number to reduce the number of pages. Including
# first and afterToken is optional, and if not included, TSC will
# use its default page size of 100.
variables={"first": 5, "afterToken": None},
)

# Multiple pages are captured in result["pages"]. Each page contains
# the result of one execution of the query above.
for page in result["pages"]:
# Display warnings/errors (if any)
if page.get("errors"):
print("### Errors/Warnings:")
pprint(result["errors"])

# Print the results
if result.get("data"):
print("### Results:")
pprint(result["data"]["workbooksConnection"]["nodes"])


if __name__ == "__main__":
main()
62 changes: 62 additions & 0 deletions samples/update_connection_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import argparse
import logging
import tableauserverclient as TSC


def main():
parser = argparse.ArgumentParser(
description="Update a single connection on a datasource or workbook to embed credentials"
)

# Common options
parser.add_argument("--server", "-s", help="Server address", required=True)
parser.add_argument("--site", "-S", help="Site name", required=True)
parser.add_argument("--token-name", "-p", help="Personal access token name", required=True)
parser.add_argument("--token-value", "-v", help="Personal access token value", required=True)
parser.add_argument(
"--logging-level",
"-l",
choices=["debug", "info", "error"],
default="error",
help="Logging level (default: error)",
)

# Resource and connection details
parser.add_argument("resource_type", choices=["workbook", "datasource"])
parser.add_argument("resource_id", help="Workbook or datasource ID")
parser.add_argument("connection_id", help="Connection ID to update")
parser.add_argument("datasource_username", help="Username to set for the connection")
parser.add_argument("datasource_password", help="Password to set for the connection")
parser.add_argument("authentication_type", help="Authentication type")

args = parser.parse_args()

# Logging setup
logging_level = getattr(logging, args.logging_level.upper())
logging.basicConfig(level=logging_level)

tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site)
server = TSC.Server(args.server, use_server_version=True)

with server.auth.sign_in(tableau_auth):
endpoint = {"workbook": server.workbooks, "datasource": server.datasources}.get(args.resource_type)

update_function = endpoint.update_connection
resource = endpoint.get_by_id(args.resource_id)
endpoint.populate_connections(resource)

connections = [conn for conn in resource.connections if conn.id == args.connection_id]
assert len(connections) == 1, f"Connection ID '{args.connection_id}' not found."

connection = connections[0]
connection.username = args.datasource_username
connection.password = args.datasource_password
connection.auth_type = args.authentication_type
connection.embed_password = True

updated_connection = update_function(resource, connection)
print(f"Updated connection: {updated_connection.__dict__}")


if __name__ == "__main__":
main()
Loading
Loading