Skip to content

Commit a1aad63

Browse files
jacalataclaude
andcommitted
fix: reject naive datetimes and document Filter.__str__ format (#1025)
Naive datetime objects passed to Filter now raise ValueError with a clear message instead of silently converting using the local system timezone, which would produce wrong UTC values on any non-UTC machine. Also adds a docstring to Filter.__str__ documenting the serialization rules for all supported value types. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 82468f3 commit a1aad63

2 files changed

Lines changed: 29 additions & 0 deletions

File tree

tableauserverclient/server/filter.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,26 @@ def __init__(self, field, operator, value):
1212
self.value = value
1313

1414
def __str__(self):
15+
"""Return the filter as a Tableau REST API filter string.
16+
17+
Format: ``<field>:<operator>:<value>``
18+
19+
Value serialization rules:
20+
- datetime: ISO-8601 UTC, e.g. ``2023-01-01T00:00:00Z``.
21+
Naive datetimes (no tzinfo) are rejected with ValueError; always
22+
pass timezone-aware datetime objects.
23+
- bool: lowercase ``true`` or ``false`` as required by the REST API.
24+
- list: bracket-enclosed comma-separated values, e.g. ``[a,b,c]``.
25+
Only valid with the ``in`` operator.
26+
- All other types: ``str()`` of the value.
27+
"""
1528
if isinstance(self._value, datetime.datetime):
29+
if self._value.tzinfo is None:
30+
raise ValueError(
31+
"Naive datetime passed to Filter; Tableau Server requires UTC. "
32+
"Use a timezone-aware datetime, e.g. "
33+
"datetime.datetime(..., tzinfo=datetime.timezone.utc)."
34+
)
1635
return f"{self.field}:{self.operator}:{format_datetime(self._value)}"
1736
if isinstance(self._value, bool):
1837
# Tableau REST API requires lowercase 'true'/'false', not Python's 'True'/'False'

test/test_filter.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ def test_filter_bool_has_extracts():
107107
assert str(filter) == "hasExtracts:eq:true"
108108

109109

110+
def test_filter_date_naive_raises():
111+
"""A naive datetime (no tzinfo) raises ValueError with a helpful message."""
112+
import pytest
113+
114+
naive_dt = datetime.datetime(2023, 1, 1, 12, 0, 0) # no tzinfo
115+
f = TSC.Filter(TSC.RequestOptions.Field.CreatedAt, TSC.RequestOptions.Operator.Equals, naive_dt)
116+
with pytest.raises(ValueError, match="Naive datetime"):
117+
str(f)
118+
119+
110120
def test_filter_list_rejects_non_in_operator():
111121
"""A list value with a non-In operator raises ValueError."""
112122
import pytest

0 commit comments

Comments
 (0)