Skip to content

Add structural Protocol types for TSC item classes#1802

Open
jacalata wants to merge 8 commits into
developmentfrom
jac/base-item-class
Open

Add structural Protocol types for TSC item classes#1802
jacalata wants to merge 8 commits into
developmentfrom
jac/base-item-class

Conversation

@jacalata

Copy link
Copy Markdown
Contributor

Summary

  • Introduces base_item.py with four runtime_checkable Protocol classes: BaseItem (id, name), OwnedItem (+ owner_id), TaggableItem (+ tags), and ContentItem (+ created_at, updated_at), forming a composable hierarchy
  • Exports all four from tableauserverclient.models with section comments separating them from the concrete model list
  • Replaces the ad-hoc Taggable protocol in resource_tagger.py with a private _TaggableWithInitial and fully annotates all _ResourceTagger method signatures
  • Renames local BaseItem type alias in default_permissions_endpoint.py to DefaultPermissionsTarget to avoid shadowing the new public Protocol
  • Narrows _initial_tags: set to set[str] in DatasourceItem and WorkbookItem for Protocol invariance compliance
  • Removes pre-existing duplicate SiteOIDCConfiguration entry from models.__all__
  • 41 new unit tests in test/test_protocols.py

Schema compliance

All five primary content types in ts-api_3_29.xsd (workbookType, dataSourceType, viewType, flowType, metricType) carry id, name, owner, tags, and createdAt/updatedAt attributes. The Protocol hierarchy matches the schema exactly. MetricItem correctly satisfies ContentItem per schema confirmation.

Test plan

  • python -m pytest test/test_protocols.py -v -- 41 tests, all pass
  • Full suite: 843 passed, 1 skipped
  • mypy: no issues in 211 source files
  • Verify from tableauserverclient.models import BaseItem, OwnedItem, TaggableItem, ContentItem works
  • Verify isinstance(TSC.WorkbookItem(...), ContentItem) returns True at runtime

🤖 Generated with Claude Code

jacalata and others added 7 commits June 25, 2026 17:31
…gs type

- Rename DefaultPermissionsEndpoint's local BaseItem alias to
  DefaultPermissionsTarget to avoid shadowing the new public Protocol
- Remove _initial_tags from TaggableItem (internal dirty-tracking detail,
  not a public contract); update ContentItem docstring to include MetricItem
- Narrow WorkbookItem._initial_tags and DatasourceItem._initial_tags
  annotations from bare set to set[str] for Protocol invariance compliance

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… __all__

Taggable in resource_tagger.py was never used as a type bound -- TaggableItem
in base_item.py now covers the public contract. Also remove runtime_checkable
import which became unused. Fix pre-existing duplicate SiteOIDCConfiguration
entry in models/__init__.__all__.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ViewItem.owner_id is not independently writable (it tracks the parent
workbook's owner), so a plain writable Protocol attribute annotation would
mislead mypy. A @Property annotation satisfies both ViewItem's read-only
property and the writable instance attributes on other item classes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add test/test_protocols.py covering isinstance() checks for BaseItem,
  OwnedItem, TaggableItem, and ContentItem against all representative item
  classes (WorkbookItem, DatasourceItem, ViewItem, FlowItem, ProjectItem,
  MetricItem, UserItem) and plain structural objects, including negative
  cases and protocol-hierarchy checks.
- Add _TaggableWithInitial private Protocol in resource_tagger.py to
  capture the _initial_tags implementation detail alongside the public
  TaggableItem surface; use it to fully annotate _ResourceTagger._add_tags,
  _delete_tag, and update_tags.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…test

Add section comments to __init__.__all__ to distinguish the new
structural protocols from the alphabetical concrete-model list.

Move the inline `import datetime` in test_protocols.py to module
level, matching the import style used throughout the test suite.

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

github-actions Bot commented Jun 26, 2026

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__.py470100% 
   base_item.py140100% 
   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.py1711 94%
   tag_item.py150100% 
   target.py60100% 
   task_item.py5622 96%
   user_item.py3101818 94%
   view_item.py2201616 93%
   virtual_connection_item.py6488 88%
   webhook_item.py6911 99%
   workbook_item.py3621616 96%
tableauserverclient/server
   __init__.py90100% 
   exceptions.py40100% 
   filter.py2111 95%
   pager.py3311 97%
   query.py1431515 90%
   request_factory.py1335195195 85%
   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.py4533 93%
   projects_endpoint.py1572424 85%
   resource_tagger.py1243434 73%
   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.py5499 83%
   workbooks_endpoint.py3542424 93%
TOTAL12002142688% 

@jorwoods

Copy link
Copy Markdown
Contributor

This is great and will make other typing easier. Should they be named differently to clearly differentiate them?

Something like _BaseItem or BaseItemProtocol

@jacalata

Copy link
Copy Markdown
Contributor Author

Hm, I think underscores would make some more helpful IDEs hide/limit them in autocomplete etc, so not that. A common approach is something like "SupportsOwner", but that doesn't quite work for "ContentItem". I think the current item works ok and is pretty standard.
But thinking about it did make me remember to absorb the half-implemented TableauItem, so good question :)

…es.py

BaseItem is renamed to TableauItem to match the existing name in
tableau_types.py and fulfill its TODO comment ("should define TableauItem
as an interface"). The Union type alias is replaced with an import of the
Protocol, removing the need to enumerate concrete types.

id and name are now declared as @Property on the Protocol so that concrete
classes with read-only property implementations (and VirtualConnectionItem
whose name: str is narrower than str | None) satisfy it under mypy's
covariant property checking.

A private _PermissibleItem Protocol is added to permissions_endpoint.py
to type the populate() method's _set_permissions call, which is an
implementation detail not appropriate for the public TableauItem Protocol.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

2 participants