Skip to content

Commit 9895995

Browse files
jacalataclaude
andcommitted
Address type-hint and docstring feedback on Cloud schedule parsing
- Widen SubscriptionItem.__init__.schedule_id to Optional[str]; Cloud inline <schedule> elements have no id and _parse_element passes None - Rewrite property_is_datetime docstring: parse_datetime accepts both trailing-Z and explicit-offset forms; setter now raises ValueError on unparseable strings - Correct the SubscriptionItem docstring's Cloud-vs-Server discriminator: the reliable check is (schedule is not None and schedule.id is None), not (interval_item is not None) - Cloud can omit <frequencyDetails> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 93d9c87 commit 9895995

3 files changed

Lines changed: 27 additions & 14 deletions

File tree

‎tableauserverclient/models/property_decorators.py‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,17 +120,24 @@ def validate_regex_decorator(self, value):
120120

121121

122122
def property_is_datetime(func):
123-
"""Takes the following datetime format and turns it into a datetime object:
123+
"""Coerces the incoming value into a timezone-aware ``datetime`` object.
124124
125-
2016-08-18T18:25:36Z
125+
``parse_datetime`` (used on the read side) accepts two wire formats:
126126
127-
Because we return everything with Z as the timezone, we assume everything is in UTC and create
128-
a timezone aware datetime.
127+
* trailing-``Z`` UTC, e.g. ``2016-08-18T18:25:36Z``
128+
* explicit ``%z`` offset, e.g. ``2026-09-01T10:00:00-0700`` (produced by
129+
Tableau Cloud's inlined ``<schedule nextRunAt=...>``)
129130
130-
Setter-side strictness lives here: ``parse_datetime`` is deliberately lenient
131-
on the server-response side (unparseable -> ``None``), so bad user input would
132-
otherwise silently clear the attribute. We reject it here instead so misuse
133-
surfaces at the assignment site with the offending value in the message.
131+
The returned ``datetime`` preserves the offset that was on the wire; it is
132+
always offset-aware but is only guaranteed to be UTC when the input used
133+
the trailing-``Z`` form.
134+
135+
Setter-side strictness lives here: ``parse_datetime`` is deliberately
136+
lenient on the server-response side (unparseable -> ``None``). This
137+
decorator instead raises ``ValueError`` when the input is a string that
138+
matches neither format, so bad user input surfaces at the assignment site
139+
with the offending value in the message rather than silently clearing the
140+
attribute.
134141
"""
135142

136143
@wraps(func)

‎tableauserverclient/models/subscription_item.py‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@ class SubscriptionItem:
3434
</schedule>
3535
3636
The reliable Cloud-vs-Server discriminator on a parsed ``SubscriptionItem``
37-
is ``schedule.interval_item is not None`` (Cloud inlines the interval
38-
detail; Server only sends an ``id``/``name`` reference and this field will
39-
always be ``None`` there). ``schedule.id is None`` also identifies Cloud
40-
but only in combination with ``schedule is not None`` -- see below.
37+
is ``schedule is not None and schedule.id is None``. Cloud inlines the
38+
``<schedule>`` element **without** an ``id`` attribute; Server always sends
39+
an ``id``/``name`` reference. ``schedule.interval_item`` is **not** a safe
40+
discriminator: Cloud responses may omit ``<frequencyDetails>`` entirely
41+
(leaving ``interval_item`` as ``None``), and malformed intervals are
42+
intentionally downgraded to ``None`` during parsing.
4143
4244
Attributes
4345
----------
@@ -58,7 +60,7 @@ class SubscriptionItem:
5860
child at all -- always guard before dereferencing.
5961
"""
6062

61-
def __init__(self, subject: str, schedule_id: str, user_id: str, target: "Target") -> None:
63+
def __init__(self, subject: str, schedule_id: Optional[str], user_id: str, target: "Target") -> None:
6264
self._id = None
6365
self.attach_image = True
6466
self.attach_pdf = False

‎tableauserverclient/server/request_factory.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1342,7 +1342,11 @@ def create_req(self, xml_request: ET.Element, subscription_item: "SubscriptionIt
13421342
if subscription_item.send_if_view_empty is not None:
13431343
content_element.attrib["sendIfViewEmpty"] = str(subscription_item.send_if_view_empty).lower()
13441344

1345-
# Schedule element
1345+
# Schedule element -- create_req is the Server workflow (Cloud
1346+
# subscriptions come with an inlined schedule and no id, and are not
1347+
# created via this path), so schedule_id is required here.
1348+
if subscription_item.schedule_id is None:
1349+
raise ValueError("schedule_id is required to create a subscription")
13461350
schedule_element = ET.SubElement(subscription_element, "schedule")
13471351
schedule_element.attrib["id"] = subscription_item.schedule_id
13481352

0 commit comments

Comments
 (0)