Skip to content

Commit 99bad08

Browse files
jacalataclaude
andcommitted
fix: keep frequency on schedule update and parse hourly hours as numbers
- ScheduleItem._parse_common_tags discarded the frequency returned by _parse_element, so a schedule returned from schedules.update() had frequency None. Capture it and pass it to _set_values, as from_element does. - _parse_interval_item left hourly hours="N" as a string, so an invalid value such as 3 passed HourlyInterval validation instead of degrading to interval_item None. Convert with float(), matching the Daily branch. - Add tests for <frequencyDetails> without start and for hourly hours validation; update test_create_hourly to expect numeric intervals. Co-Authored-By: Claude Code <noreply@anthropic.com>
1 parent 5d625ff commit 99bad08

5 files changed

Lines changed: 69 additions & 3 deletions

File tree

‎tableauserverclient/models/schedule_item.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ def _parse_common_tags(self, schedule_xml, ns):
243243
_,
244244
updated_at,
245245
_,
246-
_,
246+
frequency,
247247
next_run_at,
248248
end_schedule_at,
249249
execution_order,
@@ -263,6 +263,7 @@ def _parse_common_tags(self, schedule_xml, ns):
263263
execution_order=execution_order,
264264
priority=priority,
265265
interval_item=interval_item,
266+
frequency=frequency,
266267
)
267268

268269
return self
@@ -404,7 +405,7 @@ def _parse_interval_item(parsed_response, frequency, ns):
404405
if i[0] == IntervalItem.Occurrence.Minutes:
405406
converted_intervals.append(float(i[1]) / 60)
406407
elif i[0] == IntervalItem.Occurrence.Hours:
407-
converted_intervals.append(i[1])
408+
converted_intervals.append(float(i[1]))
408409
else:
409410
converted_intervals.append(i[1])
410411

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<tsResponse
3+
xmlns="http://tableau.com/api"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-3.29.xsd">
5+
<pagination pageNumber="1" pageSize="100" totalAvailable="2" />
6+
<subscriptions>
7+
<subscription id="00000001-2222-4b6f-a2c4-1234567890ab" subject="s1" message="m" attachImage="true" attachPdf="false" suspended="false">
8+
<content id="aaaa1111-2222-3333-4444-555555555555" type="View" sendIfViewEmpty="false" />
9+
<schedule frequency="Hourly" nextRunAt="2026-08-29T16:55:00-0700">
10+
<frequencyDetails start="08:00:00" end="18:00:00"><intervals><interval hours="3"/></intervals></frequencyDetails>
11+
</schedule>
12+
<user id="bbbb1111-2222-3333-4444-555555555555" name="cloud.user@example.com" />
13+
</subscription>
14+
<subscription id="00000002-2222-4b6f-a2c4-1234567890ab" subject="s2" message="m" attachImage="true" attachPdf="false" suspended="false">
15+
<content id="aaaa1111-2222-3333-4444-555555555555" type="View" sendIfViewEmpty="false" />
16+
<schedule frequency="Hourly" nextRunAt="2026-08-29T16:55:00-0700">
17+
<frequencyDetails start="08:00:00" end="18:00:00"><intervals><interval hours="2"/></intervals></frequencyDetails>
18+
</schedule>
19+
<user id="bbbb1111-2222-3333-4444-555555555555" name="cloud.user@example.com" />
20+
</subscription>
21+
</subscriptions>
22+
</tsResponse>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<tsResponse
3+
xmlns="http://tableau.com/api"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-3.29.xsd">
5+
<pagination pageNumber="1" pageSize="100" totalAvailable="2" />
6+
<subscriptions>
7+
<subscription id="00000001-2222-4b6f-a2c4-1234567890ab" subject="s1" message="m" attachImage="true" attachPdf="false" suspended="false">
8+
<content id="aaaa1111-2222-3333-4444-555555555555" type="View" sendIfViewEmpty="false" />
9+
<schedule frequency="Daily" nextRunAt="2026-08-29T16:55:00-0700">
10+
<frequencyDetails end="16:55:00"><intervals><interval hours="24"/></intervals></frequencyDetails>
11+
</schedule>
12+
<user id="bbbb1111-2222-3333-4444-555555555555" name="cloud.user@example.com" />
13+
</subscription>
14+
</subscriptions>
15+
</tsResponse>

‎test/test_schedule.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ def test_create_hourly(server: TSC.Server) -> None:
231231
assert TSC.ScheduleItem.ExecutionOrder.Parallel == new_schedule.execution_order
232232
assert time(2, 30) == new_schedule.interval_item.start_time
233233
assert time(23) == new_schedule.interval_item.end_time # type: ignore[union-attr]
234-
assert ("8",) == new_schedule.interval_item.interval # type: ignore[union-attr]
234+
assert (8.0,) == new_schedule.interval_item.interval # type: ignore[union-attr]
235235

236236

237237
def test_create_daily(server: TSC.Server) -> None:

‎test/test_subscription.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
GET_XML_CLOUD_NO_FREQ_DETAILS = TEST_ASSET_DIR / "subscription_get_cloud_no_frequency_details.xml"
1616
GET_XML_CLOUD_EMPTY_INTERVALS = TEST_ASSET_DIR / "subscription_get_cloud_empty_intervals.xml"
1717
GET_XML_CLOUD_BAD_HOURS = TEST_ASSET_DIR / "subscription_get_cloud_bad_hours.xml"
18+
GET_XML_CLOUD_NO_START = TEST_ASSET_DIR / "subscription_get_cloud_no_start.xml"
19+
GET_XML_CLOUD_HOURLY = TEST_ASSET_DIR / "subscription_get_cloud_hourly.xml"
1820

1921

2022
@pytest.fixture(scope="function")
@@ -246,3 +248,29 @@ def test_get_subscriptions_cloud_empty_intervals(server: TSC.Server) -> None:
246248
assert "Daily" == subscription.schedule.frequency
247249
assert subscription.schedule.interval_item is not None
248250
assert () == tuple(subscription.schedule.interval_item.interval)
251+
252+
253+
def test_get_subscriptions_cloud_frequency_details_without_start(server: TSC.Server) -> None:
254+
"""``<frequencyDetails>`` with ``intervals`` but no ``start`` must not abort the page."""
255+
with requests_mock.mock() as m:
256+
m.get(server.subscriptions.baseurl, text=GET_XML_CLOUD_NO_START.read_text())
257+
all_subscriptions, _ = server.subscriptions.get()
258+
259+
assert 1 == len(all_subscriptions)
260+
schedule = all_subscriptions[0].schedule
261+
assert schedule is not None
262+
assert "Daily" == schedule.frequency
263+
# DailyInterval requires a start time; the missing one degrades to None.
264+
assert schedule.interval_item is None
265+
266+
267+
def test_get_subscriptions_cloud_hourly_hours_are_numeric(server: TSC.Server) -> None:
268+
"""Hourly ``hours="3"`` is invalid and must degrade to None; ``hours="2"`` parses."""
269+
with requests_mock.mock() as m:
270+
m.get(server.subscriptions.baseurl, text=GET_XML_CLOUD_HOURLY.read_text())
271+
all_subscriptions, _ = server.subscriptions.get()
272+
273+
invalid, valid = all_subscriptions
274+
assert invalid.schedule.interval_item is None
275+
assert valid.schedule.interval_item is not None
276+
assert 2 in valid.schedule.interval_item.interval

0 commit comments

Comments
 (0)