From 73b1609b882cb9a2714187746d85cfdc589064fb Mon Sep 17 00:00:00 2001 From: Jordan Woods <13803242+jorwoods@users.noreply.github.com> Date: Fri, 8 Aug 2025 17:51:07 -0500 Subject: [PATCH] fix: add contentType to tags batch actions According to the .xsd schema file, the tags:batchCreate and tags:batchDelete need a "contentType" attribute on the "content" elements. This PR adds the missing attribute and checks in the test that the string is carried through in the request body. --- tableauserverclient/server/request_factory.py | 1 + test/test_tagging.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 1df47f670..877a18c39 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -924,6 +924,7 @@ def batch_create(self, element: ET.Element, tags: set[str], content: content_typ if item.id is None: raise ValueError(f"Item {item} must have an ID to be tagged.") content_element.attrib["id"] = item.id + content_element.attrib["contentType"] = item.__class__.__name__.replace("Item", "") return ET.tostring(element) diff --git a/test/test_tagging.py b/test/test_tagging.py index 23dffebfb..8bfc90386 100644 --- a/test/test_tagging.py +++ b/test/test_tagging.py @@ -1,6 +1,7 @@ from contextlib import ExitStack import re from collections.abc import Iterable +from typing import Optional, Protocol import uuid from xml.etree import ElementTree as ET @@ -198,9 +199,14 @@ def test_update_tags(get_server, endpoint_type, item, tags) -> None: endpoint.update_tags(item) +class HasID(Protocol): + @property + def id(self) -> Optional[str]: ... + + def test_tags_batch_add(get_server) -> None: server = get_server - content = [make_workbook(), make_view(), make_datasource(), make_table(), make_database()] + content: list[HasID] = [make_workbook(), make_view(), make_datasource(), make_table(), make_database()] tags = ["a", "b"] add_tags_xml = batch_add_tags_xml_response_factory(tags, content) with requests_mock.mock() as m: @@ -210,8 +216,16 @@ def test_tags_batch_add(get_server) -> None: text=add_tags_xml, ) tag_result = server.tags.batch_add(tags, content) + history = m.request_history assert set(tag_result) == set(tags) + assert len(history) == 1 + body = ET.fromstring(history[0].body) + id_types = {c.id: c.__class__.__name__.replace("Item", "") for c in content} + for tag in body.findall(".//content"): + content_type = tag.attrib.get("contentType", "") + content_id = tag.attrib.get("id", "") + assert content_type == id_types.get(content_id, ""), f"Content type mismatch for {content_id}" def test_tags_batch_delete(get_server) -> None: