From 35ab86d2940d12e5ecd981e2c07cda35dab48928 Mon Sep 17 00:00:00 2001 From: Jordan Woods <13803242+jorwoods@users.noreply.github.com> Date: Fri, 14 Nov 2025 18:42:18 -0600 Subject: [PATCH] fix: datasource owner/project missing parsing Eric Summers pointed out that when the User Visibility setting is set to "Limited," TSC fails to parse because it can't retrieve any owner information. This bug is due to an `UnboundLocalError` where the `owner` and `project` variables were not assigned in cases where the owner and project elements were not included in the XML response. This PR also includes a test for the parsing where the owner and project elements are missing and properly set to `None` on the DatasourceItem. --- tableauserverclient/models/datasource_item.py | 2 ++ test/assets/datasource_get_no_owner.xml | 16 ++++++++++++++++ test/test_datasource.py | 11 +++++++++++ 3 files changed, 29 insertions(+) create mode 100644 test/assets/datasource_get_no_owner.xml diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index 2813c370c..f03a86355 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -535,6 +535,7 @@ def _parse_element(datasource_xml: ET.Element, ns: dict) -> tuple: project_id = None project_name = None + project = None project_elem = datasource_xml.find(".//t:project", namespaces=ns) if project_elem is not None: project = ProjectItem.from_xml(project_elem, ns) @@ -542,6 +543,7 @@ def _parse_element(datasource_xml: ET.Element, ns: dict) -> tuple: project_name = project_elem.get("name", None) owner_id = None + owner = None owner_elem = datasource_xml.find(".//t:owner", namespaces=ns) if owner_elem is not None: owner = UserItem.from_xml(owner_elem, ns) diff --git a/test/assets/datasource_get_no_owner.xml b/test/assets/datasource_get_no_owner.xml new file mode 100644 index 000000000..a0149d5ee --- /dev/null +++ b/test/assets/datasource_get_no_owner.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/test/test_datasource.py b/test/test_datasource.py index e9635874d..6870d319b 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -24,6 +24,7 @@ GET_EMPTY_XML = TEST_ASSET_DIR / "datasource_get_empty.xml" GET_BY_ID_XML = TEST_ASSET_DIR / "datasource_get_by_id.xml" GET_XML_ALL_FIELDS = TEST_ASSET_DIR / "datasource_get_all_fields.xml" +GET_NO_OWNER = TEST_ASSET_DIR / "datasource_get_no_owner.xml" POPULATE_CONNECTIONS_XML = TEST_ASSET_DIR / "datasource_populate_connections.xml" POPULATE_PERMISSIONS_XML = TEST_ASSET_DIR / "datasource_populate_permissions.xml" PUBLISH_XML = TEST_ASSET_DIR / "datasource_publish.xml" @@ -850,3 +851,13 @@ def test_get_datasource_all_fields(server) -> None: assert datasources[0].owner.last_login == parse_datetime("2025-02-04T06:39:20Z") assert datasources[0].owner.name == "bob@example.com" assert datasources[0].owner.site_role == "SiteAdministratorCreator" + + +def test_get_datasource_no_owner(server: TSC.Server) -> None: + with requests_mock.mock() as m: + m.get(server.datasources.baseurl, text=GET_NO_OWNER.read_text()) + datasources, _ = server.datasources.get() + + datasource = datasources[0] + assert datasource.owner is None + assert datasource.project is None