Skip to content

Commit b1809c2

Browse files
jacalataclaude
andcommitted
fix: quote tag labels containing spaces or commas in XML request
The server's TagUtil.parseTags splits unquoted labels on spaces and commas, causing tags like "Yearly Sales" to be stored as two separate tags. Wrapping labels in double quotes prevents the split; the server strips the quotes before storing. Fixes #1738 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5d9a3c5 commit b1809c2

2 files changed

Lines changed: 26 additions & 4 deletions

File tree

tableauserverclient/server/request_factory.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,7 @@ def update_req(self, schedule_item):
611611
intervals_element = ET.SubElement(frequency_element, "intervals")
612612
if hasattr(interval_item, "interval"):
613613
for interval in interval_item._interval_type_pairs():
614-
(expression, value) = interval
614+
expression, value = interval
615615
single_interval_element = ET.SubElement(intervals_element, "interval")
616616
single_interval_element.attrib[expression] = value
617617
return ET.tostring(xml_request)
@@ -921,13 +921,21 @@ def update_req(self, table_item):
921921
content_types = Iterable["ColumnItem | DatabaseItem | DatasourceItem | FlowItem | TableItem | WorkbookItem"]
922922

923923

924+
def _encode_tag_label(tag: str) -> str:
925+
# The server splits unquoted labels on spaces or commas. Wrap in double
926+
# quotes so labels containing spaces are stored as a single tag.
927+
if " " in tag or "," in tag:
928+
return f'"{tag}"'
929+
return tag
930+
931+
924932
class TagRequest:
925933
def add_req(self, tag_set):
926934
xml_request = ET.Element("tsRequest")
927935
tags_element = ET.SubElement(xml_request, "tags")
928936
for tag in tag_set:
929937
tag_element = ET.SubElement(tags_element, "tag")
930-
tag_element.attrib["label"] = tag
938+
tag_element.attrib["label"] = _encode_tag_label(tag)
931939
return ET.tostring(xml_request)
932940

933941
@_tsrequest_wrapped
@@ -936,7 +944,7 @@ def batch_create(self, element: ET.Element, tags: set[str], content: content_typ
936944
tags_element = ET.SubElement(tag_batch, "tags")
937945
for tag in tags:
938946
tag_element = ET.SubElement(tags_element, "tag")
939-
tag_element.attrib["label"] = tag
947+
tag_element.attrib["label"] = _encode_tag_label(tag)
940948
contents_element = ET.SubElement(tag_batch, "contents")
941949
for item in content:
942950
content_element = ET.SubElement(contents_element, "content")

test/test_tagging.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ def get_server() -> TSC.Server:
2121
return server
2222

2323

24-
def add_tag_xml_response_factory(tags: Iterable[str]) -> str:
24+
def add_tag_xml_response_factory(tags: Iterable[str] | str) -> str:
25+
if isinstance(tags, str):
26+
tags = [tags]
2527
root = ET.Element("tsResponse")
2628
tags_element = ET.SubElement(root, "tags")
2729
for tag in tags:
@@ -242,3 +244,15 @@ def test_tags_batch_delete(get_server) -> None:
242244
tag_result = server.tags.batch_delete(tags, content)
243245

244246
assert set(tag_result) == set(tags)
247+
248+
249+
def test_tag_with_spaces_is_quoted_in_request() -> None:
250+
"""Tags containing spaces must be quoted in the XML request to prevent server-side splitting."""
251+
from tableauserverclient.server.request_factory import RequestFactory
252+
253+
tag_set = {"Yearly Sales", "simple"}
254+
xml_bytes = RequestFactory.Tag.add_req(tag_set)
255+
root = ET.fromstring(xml_bytes)
256+
labels = {tag.get("label") for tag in root.findall(".//tag")}
257+
assert '"Yearly Sales"' in labels
258+
assert "simple" in labels

0 commit comments

Comments
 (0)