Skip to content

Commit 3f4b08a

Browse files
committed
refactor: use copy.copy + _parse_common_tags in Webhooks.update
Match the convention used by users_endpoint.update and datasources_endpoint.update so that fields set locally on a WebhookItem are preserved when the server's update response omits them. Previously the endpoint used WebhookItem.from_response(...)[0], which returned a fresh item populated only from server-supplied fields. - Adds WebhookItem._parse_common_tags matching the pattern in UserItem and WorkbookItem (name refers to XML common tags, not user-facing tags). - Adds test_update_preserves_locally_set_fields_omitted_by_server exercising the local-preservation semantics against a partial server response. Feedback from bcantoni on #1806.
1 parent 9bd222f commit 3f4b08a

3 files changed

Lines changed: 58 additions & 2 deletions

File tree

‎tableauserverclient/models/webhook_item.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,21 @@ def from_response(cls: type["WebhookItem"], resp: bytes, ns) -> list["WebhookIte
109109
all_webhooks_items.append(webhook_item)
110110
return all_webhooks_items
111111

112+
def _parse_common_tags(self, webhook_xml, ns) -> "WebhookItem":
113+
"""Merge fields from a server response into this item.
114+
115+
Used by the update endpoint (matching the convention in users_endpoint
116+
and datasources_endpoint) so that locally-set fields are preserved
117+
when the server's response omits them.
118+
"""
119+
if not isinstance(webhook_xml, ET.Element):
120+
parsed = fromstring(webhook_xml)
121+
webhook_xml = parsed.find(".//t:webhook", namespaces=ns)
122+
if webhook_xml is not None:
123+
values = self._parse_element(webhook_xml, ns)
124+
self._set_values(*values)
125+
return self
126+
112127
@staticmethod
113128
def _parse_element(webhook_xml: ET.Element, ns) -> tuple:
114129
id = webhook_xml.get("id", None)

‎tableauserverclient/server/endpoint/webhooks_endpoint.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import copy
12
import logging
23

34
from .endpoint import Endpoint, api
@@ -142,9 +143,9 @@ def update(self, webhook_item: WebhookItem) -> WebhookItem:
142143
url = f"{self.baseurl}/{webhook_item.id}"
143144
update_req = RequestFactory.Webhook.update_req(webhook_item)
144145
server_response = self.put_request(url, update_req)
145-
updated_webhook = WebhookItem.from_response(server_response.content, self.parent_srv.namespace)[0]
146146
logger.info(f"Updated webhook (ID: {webhook_item.id})")
147-
return updated_webhook
147+
updated_webhook = copy.copy(webhook_item)
148+
return updated_webhook._parse_common_tags(server_response.content, self.parent_srv.namespace)
148149

149150
@api(version="3.6")
150151
def test(self, webhook_id: str):

‎test/test_webhook.py‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,46 @@ def test_update_missing_id(server: TSC.Server) -> None:
207207
server.webhooks.update(webhook_item)
208208

209209

210+
def test_update_preserves_locally_set_fields_omitted_by_server(server: TSC.Server) -> None:
211+
"""update should preserve locally-set fields that the server response omits.
212+
213+
Matches the pattern used by users_endpoint.update and datasources_endpoint.update:
214+
fields set on the input item should survive when the server returns a partial
215+
response.
216+
"""
217+
# Server response omits is_enabled, owner, and url; only id and name are returned.
218+
partial_response = (
219+
"<?xml version='1.0' encoding='UTF-8'?>"
220+
'<tsResponse xmlns="http://tableau.com/api">'
221+
' <webhook id="webhook-id" name="webhook-name-updated">'
222+
" <webhook-source>"
223+
" <webhook-source-event-datasource-created />"
224+
" </webhook-source>"
225+
" </webhook>"
226+
"</tsResponse>"
227+
)
228+
with requests_mock.mock() as m:
229+
m.put(server.webhooks.baseurl + "/webhook-id", text=partial_response)
230+
webhook_item = WebhookItem()
231+
webhook_item._set_values(
232+
"webhook-id",
233+
"webhook-name-original",
234+
"https://local-url.example.com/hook",
235+
"datasource-created",
236+
"local-owner-luid",
237+
)
238+
webhook_item.is_enabled = False
239+
240+
updated_webhook = server.webhooks.update(webhook_item)
241+
242+
# Fields returned by the server should be updated.
243+
assert updated_webhook.name == "webhook-name-updated"
244+
# Fields omitted by the server should retain their locally-set values.
245+
assert updated_webhook.url == "https://local-url.example.com/hook"
246+
assert updated_webhook.owner_id == "local-owner-luid"
247+
assert updated_webhook.is_enabled is False
248+
249+
210250
def test_update_request_factory_is_enabled() -> None:
211251
webhook_item = WebhookItem()
212252
webhook_item._set_values(

0 commit comments

Comments
 (0)