├── test
├── __init__.py
├── request_factory
│ ├── __init__.py
│ └── test_datasource_requests.py
├── assets
│ ├── Data
│ │ ├── user_details.csv
│ │ ├── usernames.csv
│ │ └── Tableau Samples
│ │ │ └── World Indicators.tde
│ ├── SampleFlow.tfl
│ ├── SampleWB.twbx
│ ├── populate_pdf.pdf
│ ├── populate_excel.xlsx
│ ├── Sample View Image.png
│ ├── World Indicators.hyper
│ ├── World Indicators.tdsx
│ ├── RESTAPISample Image.png
│ ├── populate_powerpoint.pptx
│ ├── webhook_create_request.xml
│ ├── fileupload_initialize.xml
│ ├── user_get_empty.xml
│ ├── user_update.xml
│ ├── fileupload_append.xml
│ ├── schedule_get_empty.xml
│ ├── workbook_get_empty.xml
│ ├── datasource_get_empty.xml
│ ├── groupsets_create.xml
│ ├── server_info_25.xml
│ ├── server_info_get.xml
│ ├── group_populate_users_empty.xml
│ ├── group_add_user.xml
│ ├── workbook_add_tags.xml
│ ├── tasks_run_now_response.xml
│ ├── data_alerts_add_user.xml
│ ├── user_add.xml
│ ├── view_add_tags.xml
│ ├── datasource_add_tags.xml
│ ├── group_create_async.xml
│ ├── schedule_add_flow.xml
│ ├── auth_sign_in_error.xml
│ ├── server_info_404.xml
│ ├── workbook_publish_async.xml
│ ├── datasource_connection_update.xml
│ ├── datasource_publish_async.xml
│ ├── group_create.xml
│ ├── schedule_add_workbook.xml
│ ├── schedule_add_datasource.xml
│ ├── virtual_connection_database_connection_update.xml
│ ├── linked_tasks_run_now.xml
│ ├── group_update_async.xml
│ ├── project_create.xml
│ ├── server_info_auth_info.xml
│ ├── auth_sign_in.xml
│ ├── group_update.xml
│ ├── auth_sign_in_impersonate.xml
│ ├── group_users_added.xml
│ ├── user_get_by_id.xml
│ ├── virtual_connection_populate_connections2.xml
│ ├── project_content_permission.xml
│ ├── schedule_get_by_id.xml
│ ├── virtual_connection_populate_connections.xml
│ ├── group_populate_users.xml
│ ├── group_create_ad.xml
│ ├── schedule_add_workbook_with_warnings.xml
│ ├── favorites_add_project.xml
│ ├── workbook_refresh.xml
│ ├── datasource_refresh.xml
│ ├── workbook_populate_connections.xml
│ ├── project_update.xml
│ ├── virtual_connections_update.xml
│ ├── metadata_paged_1.json
│ ├── metadata_paged_2.json
│ ├── metadata_paged_3.json
│ ├── table_update.xml
│ ├── flow_runs_get_by_id_inprogress.xml
│ ├── flow_runs_get_by_id.xml
│ ├── flow_runs_get_by_id_failed.xml
│ ├── group_add_users.xml
│ ├── groupsets_get_by_id.xml
│ ├── groupsets_update.xml
│ ├── datasource_publish.xml
│ ├── schedule_create_daily.xml
│ ├── subscription_create.xml
│ ├── tasks_without_schedule.xml
│ ├── datasource_populate_connections.xml
│ ├── favorites_add_view.xml
│ ├── webhook_create.xml
│ ├── flow_refresh.xml
│ ├── schedule_get_extract_refresh_tasks.xml
│ ├── virtual_connections_download.xml
│ ├── virtual_connections_publish.xml
│ ├── datasource_update.xml
│ ├── metadata_query_success.json
│ ├── custom_view_update.xml
│ ├── datasource_data_update.xml
│ ├── job_get.xml
│ ├── flow_publish.xml
│ ├── odata_connection.xml
│ ├── webhook_get.xml
│ ├── workbook_populate_views.xml
│ ├── custom_view_get_id.xml
│ ├── user_get.xml
│ ├── subscription_get_by_id.xml
│ ├── workbook_update_data_freshness_policy.xml
│ ├── workbook_update_data_freshness_policy2.xml
│ ├── flow_get_by_id.xml
│ ├── flow_update.xml
│ ├── workbook_update.xml
│ ├── schedule_get_monthly_id.xml
│ ├── tasks_create_extract_task.xml
│ ├── user_add_favorite.xml
│ ├── virtual_connections_get.xml
│ ├── job_get_by_id_inprogress.xml
│ ├── schedule_get_daily_id.xml
│ ├── schedule_get_hourly_id.xml
│ ├── schedule_create_monthly.xml
│ ├── schedule_create_hourly.xml
│ ├── workbook_get_page_3.xml
│ ├── flow_populate_connections.xml
│ ├── job_get_by_id_failed.xml
│ ├── workbook_get_invalid_date.xml
│ ├── database_update.xml
│ ├── workbook_get_page_1.xml
│ ├── group_get.xml
│ ├── schedule_update.xml
│ ├── dqw_by_content_type.xml
│ ├── view_get_id.xml
│ ├── user_populate_groups.xml
│ ├── schedule_get_monthly_id_2.xml
│ ├── site_auth_configurations.xml
│ ├── project_populate_permissions.xml
│ ├── workbook_publish.xml
│ ├── group_get_all_fields.xml
│ ├── metrics_get_by_id.xml
│ ├── metrics_update.xml
│ ├── workbook_update_data_freshness_policy3.xml
│ ├── favorites_add_datasource.xml
│ ├── view_get_id_usage.xml
│ ├── workbook_revision.xml
│ ├── datasource_revision.xml
│ ├── job_get_by_id_failed_workbook.xml
│ ├── datasource_get_by_id.xml
│ ├── virtual_connections_revisions.xml
│ ├── metadata_query_error.json
│ ├── workbook_update_data_freshness_policy4.xml
│ ├── groupsets_get.xml
│ ├── tasks_with_datasource.xml
│ ├── tasks_with_workbook.xml
│ ├── workbook_populate_views_usage.xml
│ ├── data_acceleration_report.xml
│ ├── job_get_by_id_completed.xml
│ ├── user_populate_workbooks.xml
│ ├── workbook_get_page_2.xml
│ ├── job_get_by_id.xml
│ ├── project_update_virtualconnection_default_permissions.xml
│ ├── project_update_datasource_default_permissions.xml
│ ├── favorites_add_workbook.xml
│ ├── flow_runs_get.xml
│ ├── workbook_get_by_id_personal.xml
│ ├── data_alerts_get.xml
│ ├── data_alerts_update.xml
│ ├── schedule_create_weekly.xml
│ ├── view_update_permissions.xml
│ ├── project_get_all_fields.xml
│ ├── workbook_update_permissions.xml
│ ├── virtual_connection_add_permissions.xml
│ ├── workbook_update_data_freshness_policy6.xml
│ ├── view_populate_permissions.xml
│ ├── site_create.xml
│ ├── workbook_get_by_id.xml
│ ├── site_get_by_name.xml
│ ├── request_option_filter_name_in.xml
│ ├── project_get.xml
│ ├── workbook_update_data_freshness_policy5.xml
│ ├── tasks_with_interval.xml
│ ├── project_populate_virtualconnection_default_permissions.xml
│ ├── datasource_connections_update.xml
│ ├── data_alerts_get_by_id.xml
│ ├── database_populate_permissions.xml
│ ├── site_get_by_id.xml
│ ├── tasks_create_flow_task.xml
│ ├── workbook_update_connections.xml
│ ├── flow_populate_permissions.xml
│ ├── custom_view_get.xml
│ ├── view_get_usage.xml
│ ├── request_option_filter_equals.xml
│ ├── site_update.xml
│ ├── tasks_with_dataacceleration_task.xml
│ ├── datasource_populate_permissions.xml
│ ├── workbook_update_acceleration_status.xml
│ ├── user_get_all_fields.xml
│ ├── datasource_get_all_fields.xml
│ ├── view_get.xml
│ ├── datasource_get_no_owner.xml
│ ├── server_info_wrong_site.html
│ ├── workbook_populate_permissions.xml
│ ├── schedule_get.xml
│ ├── metadata_query_expected_dict.dict
│ ├── table_get.xml
│ ├── linked_tasks_get.xml
│ ├── oidc_create.xml
│ ├── oidc_get.xml
│ ├── oidc_update.xml
│ ├── workbook_get_by_id_acceleration_status.xml
│ ├── workbook_get.xml
│ ├── workbook_update_views_acceleration_status.xml
│ ├── subscription_get.xml
│ ├── metrics_get.xml
│ ├── datasource_get.xml
│ ├── project_populate_workbook_default_permissions.xml
│ ├── request_option_page_size.xml
│ └── request_option_filter_tags_in.xml
├── test_dqw.py
├── test_tableauauth_model.py
├── test_workbook_model.py
├── test_group_model.py
├── test_datasource_model.py
├── test_project_model.py
├── test_connection_.py
├── test_filter.py
└── models
│ └── _models.py
├── tableauserverclient
├── py.typed
├── helpers
│ ├── __init__.py
│ ├── logging.py
│ └── headers.py
├── models
│ ├── exceptions.py
│ ├── target.py
│ ├── tag_item.py
│ ├── fileupload_item.py
│ ├── reference_item.py
│ ├── location_item.py
│ ├── pagination_item.py
│ └── tableau_types.py
├── server
│ ├── exceptions.py
│ ├── sort.py
│ ├── endpoint
│ │ ├── flow_task_endpoint.py
│ │ └── data_acceleration_report_endpoint.py
│ └── filter.py
├── config.py
├── datetime_helpers.py
├── namespace.py
└── exponential_backoff.py
├── samples
├── name.txt
├── online_users.csv
├── smoke_test.py
└── getting_started
│ └── 1_hello_server.py
├── .gitattributes
├── CODEOWNERS
├── publish.sh
├── getcontributors.py
├── LICENSE.versioneer
├── docs
└── README.md
├── setup.py
├── MANIFEST.in
├── .github
├── ISSUE_TEMPLATE
│ └── bug_report.md
└── workflows
│ ├── slack.yml
│ ├── code-coverage.yml
│ ├── pypi-smoke-tests.yml
│ ├── publish-pypi.yml
│ ├── meta-checks.yml
│ └── run-tests.yml
├── LICENSE
└── contributing.md
/test/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tableauserverclient/py.typed:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/request_factory/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/samples/name.txt:
--------------------------------------------------------------------------------
1 | 92 08 23
2 | Book2
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | tableauserverclient/_version.py export-subst
2 |
--------------------------------------------------------------------------------
/tableauserverclient/helpers/__init__.py:
--------------------------------------------------------------------------------
1 | from .strings import *
2 |
--------------------------------------------------------------------------------
/test/assets/Data/user_details.csv:
--------------------------------------------------------------------------------
1 | username, pword, , yes, email
2 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | #ECCN:Open Source
2 | #GUSINFO:Open Source,Open Source Workflow
3 |
--------------------------------------------------------------------------------
/test/assets/SampleFlow.tfl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tableau/server-client-python/HEAD/test/assets/SampleFlow.tfl
--------------------------------------------------------------------------------
/test/assets/SampleWB.twbx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tableau/server-client-python/HEAD/test/assets/SampleWB.twbx
--------------------------------------------------------------------------------
/samples/online_users.csv:
--------------------------------------------------------------------------------
1 | ayoung@tableau.com, , , "Creator", None, Yes
2 | ahsiao@tableau.com, , , "Explorer", None, No
3 |
--------------------------------------------------------------------------------
/test/assets/populate_pdf.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tableau/server-client-python/HEAD/test/assets/populate_pdf.pdf
--------------------------------------------------------------------------------
/test/assets/populate_excel.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tableau/server-client-python/HEAD/test/assets/populate_excel.xlsx
--------------------------------------------------------------------------------
/test/assets/Sample View Image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tableau/server-client-python/HEAD/test/assets/Sample View Image.png
--------------------------------------------------------------------------------
/test/assets/World Indicators.hyper:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tableau/server-client-python/HEAD/test/assets/World Indicators.hyper
--------------------------------------------------------------------------------
/test/assets/World Indicators.tdsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tableau/server-client-python/HEAD/test/assets/World Indicators.tdsx
--------------------------------------------------------------------------------
/test/assets/RESTAPISample Image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tableau/server-client-python/HEAD/test/assets/RESTAPISample Image.png
--------------------------------------------------------------------------------
/test/assets/populate_powerpoint.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tableau/server-client-python/HEAD/test/assets/populate_powerpoint.pptx
--------------------------------------------------------------------------------
/test/assets/Data/usernames.csv:
--------------------------------------------------------------------------------
1 | valid,
2 | valid@email.com,
3 | domain/valid,
4 | domain/valid@tmail.com,
5 | va!@#$%^&*()lid,
6 | in@v@lid,
7 | in valid,
8 |
--------------------------------------------------------------------------------
/tableauserverclient/helpers/logging.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | # TODO change: this defaults to logging *everything* to stdout
4 | logger = logging.getLogger("TSC")
5 |
--------------------------------------------------------------------------------
/tableauserverclient/models/exceptions.py:
--------------------------------------------------------------------------------
1 | class UnpopulatedPropertyError(Exception):
2 | pass
3 |
4 |
5 | class UnknownGranteeTypeError(Exception):
6 | pass
7 |
--------------------------------------------------------------------------------
/test/assets/Data/Tableau Samples/World Indicators.tde:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tableau/server-client-python/HEAD/test/assets/Data/Tableau Samples/World Indicators.tde
--------------------------------------------------------------------------------
/tableauserverclient/server/exceptions.py:
--------------------------------------------------------------------------------
1 | # These errors can be thrown without even talking to Tableau Server
2 |
3 |
4 | class ServerInfoEndpointNotFoundError(Exception):
5 | pass
6 |
7 |
8 | class EndpointUnavailableError(Exception):
9 | pass
10 |
--------------------------------------------------------------------------------
/test/assets/webhook_create_request.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/publish.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # tag the release version and confirm a clean version number
4 | git tag vxxxx
5 | git describe --tag --dirty --always
6 |
7 | set -e
8 |
9 | rm -rf dist
10 | python setup.py sdist bdist_wheel
11 | twine upload dist/*
12 |
--------------------------------------------------------------------------------
/test/test_dqw.py:
--------------------------------------------------------------------------------
1 | import tableauserverclient as TSC
2 |
3 |
4 | def test_dqw_existence():
5 | dqw: TSC.DQWItem = TSC.DQWItem()
6 | dqw.message = "message"
7 | dqw.warning_type = TSC.DQWItem.WarningType.STALE
8 | dqw.active = True
9 | dqw.severe = True
10 |
--------------------------------------------------------------------------------
/getcontributors.py:
--------------------------------------------------------------------------------
1 | import json
2 | import requests
3 |
4 |
5 | logins = json.loads(
6 | requests.get("https://api.github.com/repos/tableau/server-client-python/contributors?per_page=200").text
7 | )
8 | for login in logins:
9 | print(f"* [{login["login"]}]({login["html_url"]})")
10 |
--------------------------------------------------------------------------------
/test/assets/fileupload_initialize.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/tableauserverclient/models/target.py:
--------------------------------------------------------------------------------
1 | """Target class meant to abstract mappings to other objects"""
2 |
3 |
4 | class Target:
5 | def __init__(self, id_, target_type):
6 | self.id = id_
7 | self.type = target_type
8 |
9 | def __repr__(self):
10 | return "".format(**self.__dict__)
11 |
--------------------------------------------------------------------------------
/test/assets/user_get_empty.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/assets/user_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/test/assets/fileupload_append.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/test/assets/schedule_get_empty.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/assets/workbook_get_empty.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/assets/datasource_get_empty.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/assets/groupsets_create.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/assets/server_info_25.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 10.1.0
4 | 2.5
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE.versioneer:
--------------------------------------------------------------------------------
1 | ## License
2 |
3 | To make Versioneer easier to embed, all its code is dedicated to the public
4 | domain. The `_version.py` that it creates is also in the public domain.
5 | Specifically, both are released under the Creative Commons "Public Domain
6 | Dedication" license (CC0-1.0), as described in
7 | https://creativecommons.org/publicdomain/zero/1.0/ .
8 |
--------------------------------------------------------------------------------
/test/assets/server_info_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 10.1.0
4 | 3.10
5 |
6 |
7 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | To view the documentation source for the Tableau Server Client library, find the `doc` folder in the [`gh-pages`](https://github.com/tableau/server-client-python/tree/gh-pages/docs) branch of this repo.
2 |
3 | For more info about contributing, see the [Developer Guide](https://tableau.github.io/server-client-python/docs/dev-guide#update-the-documentation) page.
4 |
--------------------------------------------------------------------------------
/test/assets/group_populate_users_empty.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/test/assets/group_add_user.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/test/assets/workbook_add_tags.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/assets/tasks_run_now_response.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/assets/data_alerts_add_user.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/test/assets/user_add.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/test/assets/view_add_tags.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/datasource_add_tags.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/group_create_async.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
7 |
--------------------------------------------------------------------------------
/test/assets/schedule_add_flow.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/auth_sign_in_error.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Signin Error
5 | Error signing in to Tableau Server
6 |
7 |
--------------------------------------------------------------------------------
/test/assets/server_info_404.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Resource Not Found
5 | Unknown resource '/2.4/serverInfo' specified in URI.
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/assets/workbook_publish_async.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/test_tableauauth_model.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import tableauserverclient as TSC
4 |
5 |
6 | class TableauAuthModelTests(unittest.TestCase):
7 | def setUp(self):
8 | self.auth = TSC.TableauAuth("user", "password", site_id="site1", user_id_to_impersonate="admin")
9 |
10 | def test_username_password_required(self):
11 | with self.assertRaises(TypeError):
12 | TSC.TableauAuth()
13 |
--------------------------------------------------------------------------------
/test/test_workbook_model.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import tableauserverclient as TSC
4 |
5 |
6 | class WorkbookModelTests(unittest.TestCase):
7 | def test_invalid_show_tabs(self):
8 | workbook = TSC.WorkbookItem("10")
9 | with self.assertRaises(ValueError):
10 | workbook.show_tabs = "Hello"
11 |
12 | with self.assertRaises(ValueError):
13 | workbook.show_tabs = None
14 |
--------------------------------------------------------------------------------
/test/assets/datasource_connection_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
--------------------------------------------------------------------------------
/test/assets/datasource_publish_async.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/assets/group_create.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
--------------------------------------------------------------------------------
/test/assets/schedule_add_workbook.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/schedule_add_datasource.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/virtual_connection_database_connection_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
--------------------------------------------------------------------------------
/test/assets/linked_tasks_run_now.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import versioneer
2 | from setuptools import setup
3 |
4 | setup(
5 | version=versioneer.get_version(),
6 | cmdclass=versioneer.get_cmdclass(),
7 | # not yet sure how to move this to pyproject.toml
8 | packages=[
9 | "tableauserverclient",
10 | "tableauserverclient.helpers",
11 | "tableauserverclient.models",
12 | "tableauserverclient.server",
13 | "tableauserverclient.server.endpoint",
14 | ],
15 | )
16 |
--------------------------------------------------------------------------------
/test/assets/group_update_async.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/test/assets/project_create.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/assets/server_info_auth_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 0.31
4 | 0.31
5 | 9.2
6 | 9.3
7 | 9.3.4
8 | hello.16.1106.2025
9 | unrestricted
10 | 2.6
11 |
12 |
13 |
--------------------------------------------------------------------------------
/test/assets/auth_sign_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/assets/group_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/assets/auth_sign_in_impersonate.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/assets/group_users_added.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/assets/user_get_by_id.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/test/assets/virtual_connection_populate_connections2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/assets/project_content_permission.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/assets/schedule_get_by_id.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/test/assets/virtual_connection_populate_connections.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/assets/group_populate_users.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/test_group_model.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import tableauserverclient as TSC
4 |
5 |
6 | class GroupModelTests(unittest.TestCase):
7 | def test_invalid_minimum_site_role(self):
8 | group = TSC.GroupItem("grp")
9 | with self.assertRaises(ValueError):
10 | group.minimum_site_role = "Captain"
11 |
12 | def test_invalid_license_mode(self):
13 | group = TSC.GroupItem("grp")
14 | with self.assertRaises(ValueError):
15 | group.license_mode = "off"
16 |
--------------------------------------------------------------------------------
/tableauserverclient/helpers/headers.py:
--------------------------------------------------------------------------------
1 | from copy import deepcopy
2 | from urllib.parse import unquote_plus
3 |
4 |
5 | def fix_filename(params):
6 | if "filename*" not in params:
7 | return params
8 |
9 | params = deepcopy(params)
10 | filename = params["filename*"]
11 | prefix = "UTF-8''"
12 | if filename.startswith(prefix):
13 | filename = filename[len(prefix) :]
14 |
15 | params["filename"] = unquote_plus(filename)
16 | del params["filename*"]
17 | return params
18 |
--------------------------------------------------------------------------------
/test/assets/group_create_ad.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/schedule_add_workbook_with_warnings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/assets/favorites_add_project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/assets/workbook_refresh.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/assets/datasource_refresh.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/assets/workbook_populate_connections.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/assets/project_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/assets/virtual_connections_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/samples/smoke_test.py:
--------------------------------------------------------------------------------
1 | # This sample verifies that tableau server client is installed
2 | # and you can run it. It also shows the version of the client.
3 |
4 | import logging
5 | import tableauserverclient as TSC
6 |
7 |
8 | logger = logging.getLogger("Sample")
9 | logger.setLevel(logging.DEBUG)
10 | logger.addHandler(logging.StreamHandler())
11 |
12 |
13 | server = TSC.Server("Fake-Server-Url", use_server_version=False)
14 | print("Client details:")
15 | logger.info(server.server_address)
16 | logger.debug(TSC.server.endpoint.Endpoint.set_user_agent({}))
17 |
--------------------------------------------------------------------------------
/test/assets/metadata_paged_1.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "publishedDatasourcesConnection": {
4 | "pageInfo": {
5 | "hasNextPage": true,
6 | "endCursor": "eyJ0eXBlIjoiUHVibGlzaGVkRGF0YXNvdXJjZSIsInNjb3BlIjoic2l0ZXMvMSIsInNvcnRPcmRlclZhbHVlIjp7Imxhc3RJZCI6IjAwMzllNWQ1LTI1ZmEtMTk2Yi1jNjZlLWMwNjc1ODM5ZTBiMCJ9fQ=="
7 | },
8 | "nodes": [
9 | {
10 | "id": "0039e5d5-25fa-196b-c66e-c0675839e0b0"
11 | }
12 | ]
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/test/assets/metadata_paged_2.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "publishedDatasourcesConnection": {
4 | "pageInfo": {
5 | "hasNextPage": true,
6 | "endCursor": "eyJ0eXBlIjoiUHVibGlzaGVkRGF0YXNvdXJjZSIsInNjb3BlIjoic2l0ZXMvMSIsInNvcnRPcmRlclZhbHVlIjp7Imxhc3RJZCI6IjAwYjE5MWNlLTYwNTUtYWZmNS1lMjc1LWMyNjYxMGM4YzRkNiJ9fQ=="
7 | },
8 | "nodes": [
9 | {
10 | "id": "00b191ce-6055-aff5-e275-c26610c8c4d6"
11 | }
12 | ]
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/test/assets/metadata_paged_3.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "publishedDatasourcesConnection": {
4 | "pageInfo": {
5 | "hasNextPage": false,
6 | "endCursor": "eyJ0eXBlIjoiUHVibGlzaGVkRGF0YXNvdXJjZSIsInNjb3BlIjoic2l0ZXMvMSIsInNvcnRPcmRlclZhbHVlIjp7Imxhc3RJZCI6IjAyZjNlNGQ4LTg1NmEtZGEzNi1mNmM1LWM5MDA5NDVjNTdiOSJ9fQ=="
7 | },
8 | "nodes": [
9 | {
10 | "id": "02f3e4d8-856a-da36-f6c5-c900945c57b9"
11 | }
12 | ]
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/test/assets/table_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
--------------------------------------------------------------------------------
/test/assets/flow_runs_get_by_id_inprogress.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/flow_runs_get_by_id.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/flow_runs_get_by_id_failed.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/group_add_users.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/assets/groupsets_get_by_id.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/groupsets_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/datasource_publish.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/assets/schedule_create_daily.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/test/assets/subscription_create.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/assets/tasks_without_schedule.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/assets/datasource_populate_connections.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/assets/favorites_add_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/assets/webhook_create.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/assets/flow_refresh.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/assets/schedule_get_extract_refresh_tasks.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/test_datasource_model.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | import tableauserverclient as TSC
4 |
5 |
6 | def test_nullable_project_id():
7 | datasource = TSC.DatasourceItem(name="10")
8 | assert datasource.project_id is None
9 |
10 |
11 | def test_require_boolean_flag_bridge_fail():
12 | datasource = TSC.DatasourceItem("10")
13 | with pytest.raises(ValueError):
14 | datasource.use_remote_query_agent = "yes"
15 |
16 |
17 | def test_require_boolean_flag_bridge_ok():
18 | datasource = TSC.DatasourceItem("10")
19 | datasource.use_remote_query_agent = True
20 | assert datasource.use_remote_query_agent
21 |
--------------------------------------------------------------------------------
/test/assets/virtual_connections_download.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {"policyCollection":{"luid":"34ae5eb9-ceac-4158-86f1-a5d8163d5261","policies":[]},"revision":{"luid":"1b2e2aae-b904-4f5a-aa4d-9f114b8e5f57","revisableProperties":{}}}
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/assets/virtual_connections_publish.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {"policyCollection":{"luid":"34ae5eb9-ceac-4158-86f1-a5d8163d5261","policies":[]},"revision":{"luid":"1b2e2aae-b904-4f5a-aa4d-9f114b8e5f57","revisableProperties":{}}}
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/assets/datasource_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/assets/metadata_query_success.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "publishedDatasources": [
4 | {
5 | "id": "01cf92b2-2d17-b656-fc48-5c25ef6d5352",
6 | "name": "Batters (TestV1)"
7 | },
8 | {
9 | "id": "020ae1cd-c356-f1ad-a846-b0094850d22a",
10 | "name": "SharePoint_List_sharepoint2010.test.tsi.lan"
11 | },
12 | {
13 | "id": "061493a0-c3b2-6f39-d08c-bc3f842b44af",
14 | "name": "Batters_mongodb"
15 | },
16 | {
17 | "id": "089fe515-ad2f-89bc-94bd-69f55f69a9c2",
18 | "name": "Sample - Superstore"
19 | }
20 | ]
21 | }
22 | }
--------------------------------------------------------------------------------
/test/assets/custom_view_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/assets/datasource_data_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 7ecaccd8-39b0-4875-a77d-094f6e930019
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/job_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/flow_publish.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/assets/odata_connection.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/assets/webhook_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/assets/workbook_populate_views.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include CHANGELOG.md
2 | include contributing.md
3 | include CONTRIBUTORS.md
4 | include LICENSE
5 | include LICENSE.versioneer
6 | include README.md
7 | include tableauserverclient/_version.py
8 | include versioneer.py
9 | recursive-include docs *.md
10 | recursive-include samples *.py
11 | recursive-include samples *.txt
12 | recursive-include test *.csv
13 | recursive-include test *.dict
14 | recursive-include test *.hyper
15 | recursive-include test *.json
16 | recursive-include test *.pdf
17 | recursive-include test *.png
18 | recursive-include test *.py
19 | recursive-include test *.xml
20 | recursive-include test *.tde
21 | global-include *.pyi
22 | global-include *.typed
23 |
--------------------------------------------------------------------------------
/test/assets/custom_view_get_id.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tableauserverclient/models/tag_item.py:
--------------------------------------------------------------------------------
1 | import xml.etree.ElementTree as ET
2 |
3 | from defusedxml.ElementTree import fromstring
4 |
5 |
6 | class TagItem:
7 | @classmethod
8 | def from_response(cls, resp: bytes, ns) -> set[str]:
9 | return cls.from_xml_element(fromstring(resp), ns)
10 |
11 | @classmethod
12 | def from_xml_element(cls, parsed_response: ET.Element, ns) -> set[str]:
13 | all_tags = set()
14 | tag_elem = parsed_response.findall(".//t:tag", namespaces=ns)
15 | for tag_xml in tag_elem:
16 | tag = tag_xml.get("label", None)
17 | if tag is not None:
18 | all_tags.add(tag)
19 | return all_tags
20 |
--------------------------------------------------------------------------------
/test/assets/user_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/assets/subscription_get_by_id.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/test_project_model.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import tableauserverclient as TSC
4 |
5 |
6 | class ProjectModelTests(unittest.TestCase):
7 | def test_nullable_name(self):
8 | TSC.ProjectItem(None)
9 | TSC.ProjectItem("")
10 | project = TSC.ProjectItem("proj")
11 | project.name = None
12 |
13 | def test_invalid_content_permissions(self):
14 | project = TSC.ProjectItem("proj")
15 | with self.assertRaises(ValueError):
16 | project.content_permissions = "Hello"
17 |
18 | def test_parent_id(self):
19 | project = TSC.ProjectItem("proj")
20 | project.parent_id = "foo"
21 | self.assertEqual(project.parent_id, "foo")
22 |
--------------------------------------------------------------------------------
/test/assets/workbook_update_data_freshness_policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/assets/workbook_update_data_freshness_policy2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/assets/flow_get_by_id.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/flow_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/workbook_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/assets/schedule_get_monthly_id.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/assets/tasks_create_extract_task.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/assets/user_add_favorite.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/assets/virtual_connections_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/assets/job_get_by_id_inprogress.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | c569ee62-9204-416f-843d-5ccfebc0231b
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/assets/schedule_get_daily_id.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/assets/schedule_get_hourly_id.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tableauserverclient/server/sort.py:
--------------------------------------------------------------------------------
1 | class Sort:
2 | """
3 | Used with request options (RequestOptions) where you can filter and sort on
4 | the results returned from the server.
5 |
6 | Parameters
7 | ----------
8 | field : str
9 | Sets the field to sort on. The fields are defined in the RequestOption class.
10 |
11 | direction : str
12 | The direction to sort, either ascending (Asc) or descending (Desc). The
13 | options are defined in the RequestOptions.Direction class.
14 | """
15 |
16 | def __init__(self, field, direction):
17 | self.field = field
18 | self.direction = direction
19 |
20 | def __str__(self):
21 | return f"{self.field}:{self.direction}"
22 |
--------------------------------------------------------------------------------
/test/assets/schedule_create_monthly.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/samples/getting_started/1_hello_server.py:
--------------------------------------------------------------------------------
1 | ####
2 | # Getting started Part One of Three
3 | # This script demonstrates how to use the Tableau Server Client to connect to a server
4 | # You don't need to have a site or any experience with Tableau to run it
5 | #
6 | ####
7 |
8 | import tableauserverclient as TSC
9 |
10 |
11 | def main():
12 | # This is the domain for Tableau's Developer Program
13 | server_url = "https://10ax.online.tableau.com"
14 | server = TSC.Server(server_url)
15 | print(f"Connected to {server.server_info.baseurl}")
16 | print(f"Server information: {server.server_info}")
17 | print("Sign up for a test site at https://www.tableau.com/developer")
18 |
19 |
20 | if __name__ == "__main__":
21 | main()
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a bug report or request for help
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **Versions**
14 | Details of your environment, including:
15 | - Tableau Server version (or note if using Tableau Online)
16 | - Python version
17 | - TSC library version
18 |
19 | **To Reproduce**
20 | Steps to reproduce the behavior. Please include a code snippet where possible.
21 |
22 | **Results**
23 | What are the results or error messages received?
24 |
25 | **NOTE:** Be careful not to post user names, passwords, auth tokens or any other private or sensitive information.
26 |
--------------------------------------------------------------------------------
/test/assets/schedule_create_hourly.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/workbook_get_page_3.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/request_factory/test_datasource_requests.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import tableauserverclient as TSC
3 | import tableauserverclient.server.request_factory as TSC_RF
4 | from tableauserverclient import DatasourceItem
5 |
6 |
7 | class DatasourceRequestTests(unittest.TestCase):
8 | def test_generate_xml(self):
9 | datasource_item: TSC.DatasourceItem = TSC.DatasourceItem("name")
10 | datasource_item.name = "a ds"
11 | datasource_item.description = "described"
12 | datasource_item.use_remote_query_agent = False
13 | datasource_item.ask_data_enablement = DatasourceItem.AskDataEnablement.Enabled
14 | datasource_item.project_id = "testval"
15 | TSC_RF.RequestFactory.Datasource._generate_xml(datasource_item)
16 |
--------------------------------------------------------------------------------
/test/assets/flow_populate_connections.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/assets/job_get_by_id_failed.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | c569ee62-9204-416f-843d-5ccfebc0231b
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/assets/workbook_get_invalid_date.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/assets/database_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/assets/workbook_get_page_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/assets/group_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/assets/schedule_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/assets/dqw_by_content_type.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test/assets/view_get_id.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/test/assets/user_populate_groups.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test/assets/schedule_get_monthly_id_2.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/assets/site_auth_configurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/assets/project_populate_permissions.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test/assets/workbook_publish.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/assets/group_get_all_fields.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/assets/metrics_get_by_id.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test/assets/metrics_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test/assets/workbook_update_data_freshness_policy3.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/assets/favorites_add_datasource.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/test_connection_.py:
--------------------------------------------------------------------------------
1 | import tableauserverclient as TSC
2 |
3 | import pytest
4 |
5 |
6 | def test_require_boolean_query_tag_fails() -> None:
7 | conn = TSC.ConnectionItem()
8 | conn._connection_type = "postgres"
9 | with pytest.raises(ValueError):
10 | conn.query_tagging = "no" # type: ignore[assignment]
11 |
12 |
13 | def test_set_query_tag_normal_conn() -> None:
14 | conn = TSC.ConnectionItem()
15 | conn._connection_type = "postgres"
16 | conn.query_tagging = True
17 | assert conn.query_tagging
18 |
19 |
20 | @pytest.mark.parametrize("conn_type", ["hyper", "teradata", "snowflake"])
21 | def test_ignore_query_tag(conn_type: str) -> None:
22 | conn = TSC.ConnectionItem()
23 | conn._connection_type = conn_type
24 | conn.query_tagging = True
25 | assert conn.query_tagging is None
26 |
--------------------------------------------------------------------------------
/.github/workflows/slack.yml:
--------------------------------------------------------------------------------
1 | name: 💬 Send Message to Slack 🚀
2 |
3 | on: [push, pull_request, issues]
4 |
5 | jobs:
6 | slack-notifications:
7 | continue-on-error: true
8 | runs-on: ubuntu-latest
9 | name: Sends a message to Slack when a push, a pull request or an issue is made
10 | steps:
11 | - name: Send message to Slack API
12 | continue-on-error: true
13 | uses: archive/github-actions-slack@v2.8.0
14 | id: notify
15 | with:
16 | slack-bot-user-oauth-access-token: ${{ secrets.SLACK_BOT_USER_OAUTH_ACCESS_TOKEN }}
17 | slack-channel: C019HCX84L9
18 | slack-text: Hello! Event "${{ github.event_name }}" in "${{ github.repository }}" 🤓
19 | - name: Result from "Send Message"
20 | run: echo "The result was ${{ steps.notify.outputs.slack-result }}"
21 |
--------------------------------------------------------------------------------
/tableauserverclient/config.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | ALLOWED_FILE_EXTENSIONS = ["tds", "tdsx", "tde", "hyper", "parquet"]
4 |
5 | BYTES_PER_MB = 1024 * 1024
6 |
7 | DELAY_SLEEP_SECONDS = 0.1
8 |
9 |
10 | class Config:
11 | # The maximum size of a file that can be published in a single request is 64MB
12 | @property
13 | def FILESIZE_LIMIT_MB(self):
14 | return min(int(os.getenv("TSC_FILESIZE_LIMIT_MB", 64)), 64)
15 |
16 | # For when a datasource is over 64MB, break it into 5MB(standard chunk size) chunks
17 | @property
18 | def CHUNK_SIZE_MB(self):
19 | return int(os.getenv("TSC_CHUNK_SIZE_MB", 5 * 10)) # 5MB felt too slow, upped it to 50
20 |
21 | # Default page size
22 | @property
23 | def PAGE_SIZE(self):
24 | return int(os.getenv("TSC_PAGE_SIZE", 100))
25 |
26 |
27 | config = Config()
28 |
--------------------------------------------------------------------------------
/test/assets/view_get_id_usage.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/test_filter.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 |
4 | import tableauserverclient as TSC
5 |
6 |
7 | class FilterTests(unittest.TestCase):
8 | def setUp(self):
9 | pass
10 |
11 | def test_filter_equal(self):
12 | filter = TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, "Superstore")
13 |
14 | self.assertEqual(str(filter), "name:eq:Superstore")
15 |
16 | def test_filter_in(self):
17 | # create a IN filter condition with project names that
18 | # contain spaces and "special" characters
19 | projects_to_find = ["default", "Salesforce Sales Projeśt"]
20 | filter = TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.In, projects_to_find)
21 |
22 | self.assertEqual(str(filter), "name:in:[default,Salesforce Sales Projeśt]")
23 |
--------------------------------------------------------------------------------
/test/assets/workbook_revision.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/assets/datasource_revision.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/assets/job_get_by_id_failed_workbook.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | java.lang.RuntimeException: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Login failed for user.\nIntegrated authentication failed.
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/datasource_get_by_id.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/test/assets/virtual_connections_revisions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tableauserverclient/models/fileupload_item.py:
--------------------------------------------------------------------------------
1 | from defusedxml.ElementTree import fromstring
2 |
3 |
4 | class FileuploadItem:
5 | def __init__(self):
6 | self._file_size = None
7 | self._upload_session_id = None
8 |
9 | @property
10 | def upload_session_id(self):
11 | return self._upload_session_id
12 |
13 | @property
14 | def file_size(self) -> int:
15 | return int(self._file_size)
16 |
17 | @classmethod
18 | def from_response(cls, resp, ns):
19 | parsed_response = fromstring(resp)
20 | fileupload_elem = parsed_response.find(".//t:fileUpload", namespaces=ns)
21 | fileupload_item = cls()
22 | fileupload_item._upload_session_id = fileupload_elem.get("uploadSessionId", None)
23 | fileupload_item._file_size = fileupload_elem.get("fileSize", None)
24 | return fileupload_item
25 |
--------------------------------------------------------------------------------
/test/assets/metadata_query_error.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "publishedDatasources": [
4 | {
5 | "id": "01cf92b2-2d17-b656-fc48-5c25ef6d5352",
6 | "name": "Batters (TestV1)"
7 | },
8 | {
9 | "id": "020ae1cd-c356-f1ad-a846-b0094850d22a",
10 | "name": "SharePoint_List_sharepoint2010.test.tsi.lan"
11 | },
12 | {
13 | "id": "061493a0-c3b2-6f39-d08c-bc3f842b44af",
14 | "name": "Batters_mongodb"
15 | },
16 | {
17 | "id": "089fe515-ad2f-89bc-94bd-69f55f69a9c2",
18 | "name": "Sample - Superstore"
19 | }
20 | ]
21 | },
22 | "errors": [
23 | {
24 | "message": "Reached time limit of PT5S for query execution.",
25 | "path": null,
26 | "extensions": null
27 | }
28 | ]
29 | }
--------------------------------------------------------------------------------
/test/assets/workbook_update_data_freshness_policy4.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/assets/groupsets_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test/assets/tasks_with_datasource.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/test/assets/tasks_with_workbook.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/test/assets/workbook_populate_views_usage.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/assets/data_acceleration_report.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
19 |
20 |
--------------------------------------------------------------------------------
/test/assets/job_get_by_id_completed.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Job detail notes
9 |
10 |
11 | More detail
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/assets/user_populate_workbooks.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/assets/workbook_get_page_2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/assets/job_get_by_id.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Job detail notes
9 |
10 |
11 | More detail
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/assets/project_update_virtualconnection_default_permissions.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/assets/project_update_datasource_default_permissions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/assets/favorites_add_workbook.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/test/assets/flow_runs_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/assets/workbook_get_by_id_personal.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/assets/data_alerts_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/assets/data_alerts_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/assets/schedule_create_weekly.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test/assets/view_update_permissions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/test/assets/project_get_all_fields.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/workbook_update_permissions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/test/assets/virtual_connection_add_permissions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/test/assets/workbook_update_data_freshness_policy6.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/assets/view_populate_permissions.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/assets/site_create.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/test/assets/workbook_get_by_id.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/assets/site_get_by_name.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/assets/request_option_filter_name_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/assets/project_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/workbook_update_data_freshness_policy5.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test/assets/tasks_with_interval.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/test/assets/project_populate_virtualconnection_default_permissions.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/assets/datasource_connections_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/test/assets/data_alerts_get_by_id.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/assets/database_populate_permissions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/test/assets/site_get_by_id.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/assets/tasks_create_flow_task.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
16 |
18 |
19 |
20 |
21 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/test/assets/workbook_update_connections.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/tableauserverclient/datetime_helpers.py:
--------------------------------------------------------------------------------
1 | import datetime
2 |
3 |
4 | ZERO = datetime.timedelta(0)
5 | HOUR = datetime.timedelta(hours=1)
6 |
7 |
8 | def timestamp():
9 | return datetime.datetime.now().strftime("%H:%M:%S")
10 |
11 |
12 | # This class is a concrete implementation of the abstract base class tzinfo
13 | # docs: https://docs.python.org/2.3/lib/datetime-tzinfo.html
14 | class UTC(datetime.tzinfo):
15 | """UTC"""
16 |
17 | def utcoffset(self, dt):
18 | return ZERO
19 |
20 | def tzname(self, dt):
21 | return "UTC"
22 |
23 | def dst(self, dt):
24 | return ZERO
25 |
26 |
27 | utc = UTC()
28 | TABLEAU_DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
29 |
30 |
31 | def parse_datetime(date):
32 | if date is None:
33 | return None
34 |
35 | try:
36 | return datetime.datetime.strptime(date, TABLEAU_DATE_FORMAT).replace(tzinfo=utc)
37 | except ValueError:
38 | return None
39 |
40 |
41 | def format_datetime(date):
42 | if date is None:
43 | return None
44 |
45 | return date.astimezone(tz=utc).strftime(TABLEAU_DATE_FORMAT)
46 |
--------------------------------------------------------------------------------
/tableauserverclient/models/reference_item.py:
--------------------------------------------------------------------------------
1 | from typing_extensions import Self
2 |
3 |
4 | class ResourceReference:
5 | def __init__(self, id_: str | None, tag_name: str) -> None:
6 | self.id = id_
7 | self.tag_name = tag_name
8 |
9 | def __str__(self) -> str:
10 | return f""
11 |
12 | __repr__ = __str__
13 |
14 | def __eq__(self, other: object) -> bool:
15 | if not hasattr(other, "id") or not hasattr(other, "tag_name"):
16 | return False
17 | return (self.id == other.id) and (self.tag_name == other.tag_name)
18 |
19 | def __hash__(self: Self) -> int:
20 | return hash((self.id, self.tag_name))
21 |
22 | @property
23 | def id(self) -> str | None:
24 | return self._id
25 |
26 | @id.setter
27 | def id(self, value: str | None) -> None:
28 | self._id = value
29 |
30 | @property
31 | def tag_name(self) -> str:
32 | return self._tag_name
33 |
34 | @tag_name.setter
35 | def tag_name(self, value: str) -> None:
36 | self._tag_name = value
37 |
--------------------------------------------------------------------------------
/test/assets/flow_populate_permissions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/tableauserverclient/namespace.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | from defusedxml.ElementTree import fromstring
4 |
5 | OLD_NAMESPACE = "http://tableausoftware.com/api"
6 | NEW_NAMESPACE = "http://tableau.com/api"
7 | NAMESPACE_RE = re.compile(r"\{(.*?)\}")
8 |
9 |
10 | class UnknownNamespaceError(Exception):
11 | pass
12 |
13 |
14 | class Namespace:
15 | def __init__(self):
16 | self._namespace = {"t": NEW_NAMESPACE}
17 | self._detected = False
18 |
19 | def __call__(self):
20 | return self._namespace
21 |
22 | def detect(self, xml):
23 | if self._detected:
24 | return
25 |
26 | if not xml.startswith(b"
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2022 Tableau
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/assets/view_get_usage.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.github/workflows/code-coverage.yml:
--------------------------------------------------------------------------------
1 | name: Check Test Coverage
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - development
7 |
8 | jobs:
9 | build:
10 | strategy:
11 | fail-fast: false
12 | matrix:
13 | os: [ubuntu-latest]
14 | python-version: ['3.10']
15 |
16 | runs-on: ${{ matrix.os }}
17 |
18 | steps:
19 | - uses: actions/checkout@v4
20 |
21 | - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }}
22 | uses: actions/setup-python@v5
23 | with:
24 | python-version: ${{ matrix.python-version }}
25 |
26 | - name: Install dependencies
27 | run: |
28 | python -m pip install --upgrade pip
29 | pip install -e .[test]
30 |
31 | # https://github.com/marketplace/actions/pytest-coverage-comment
32 | - name: Generate coverage report
33 | run: pytest --junitxml=pytest.xml --cov=tableauserverclient test/ | tee pytest-coverage.txt
34 |
35 | - name: Comment on pull request with coverage
36 | continue-on-error: true
37 | uses: MishaKav/pytest-coverage-comment@main
38 | with:
39 | pytest-coverage-path: ./pytest-coverage.txt
40 |
--------------------------------------------------------------------------------
/.github/workflows/pypi-smoke-tests.yml:
--------------------------------------------------------------------------------
1 | # This workflow will install TSC from pypi and validate that it runs. For more information see:
2 | # https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3 |
4 | name: Pypi smoke tests
5 |
6 | on:
7 | workflow_dispatch:
8 | schedule:
9 | - cron: 0 11 * * * # Every day at 11AM UTC (7AM EST)
10 |
11 | permissions:
12 | contents: read
13 |
14 | jobs:
15 | build:
16 | strategy:
17 | fail-fast: false
18 | matrix:
19 | os: [ubuntu-latest, macos-latest, windows-latest]
20 | python-version: ['3.x']
21 |
22 | runs-on: ${{ matrix.os }}
23 |
24 | steps:
25 | - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }}
26 | uses: actions/setup-python@v5
27 | with:
28 | python-version: ${{ matrix.python-version }}
29 | - name: pip install
30 | run: |
31 | pip uninstall tableauserverclient
32 | pip install tableauserverclient
33 | - name: Launch app
34 | run: |
35 | python -c "import tableauserverclient as TSC
36 | server = TSC.Server('http://example.com', use_server_version=False)"
37 |
--------------------------------------------------------------------------------
/test/assets/request_option_filter_equals.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test/assets/site_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/test/assets/tasks_with_dataacceleration_task.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | 2019-12-09T20:45:04Z
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/test/assets/datasource_populate_permissions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/test/assets/workbook_update_acceleration_status.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/tableauserverclient/server/endpoint/flow_task_endpoint.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from typing import TYPE_CHECKING
3 |
4 | from tableauserverclient.server.endpoint.endpoint import Endpoint, api
5 | from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError
6 | from tableauserverclient.models import TaskItem, PaginationItem
7 | from tableauserverclient.server import RequestFactory
8 |
9 | from tableauserverclient.helpers.logging import logger
10 |
11 | if TYPE_CHECKING:
12 | from tableauserverclient.server.request_options import RequestOptions
13 |
14 |
15 | class FlowTasks(Endpoint):
16 | @property
17 | def baseurl(self) -> str:
18 | return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/tasks/flows"
19 |
20 | @api(version="3.22")
21 | def create(self, flow_item: TaskItem) -> TaskItem:
22 | if not flow_item:
23 | error = "No flow provided"
24 | raise ValueError(error)
25 | logger.info("Creating an flow task %s", flow_item)
26 | url = self.baseurl
27 | create_req = RequestFactory.FlowTask.create_flow_task_req(flow_item)
28 | server_response = self.post_request(url, create_req)
29 | return server_response.content
30 |
--------------------------------------------------------------------------------
/tableauserverclient/server/filter.py:
--------------------------------------------------------------------------------
1 | from .request_options import RequestOptions
2 |
3 |
4 | class Filter:
5 | def __init__(self, field, operator, value):
6 | self.field = field
7 | self.operator = operator
8 | self._value = None
9 | self.value = value
10 |
11 | def __str__(self):
12 | value_string = str(self._value)
13 | if isinstance(self._value, list):
14 | # this should turn the string representation of the list
15 | # from ['', '', ...]
16 | # to [,]
17 | # so effectively, remove any spaces between "," and "'" and then remove all "'"
18 | value_string = value_string.replace(", '", ",'").replace("'", "")
19 | return f"{self.field}:{self.operator}:{value_string}"
20 |
21 | @property
22 | def value(self):
23 | return self._value
24 |
25 | @value.setter
26 | def value(self, filter_value):
27 | if isinstance(filter_value, list) and self.operator != RequestOptions.Operator.In:
28 | error = "Filter values can only be a list if the operator is 'in'."
29 | raise ValueError(error)
30 | else:
31 | self._value = filter_value
32 |
--------------------------------------------------------------------------------
/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We welcome contributions to this project!
4 |
5 | Contribution can include, but are not limited to, any of the following:
6 |
7 | * File an Issue
8 | * Request a Feature
9 | * Implement a Requested Feature
10 | * Fix an Issue/Bug
11 | * Add/Fix documentation
12 |
13 | ## Issues and Feature Requests
14 |
15 | To submit an issue/bug report, or to request a feature, please submit a [GitHub issue](https://github.com/tableau/server-client-python/issues) to the repo.
16 |
17 | If you are submitting a bug report, please provide as much information as you can, including clear and concise repro steps, attaching any necessary
18 | files to assist in the repro. **Be sure to scrub the files of any potentially sensitive information. Issues are public.**
19 |
20 | For a feature request, please try to describe the scenario you are trying to accomplish that requires the feature. This will help us understand
21 | the limitations that you are running into, and provide us with a use case to know if we've satisfied your request.
22 |
23 | ### Making Contributions
24 |
25 | Refer to the [Developer Guide](https://tableau.github.io/server-client-python/docs/dev-guide) which explains how to make contributions to the TSC project.
26 |
--------------------------------------------------------------------------------
/test/assets/user_get_all_fields.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/assets/datasource_get_all_fields.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/assets/view_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/tableauserverclient/server/endpoint/data_acceleration_report_endpoint.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from .default_permissions_endpoint import _DefaultPermissionsEndpoint
4 | from .endpoint import api, Endpoint
5 | from .permissions_endpoint import _PermissionsEndpoint
6 | from tableauserverclient.models import DataAccelerationReportItem
7 |
8 | from tableauserverclient.helpers.logging import logger
9 |
10 |
11 | class DataAccelerationReport(Endpoint):
12 | def __init__(self, parent_srv):
13 | super().__init__(parent_srv)
14 |
15 | self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
16 | self._default_permissions = _DefaultPermissionsEndpoint(parent_srv, lambda: self.baseurl)
17 |
18 | @property
19 | def baseurl(self):
20 | return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/dataAccelerationReport"
21 |
22 | @api(version="3.8")
23 | def get(self, req_options=None):
24 | logger.info("Querying data acceleration report")
25 | url = self.baseurl
26 | server_response = self.get_request(url, req_options)
27 | data_acceleration_report = DataAccelerationReportItem.from_response(
28 | server_response.content, self.parent_srv.namespace
29 | )
30 | return data_acceleration_report
31 |
--------------------------------------------------------------------------------
/test/assets/datasource_get_no_owner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/assets/server_info_wrong_site.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Example website
7 |
8 |
9 |
10 |
11 | |
12 | | A |
13 | B |
14 | C |
15 | D |
16 | E |
17 |
18 |
19 | | 1 |
20 | 2 |
21 | 3 |
22 | 4 |
23 | 5 |
24 |
25 |
26 | | 2 |
27 | 3 |
28 | 4 |
29 | 5 |
30 | 6 |
31 |
32 |
33 | | 3 |
34 | 4 |
35 | 5 |
36 | 6 |
37 | 7 |
38 |
39 |
40 | | 4 |
41 | 5 |
42 | 6 |
43 | 7 |
44 | 8 |
45 |
46 |
47 | | 5 |
48 | 6 |
49 | 7 |
50 | 8 |
51 | 9 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/test/assets/workbook_populate_permissions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/test/assets/schedule_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/assets/metadata_query_expected_dict.dict:
--------------------------------------------------------------------------------
1 | {'pages': [{'data': {'publishedDatasourcesConnection': {'nodes': [{'id': '0039e5d5-25fa-196b-c66e-c0675839e0b0'}],
2 | 'pageInfo': {'endCursor': 'eyJ0eXBlIjoiUHVibGlzaGVkRGF0YXNvdXJjZSIsInNjb3BlIjoic2l0ZXMvMSIsInNvcnRPcmRlclZhbHVlIjp7Imxhc3RJZCI6IjAwMzllNWQ1LTI1ZmEtMTk2Yi1jNjZlLWMwNjc1ODM5ZTBiMCJ9fQ==',
3 | 'hasNextPage': True}}}},
4 | {'data': {'publishedDatasourcesConnection': {'nodes': [{'id': '00b191ce-6055-aff5-e275-c26610c8c4d6'}],
5 | 'pageInfo': {'endCursor': 'eyJ0eXBlIjoiUHVibGlzaGVkRGF0YXNvdXJjZSIsInNjb3BlIjoic2l0ZXMvMSIsInNvcnRPcmRlclZhbHVlIjp7Imxhc3RJZCI6IjAwYjE5MWNlLTYwNTUtYWZmNS1lMjc1LWMyNjYxMGM4YzRkNiJ9fQ==',
6 | 'hasNextPage': True}}}},
7 | {'data': {'publishedDatasourcesConnection': {'nodes': [{'id': '02f3e4d8-856a-da36-f6c5-c900945c57b9'}],
8 | 'pageInfo': {'endCursor': 'eyJ0eXBlIjoiUHVibGlzaGVkRGF0YXNvdXJjZSIsInNjb3BlIjoic2l0ZXMvMSIsInNvcnRPcmRlclZhbHVlIjp7Imxhc3RJZCI6IjAyZjNlNGQ4LTg1NmEtZGEzNi1mNmM1LWM5MDA5NDVjNTdiOSJ9fQ==',
9 | 'hasNextPage': False}}}}]}
--------------------------------------------------------------------------------
/test/assets/table_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
10 |
14 |
17 |
20 |
21 |
--------------------------------------------------------------------------------
/test/assets/linked_tasks_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
16 |
17 |
20 |
21 |
25 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/test/assets/oidc_create.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
30 |
31 |
--------------------------------------------------------------------------------
/test/assets/oidc_get.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
30 |
31 |
--------------------------------------------------------------------------------
/test/assets/oidc_update.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
30 |
31 |
--------------------------------------------------------------------------------
/test/assets/workbook_get_by_id_acceleration_status.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/test/assets/workbook_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/test/assets/workbook_update_views_acceleration_status.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/assets/subscription_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/test/assets/metrics_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
13 |
14 |
15 |
16 |
17 |
24 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/.github/workflows/publish-pypi.yml:
--------------------------------------------------------------------------------
1 | name: Publish to PyPi
2 |
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
6 | on:
7 | workflow_dispatch:
8 | push:
9 | tags:
10 | - 'v*.*.*'
11 |
12 | jobs:
13 | build-n-publish:
14 | name: Build dist files for PyPi
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v4
18 | with:
19 | fetch-depth: 0
20 | - uses: actions/setup-python@v5
21 | with:
22 | python-version: 3.13
23 | - name: Build dist files
24 | run: |
25 | python -m pip install --upgrade pip
26 | pip install -e .[test] build
27 | python -m build
28 | git describe --tag --dirty --always
29 |
30 | - name: Publish distribution 📦 to Test PyPI # always run
31 | uses: pypa/gh-action-pypi-publish@release/v1 # license BSD-2
32 | with:
33 | password: ${{ secrets.TEST_PYPI_API_TOKEN }}
34 | repository_url: https://test.pypi.org/legacy/
35 |
36 | - name: Publish distribution 📦 to PyPI
37 | if: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') }}
38 | uses: pypa/gh-action-pypi-publish@release/v1 # license BSD-2
39 | with:
40 | password: ${{ secrets.PYPI_API_TOKEN }}
41 |
--------------------------------------------------------------------------------
/tableauserverclient/models/location_item.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 | import xml.etree.ElementTree as ET
3 |
4 |
5 | class LocationItem:
6 | """
7 | Details of where an item is located, such as a personal space or project.
8 |
9 | Attributes
10 | ----------
11 | id : str | None
12 | The ID of the location.
13 |
14 | type : str | None
15 | The type of location, such as PersonalSpace or Project.
16 |
17 | name : str | None
18 | The name of the location.
19 | """
20 |
21 | class Type:
22 | PersonalSpace = "PersonalSpace"
23 | Project = "Project"
24 |
25 | def __init__(self):
26 | self._id: Optional[str] = None
27 | self._type: Optional[str] = None
28 | self._name: Optional[str] = None
29 |
30 | def __repr__(self):
31 | return f"{self.__class__.__name__}({self.__dict__!r})"
32 |
33 | @property
34 | def id(self) -> Optional[str]:
35 | return self._id
36 |
37 | @property
38 | def type(self) -> Optional[str]:
39 | return self._type
40 |
41 | @property
42 | def name(self) -> Optional[str]:
43 | return self._name
44 |
45 | @classmethod
46 | def from_xml(cls, xml: ET.Element, ns: Optional[dict] = None) -> "LocationItem":
47 | if ns is None:
48 | ns = {}
49 | location = cls()
50 | location._id = xml.get("id", None)
51 | location._type = xml.get("type", None)
52 | location._name = xml.get("name", None)
53 | return location
54 |
--------------------------------------------------------------------------------
/.github/workflows/meta-checks.yml:
--------------------------------------------------------------------------------
1 | name: types and style checks
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | strategy:
8 | fail-fast: false
9 | matrix:
10 | os: [ubuntu-latest, macos-latest, windows-latest]
11 | python-version: ['3.10']
12 |
13 | runs-on: ${{ matrix.os }}
14 |
15 | steps:
16 | - name: Get pip cache dir
17 | id: pip-cache
18 | shell: bash
19 | run: |
20 | echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
21 |
22 | - name: cache
23 | uses: actions/cache@v4
24 | with:
25 | path: ${{ steps.pip-cache.outputs.dir }}
26 | key: ${{ runner.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('pyproject.toml') }}
27 | restore-keys: |
28 | ${{ runner.os }}-${{ matrix.python-version }}-pip-
29 |
30 | - uses: actions/checkout@v4
31 |
32 | - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }}
33 | uses: actions/setup-python@v5
34 | with:
35 | python-version: ${{ matrix.python-version }}
36 |
37 | - name: Install dependencies
38 | run: |
39 | python -m pip install --upgrade pip
40 | pip install -e .[test]
41 |
42 | - name: Format with black
43 | run: |
44 | black --check --line-length 120 tableauserverclient samples test
45 |
46 | - name: Run Mypy tests
47 | if: always()
48 | run: |
49 | mypy --show-error-codes --disable-error-code misc --disable-error-code import --implicit-optional tableauserverclient test
50 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: Python tests
2 |
3 | on:
4 | pull_request: {}
5 | push:
6 | branches:
7 | - development
8 | - master
9 |
10 | jobs:
11 | build:
12 | strategy:
13 | fail-fast: false
14 | matrix:
15 | os: [ubuntu-latest, macos-latest, windows-latest]
16 | python-version: ['3.10', '3.11', '3.12', '3.13', '3.14', '3.14t']
17 |
18 | runs-on: ${{ matrix.os }}
19 |
20 | steps:
21 | - name: Get pip cache dir
22 | id: pip-cache
23 | shell: bash
24 | run: |
25 | echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
26 |
27 | - name: cache
28 | uses: actions/cache@v4
29 | with:
30 | path: ${{ steps.pip-cache.outputs.dir }}
31 | key: ${{ runner.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('pyproject.toml') }}
32 | restore-keys: |
33 | ${{ runner.os }}-${{ matrix.python-version }}-pip-
34 |
35 | - uses: actions/checkout@v4
36 |
37 | - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }}
38 | uses: actions/setup-python@v5
39 | with:
40 | python-version: ${{ matrix.python-version }}
41 | allow-prereleases: ${{ matrix.allow-prereleases || false }}
42 |
43 | - name: Install dependencies
44 | run: |
45 | python -m pip install --upgrade pip
46 | pip install -e .[test] build
47 |
48 | - name: Test with pytest
49 | if: always()
50 | run: |
51 | pytest test
52 |
53 | - name: Test build
54 | if: always()
55 | run: |
56 | python -m build
57 |
--------------------------------------------------------------------------------
/test/models/_models.py:
--------------------------------------------------------------------------------
1 | from tableauserverclient import *
2 |
3 | # TODO why aren't these available in the tsc namespace? Probably a bug.
4 | from tableauserverclient.models import (
5 | DataAccelerationReportItem,
6 | Credentials,
7 | ServerInfoItem,
8 | Resource,
9 | TableauItem,
10 | )
11 |
12 |
13 | def get_defined_models():
14 | # nothing clever here: list was manually copied from tsc/models/__init__.py
15 | return [
16 | BackgroundJobItem,
17 | ConnectionItem,
18 | DataAccelerationReportItem,
19 | DataAlertItem,
20 | DatasourceItem,
21 | FlowItem,
22 | GroupItem,
23 | JobItem,
24 | MetricItem,
25 | PermissionsRule,
26 | ProjectItem,
27 | RevisionItem,
28 | ScheduleItem,
29 | SubscriptionItem,
30 | Credentials,
31 | JWTAuth,
32 | TableauAuth,
33 | PersonalAccessTokenAuth,
34 | ServerInfoItem,
35 | SiteItem,
36 | TaskItem,
37 | UserItem,
38 | ViewItem,
39 | WebhookItem,
40 | WorkbookItem,
41 | PaginationItem,
42 | Permission.Mode,
43 | Permission.Capability,
44 | DailyInterval,
45 | WeeklyInterval,
46 | MonthlyInterval,
47 | HourlyInterval,
48 | TableItem,
49 | Target,
50 | ]
51 |
52 |
53 | def get_unimplemented_models():
54 | return [
55 | FavoriteItem, # no repr because there is no state
56 | Resource, # list of type names
57 | TableauItem, # should be an interface
58 | ]
59 |
--------------------------------------------------------------------------------
/test/assets/datasource_get.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/tableauserverclient/exponential_backoff.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | # Polling for server-side events (such as job completion) uses exponential backoff for the sleep intervals between polls
4 | ASYNC_POLL_MIN_INTERVAL = 0.5
5 | ASYNC_POLL_MAX_INTERVAL = 30
6 | ASYNC_POLL_BACKOFF_FACTOR = 1.4
7 |
8 |
9 | class ExponentialBackoffTimer:
10 | def __init__(self, *, timeout=None):
11 | self.start_time = time.time()
12 | self.timeout = timeout
13 | self.current_sleep_interval = ASYNC_POLL_MIN_INTERVAL
14 |
15 | def sleep(self):
16 | max_sleep_time = ASYNC_POLL_MAX_INTERVAL
17 | if self.timeout is not None:
18 | elapsed = time.time() - self.start_time
19 | if elapsed >= self.timeout:
20 | raise TimeoutError(f"Timeout after {elapsed} seconds waiting for asynchronous event")
21 | remaining_time = self.timeout - elapsed
22 | # Usually, we would sleep for `ASYNC_POLL_MAX_INTERVAL`, but we don't want to sleep over the timeout
23 | max_sleep_time = min(ASYNC_POLL_MAX_INTERVAL, remaining_time)
24 | # We want to sleep at least for `ASYNC_POLL_MIN_INTERVAL`. This is important to ensure that, as we get
25 | # closer to the timeout, we don't accidentally wake up multiple times and hit the server in rapid succession
26 | # due to waking up to early from the `sleep`.
27 | max_sleep_time = max(max_sleep_time, ASYNC_POLL_MIN_INTERVAL)
28 |
29 | time.sleep(min(self.current_sleep_interval, max_sleep_time))
30 | self.current_sleep_interval *= ASYNC_POLL_BACKOFF_FACTOR
31 |
--------------------------------------------------------------------------------
/test/assets/project_populate_workbook_default_permissions.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/tableauserverclient/models/pagination_item.py:
--------------------------------------------------------------------------------
1 | from defusedxml.ElementTree import fromstring
2 |
3 |
4 | class PaginationItem:
5 | def __init__(self):
6 | self._page_number = None
7 | self._page_size = None
8 | self._total_available = None
9 |
10 | def __repr__(self):
11 | return f""
12 |
13 | @property
14 | def page_number(self) -> int:
15 | return self._page_number
16 |
17 | @property
18 | def page_size(self) -> int:
19 | return self._page_size
20 |
21 | @property
22 | def total_available(self) -> int:
23 | return self._total_available
24 |
25 | @classmethod
26 | def from_response(cls, resp, ns) -> "PaginationItem":
27 | parsed_response = fromstring(resp)
28 | pagination_xml = parsed_response.find("t:pagination", namespaces=ns)
29 | pagination_item = cls()
30 | if pagination_xml is not None:
31 | pagination_item._page_number = int(pagination_xml.get("pageNumber", "-1"))
32 | pagination_item._page_size = int(pagination_xml.get("pageSize", "-1"))
33 | pagination_item._total_available = int(pagination_xml.get("totalAvailable", "-1"))
34 | return pagination_item
35 |
36 | @classmethod
37 | def from_single_page_list(cls, single_page_list) -> "PaginationItem":
38 | item = cls()
39 | item._page_number = 1
40 | item._page_size = len(single_page_list)
41 | item._total_available = len(single_page_list)
42 |
43 | return item
44 |
--------------------------------------------------------------------------------
/tableauserverclient/models/tableau_types.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 | from tableauserverclient.models.database_item import DatabaseItem
4 | from tableauserverclient.models.datasource_item import DatasourceItem
5 | from tableauserverclient.models.flow_item import FlowItem
6 | from tableauserverclient.models.project_item import ProjectItem
7 | from tableauserverclient.models.table_item import TableItem
8 | from tableauserverclient.models.view_item import ViewItem
9 | from tableauserverclient.models.workbook_item import WorkbookItem
10 | from tableauserverclient.models.metric_item import MetricItem
11 | from tableauserverclient.models.virtual_connection_item import VirtualConnectionItem
12 |
13 |
14 | class Resource:
15 | Database = "database"
16 | Datarole = "datarole"
17 | Table = "table"
18 | Datasource = "datasource"
19 | Flow = "flow"
20 | Lens = "lens"
21 | Metric = "metric"
22 | Project = "project"
23 | View = "view"
24 | VirtualConnection = "virtualConnection"
25 | Workbook = "workbook"
26 |
27 |
28 | # resource types that have permissions, can be renamed, etc
29 | # todo: refactoring: should actually define TableauItem as an interface and let all these implement it
30 | TableauItem = Union[
31 | DatasourceItem,
32 | FlowItem,
33 | MetricItem,
34 | ProjectItem,
35 | ViewItem,
36 | WorkbookItem,
37 | VirtualConnectionItem,
38 | DatabaseItem,
39 | TableItem,
40 | ]
41 |
42 |
43 | def plural_type(content_type: Union[Resource, str]) -> str:
44 | if content_type == Resource.Lens:
45 | return "lenses"
46 | else:
47 | return f"{content_type}s"
48 |
--------------------------------------------------------------------------------
/test/assets/request_option_page_size.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/test/assets/request_option_filter_tags_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------