├── 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 | 5 | 6 | 7 |
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 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
12 | ABCDE
12345
23456
34567
45678
56789
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 | 7 | 8 | 9 |
10 | 11 | 12 | 13 |
14 | 15 | 16 |
17 | 18 | 19 |
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 | --------------------------------------------------------------------------------