Skip to content

Add webhook update method with isEnabled/statusChangeReason#1806

Open
jacalata wants to merge 3 commits into
developmentfrom
jac/webhook-update
Open

Add webhook update method with isEnabled/statusChangeReason#1806
jacalata wants to merge 3 commits into
developmentfrom
jac/webhook-update

Conversation

@jacalata

@jacalata jacalata commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds Webhooks.update(webhook_item) (REST API v3.6) for modifying an existing webhook's name, event, destination URL, and enabled state
  • Adds is_enabled (bool) and status_change_reason (str) fields to WebhookItem, parsed from the isEnabled and statusChangeReason XML attributes returned by the server
  • RequestFactory.Webhook.update_req() serializes all updatable fields; is_enabled is omitted when None to support partial updates; statusChangeReason is intentionally not serialized (server-set, read-only)
  • Raises MissingRequiredFieldError if the webhook item has no ID
  • Handles both webhook-source-event-* (legacy) and webhook-event-* (newer) event name prefixes in the event setter

Closes #1135

Schema compliance

isEnabled and statusChangeReason are both defined on webhookType in ts-api_3_29.xsd. The update_req() child element structure (webhook-source, webhook-destination) matches the schema. webhook-event-* style event names are a live API extension not yet reflected in the published XSD; handling them is consistent with the pre-existing behavior in create_req().

Test plan

  • python -m pytest test/test_webhook.py -v -- 22 tests, all pass
  • mypy: no issues
  • Verify server.webhooks.update(item) returns updated WebhookItem with correct fields
  • Verify updating only is_enabled=False leaves name/url/event unchanged on server
  • Verify MissingRequiredFieldError raised when item has no ID
  • Verify isEnabled attribute absent from request XML when is_enabled is None

🤖 Generated with Claude Code

jacalata and others added 3 commits June 26, 2026 01:00
Fixes #1135

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tighten test_update_missing_id to assert MissingRequiredFieldError
specifically. Add tests for update_req serializing url and event,
omitting isEnabled when None, partial (name-only) updates, and
correct parsing of isEnabled="false" from XML.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The return-type annotation added in the branch accidentally dropped the
docstring that was on test_event_setter_none. Restore it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Coverage

Coverage Report
FileStmtsMissCoverMissing
tableauserverclient
   __init__.py50100% 
   config.py150100% 
   datetime_helpers.py2511 96%
   exponential_backoff.py200100% 
   filesys_helpers.py310100% 
   namespace.py2633 88%
tableauserverclient/bin
   __init__.py20100% 
   _version.py358212212 41%
tableauserverclient/helpers
   __init__.py10100% 
   logging.py20100% 
   strings.py3111 97%
tableauserverclient/models
   __init__.py460100% 
   collection_item.py4177 83%
   column_item.py553232 42%
   connection_credentials.py351111 69%
   connection_item.py941414 85%
   custom_view_item.py1442121 85%
   data_acceleration_report_item.py5411 98%
   data_alert_item.py15844 97%
   data_freshness_policy_item.py1551515 90%
   database_item.py2073636 83%
   datasource_item.py3001212 96%
   dqw_item.py10455 95%
   exceptions.py40100% 
   extensions_item.py13244 97%
   extract_item.py4444 91%
   favorites_item.py6988 88%
   fileupload_item.py190100% 
   flow_item.py1491010 93%
   flow_run_item.py710100% 
   group_item.py8966 93%
   groupset_item.py4977 86%
   interval_item.py1823232 82%
   job_item.py1871010 95%
   linked_tasks_item.py7911 99%
   location_item.py2922 93%
   metric_item.py1291313 90%
   oidc_item.py6333 95%
   pagination_item.py3411 97%
   permissions_item.py1111212 89%
   project_item.py2073131 85%
   property_decorators.py1001818 82%
   reference_item.py2622 92%
   revision_item.py5911 98%
   schedule_item.py20966 97%
   server_info_item.py3777 81%
   site_item.py6361313 98%
   subscription_item.py10122 98%
   table_item.py1191818 85%
   tableau_auth.py612525 59%
   tableau_types.py2711 96%
   tag_item.py150100% 
   target.py60100% 
   task_item.py5622 96%
   user_item.py3101818 94%
   view_item.py2201616 93%
   virtual_connection_item.py6488 88%
   webhook_item.py8011 99%
   workbook_item.py3621616 96%
