Skip to content

Commit 55d6eae

Browse files
jacalataclaude
andcommitted
Address review feedback on webhook update
- Make status_change_reason a getter-only property (server-set field, silent no-op on assignment) - Add CHANGELOG entry for Webhooks.update() and new fields - Align _parse_common_tags with UserItem's selective-None merge pattern - Fix stale _parse_element return-type annotation - Add -> None to test_request_factory for consistency - Document which fields Webhooks.update() serializes - Match create_req's type-validation in update_req Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 18d7afc commit 55d6eae

5 files changed

Lines changed: 39 additions & 9 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
hierarchy path (e.g. `"Marketing/Q1 Reports"`). The walk is performed level by
66
level using the REST API name filter, so a path with *n* components issues *n*
77
requests. Returns the matching `ProjectItem` or `None` if no project is found.
8+
* Added `Webhooks.update()` to modify an existing webhook's name, url, event, or
9+
enabled state. Exposed new `WebhookItem` fields: `is_enabled`,
10+
`status_change_reason` (read-only, server-set), and `event_tag` (raw wire form
11+
of the event tag).
812

913
## 0.18.0 (6 April 2022)
1014
* Switched to using defused_xml for xml attack protection

‎tableauserverclient/models/webhook_item.py‎

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def __init__(self):
5959
self._event: str | None = None
6060
self.owner_id: str | None = None
6161
self.is_enabled: bool | None = None
62-
self.status_change_reason: str | None = None
62+
self._status_change_reason: str | None = None
6363

6464
def _set_values(self, id, name, url, event, owner_id, is_enabled=None, status_change_reason=None):
6565
if id is not None:
@@ -75,12 +75,17 @@ def _set_values(self, id, name, url, event, owner_id, is_enabled=None, status_ch
7575
if is_enabled is not None:
7676
self.is_enabled = is_enabled
7777
if status_change_reason is not None:
78-
self.status_change_reason = status_change_reason
78+
self._status_change_reason = status_change_reason
7979

8080
@property
8181
def id(self) -> str | None:
8282
return self._id
8383

84+
@property
85+
def status_change_reason(self) -> str | None:
86+
"""Server-set; assignment has no effect on the wire."""
87+
return self._status_change_reason
88+
8489
@property
8590
def event(self) -> str | None:
8691
if self._event:
@@ -129,12 +134,22 @@ def _parse_common_tags(self, webhook_xml, ns) -> "WebhookItem":
129134
parsed = fromstring(webhook_xml)
130135
webhook_xml = parsed.find(".//t:webhook", namespaces=ns)
131136
if webhook_xml is not None:
132-
values = self._parse_element(webhook_xml, ns)
133-
self._set_values(*values)
137+
(
138+
_,
139+
name,
140+
url,
141+
event,
142+
_,
143+
is_enabled,
144+
status_change_reason,
145+
) = self._parse_element(webhook_xml, ns)
146+
self._set_values(None, name, url, event, None, is_enabled, status_change_reason)
134147
return self
135148

136149
@staticmethod
137-
def _parse_element(webhook_xml: ET.Element, ns) -> tuple:
150+
def _parse_element(
151+
webhook_xml: ET.Element, ns
152+
) -> tuple[str | None, str | None, str | None, str | None, str | None, bool | None, str | None]:
138153
id = webhook_xml.get("id", None)
139154
name = webhook_xml.get("name", None)
140155

@@ -143,9 +158,10 @@ def _parse_element(webhook_xml: ET.Element, ns) -> tuple:
143158
if url_tag is not None:
144159
url = url_tag.get("url", None)
145160

146-
event = webhook_xml.findall(".//t:webhook-source/*", namespaces=ns)
147-
if event is not None and len(event) > 0:
148-
event = _parse_event(event)
161+
event: str | None = None
162+
event_elements = webhook_xml.findall(".//t:webhook-source/*", namespaces=ns)
163+
if event_elements:
164+
event = _parse_event(event_elements)
149165

150166
owner_id = None
151167
owner_tag = webhook_xml.find(".//t:owner", namespaces=ns)

‎tableauserverclient/server/endpoint/webhooks_endpoint.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ def update(self, webhook_item: WebhookItem) -> WebhookItem:
125125
"""
126126
Modifies an existing webhook.
127127
128+
Fields serialized to the wire: name, url, event, is_enabled. Other
129+
fields on the passed `WebhookItem` (e.g. `status_change_reason`,
130+
`owner_id`) are not sent.
131+
128132
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm#update_webhook
129133
130134
Parameters

‎tableauserverclient/server/request_factory.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,15 +1431,21 @@ def update_req(self, xml_request: ET.Element, webhook_item: "WebhookItem") -> by
14311431

14321432
webhook = ET.SubElement(xml_request, "webhook")
14331433
if webhook_item.name is not None:
1434+
if not isinstance(webhook_item.name, str):
1435+
raise ValueError(f"Name must be a string on {webhook_item}")
14341436
webhook.attrib["name"] = webhook_item.name
14351437
if webhook_item.is_enabled is not None:
14361438
webhook.attrib["isEnabled"] = str(webhook_item.is_enabled).lower()
14371439

14381440
if webhook_item.event_tag is not None:
1441+
if not isinstance(webhook_item.event_tag, str):
1442+
raise ValueError(f"event for Webhook must be a string on {webhook_item}")
14391443
source = ET.SubElement(webhook, "webhook-source")
14401444
ET.SubElement(source, webhook_item.event_tag)
14411445

14421446
if webhook_item.url is not None:
1447+
if not isinstance(webhook_item.url, str):
1448+
raise ValueError(f"URL must be a string on {webhook_item}")
14431449
destination = ET.SubElement(webhook, "webhook-destination")
14441450
post = ET.SubElement(destination, "webhook-destination-http")
14451451
post.attrib["method"] = "POST"

‎test/test_webhook.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def test_create(server: TSC.Server) -> None:
8181
assert new_webhook.id is not None
8282

8383

84-
def test_request_factory():
84+
def test_request_factory() -> None:
8585
webhook_request_expected = CREATE_REQUEST_XML.read_text()
8686

8787
webhook_item = WebhookItem()

0 commit comments

Comments
 (0)