tableauserverclient/server
   __init__.py90100% 
   exceptions.py40100% 
   filter.py2111 95%
   pager.py3311 97%
   query.py1431515 90%
   request_factory.py1351195195 86%
   request_options.py38655 99%
   server.py1882323 88%
   sort.py60100% 
tableauserverclient/server/endpoint
   __init__.py350100% 
   auth_endpoint.py771111 86%
   custom_views_endpoint.py1521212 92%
   data_acceleration_report_endpoint.py210100% 
   data_alert_endpoint.py942323 76%
   databases_endpoint.py1113030 73%
   datasources_endpoint.py3403535 90%
   default_permissions_endpoint.py4433 93%
   dqw_endpoint.py451616 64%
   endpoint.py1871818 90%
   exceptions.py7766 92%
   extensions_endpoint.py310100% 
   favorites_endpoint.py942222 77%
   fileuploads_endpoint.py510100% 
   flow_runs_endpoint.py6299 85%
   flow_task_endpoint.py2122 90%
   flows_endpoint.py2165555 75%
   groups_endpoint.py12699 93%
   groupsets_endpoint.py7277 90%
   jobs_endpoint.py6799 87%
   linked_tasks_endpoint.py370100% 
   metadata_endpoint.py881414 84%
   metrics_endpoint.py5566 89%
   oidc_endpoint.py4211 98%
   permissions_endpoint.py4433 93%
   projects_endpoint.py1572424 85%
   resource_tagger.py1273535 72%
   schedules_endpoint.py1191111 91%
   server_info_endpoint.py361010 72%
   sites_endpoint.py1302727 79%
   subscriptions_endpoint.py561414 75%
   tables_endpoint.py1103636 67%
   tasks_endpoint.py6366 90%
   users_endpoint.py18388 96%
   views_endpoint.py15099 94%
   virtual_connections_endpoint.py1131010 91%
   webhooks_endpoint.py6644 94%
   workbooks_endpoint.py3542424 93%
TOTAL12038142288% 

@bcantoni

Copy link
Copy Markdown
Contributor

Code Review

Clean, well-structured PR. Here are the issues worth addressing before merge:

Issues

Convention deviation — update uses from_response()[0] instead of copy.copy + _parse_common_tags

webhooks_endpoint.py:150 — The project's documented update pattern (used in users_endpoint.py:208, datasources_endpoint.py:353) is:

updated = copy.copy(item)
return updated._parse_common_tags(server_response.content, ns)

This preserves locally-set fields that the server's partial response omits. Using from_response()[0] works here because the server always returns a complete webhook and WebhookItem has no _parse_common_tags, but it deviates from convention. Either add _parse_common_tags to WebhookItem for consistency, or leave a short comment explaining why the simpler approach is safe.

status_change_reason is a mutable public attribute

webhook_item.py:61 — It's stored as a plain self.status_change_reason: str | None = None and can be set freely by callers. Since it's server-assigned and not serialized by update_req, a caller who sets it won't get an error but also won't get the intended behavior. Per the project's property pattern for server-assigned/read-only fields (id, created_at, etc.), this should be a getter-only property (no setter), or the docstring should explicitly say "server-set, do not write."

test_update doesn't assert the request body

test/test_webhook.py:184 — The test only validates response parsing. There's no m.last_request.text assertion confirming the correct XML was sent in the PUT body. The test_update_request_factory_* tests cover serialization independently, so coverage isn't missing, but an integration-level check tying the endpoint to its request body would be more complete.

Inconsistent -> None annotation sweep

test_webhook.py:91–120 — The PR adds -> None to exactly 4 of the existing test functions while leaving others without it. Either annotate all test functions in the file or none (the file had none before this PR).

Missing CHANGELOG entry

Per repo conventions, user-visible additions get a changelog bullet with the PR number. server.webhooks.update() is a new public method that qualifies.

Minor

  • webhook_item.pyupdate_req accesses webhook_item._event directly (intentional, since the property strips the prefix), but a brief inline comment would help readers understand why the private field is used instead of the public property.
  • webhooks_endpoint.py:131 — The docstring Returns description could match the phrasing used in other update methods for consistency.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Type 1] Add support for API method Update a Webhook

2 participants