From 6ac29e845b57c29f663f0fdabfd87f5f38ebf16b Mon Sep 17 00:00:00 2001 From: Jordan Woods <13803242+jorwoods@users.noreply.github.com> Date: Mon, 13 Oct 2025 21:30:06 -0500 Subject: [PATCH 1/5] feat: make ResourceReference hashable Closes #1666 This allows `ResourceReference` to be used as a key in dicts, as well as added to sets by making it hashable. Also adds a `to_reference` method, while leaving the the `as_reference` static method in place untouched. --- tableauserverclient/models/group_item.py | 7 ++++++- tableauserverclient/models/groupset_item.py | 7 ++++++- tableauserverclient/models/reference_item.py | 18 ++++++++++++------ tableauserverclient/models/user_item.py | 7 ++++++- tableauserverclient/server/request_factory.py | 5 ++++- 5 files changed, 34 insertions(+), 10 deletions(-) diff --git a/tableauserverclient/models/group_item.py b/tableauserverclient/models/group_item.py index 00f35e51..aa0a9895 100644 --- a/tableauserverclient/models/group_item.py +++ b/tableauserverclient/models/group_item.py @@ -1,4 +1,4 @@ -from typing import Callable, Optional, TYPE_CHECKING +from typing import Callable, Optional, Self, TYPE_CHECKING from defusedxml.ElementTree import fromstring @@ -157,3 +157,8 @@ def from_response(cls, resp, ns) -> list["GroupItem"]: @staticmethod def as_reference(id_: str) -> ResourceReference: return ResourceReference(id_, GroupItem.tag_name) + + def to_reference(self: Self) -> ResourceReference: + if self.id is None: + raise ValueError("UserItem must have id to be converted to reference") + return ResourceReference(self.id, self.tag_name) diff --git a/tableauserverclient/models/groupset_item.py b/tableauserverclient/models/groupset_item.py index aa653a79..f971131d 100644 --- a/tableauserverclient/models/groupset_item.py +++ b/tableauserverclient/models/groupset_item.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Self import xml.etree.ElementTree as ET from defusedxml.ElementTree import fromstring @@ -51,3 +51,8 @@ def get_group(group_xml: ET.Element) -> GroupItem: @staticmethod def as_reference(id_: str) -> ResourceReference: return ResourceReference(id_, GroupSetItem.tag_name) + + def to_reference(self: Self) -> ResourceReference: + if self.id is None: + raise ValueError("UserItem must have id to be converted to reference") + return ResourceReference(self.id, self.tag_name) diff --git a/tableauserverclient/models/reference_item.py b/tableauserverclient/models/reference_item.py index 4c1fff56..6e60dae1 100644 --- a/tableauserverclient/models/reference_item.py +++ b/tableauserverclient/models/reference_item.py @@ -1,9 +1,12 @@ +from typing import Self + + class ResourceReference: - def __init__(self, id_, tag_name): + def __init__(self, id_: str | None, tag_name: str) -> None: self.id = id_ self.tag_name = tag_name - def __str__(self): + def __str__(self) -> str: return f"" __repr__ = __str__ @@ -13,18 +16,21 @@ def __eq__(self, other: object) -> bool: return False return (self.id == other.id) and (self.tag_name == other.tag_name) + def __hash__(self: Self) -> int: + return hash((self.id, self.tag_name)) + @property - def id(self): + def id(self) -> str | None: return self._id @id.setter - def id(self, value): + def id(self, value: str | None) -> None: self._id = value @property - def tag_name(self): + def tag_name(self) -> str: return self._tag_name @tag_name.setter - def tag_name(self, value): + def tag_name(self, value: str) -> None: self._tag_name = value diff --git a/tableauserverclient/models/user_item.py b/tableauserverclient/models/user_item.py index 8b2dd3dd..c9b25c2c 100644 --- a/tableauserverclient/models/user_item.py +++ b/tableauserverclient/models/user_item.py @@ -2,7 +2,7 @@ import xml.etree.ElementTree as ET from datetime import datetime from enum import IntEnum -from typing import Optional, TYPE_CHECKING +from typing import Optional, TYPE_CHECKING, Self from defusedxml.ElementTree import fromstring @@ -377,6 +377,11 @@ def _parse_xml(cls, element_name, resp, ns): def as_reference(id_) -> ResourceReference: return ResourceReference(id_, UserItem.tag_name) + def to_reference(self: Self) -> ResourceReference: + if self.id is None: + raise ValueError("UserItem must have id to be converted to reference") + return ResourceReference(self.id, self.tag_name) + @staticmethod def _parse_element(user_xml, ns): id = user_xml.get("id", None) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 877a18c3..8445008d 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -512,7 +512,10 @@ def add_req(self, rules: Iterable[PermissionsRule]) -> bytes: for rule in rules: grantee_capabilities_element = ET.SubElement(permissions_element, "granteeCapabilities") grantee_element = ET.SubElement(grantee_capabilities_element, rule.grantee.tag_name) - grantee_element.attrib["id"] = rule.grantee.id + if rule.grantee.id is not None: + grantee_element.attrib["id"] = rule.grantee.id + else: + raise ValueError("Grantee must have an ID") capabilities_element = ET.SubElement(grantee_capabilities_element, "capabilities") self._add_all_capabilities(capabilities_element, rule.capabilities) From f7cf6c36b9f5a133e0208e6a3c218d0b9fb0e6da Mon Sep 17 00:00:00 2001 From: Jordan Woods <13803242+jorwoods@users.noreply.github.com> Date: Mon, 13 Oct 2025 21:34:30 -0500 Subject: [PATCH 2/5] fix: typing Self import fix for older python versions --- tableauserverclient/models/group_item.py | 3 ++- tableauserverclient/models/groupset_item.py | 3 ++- tableauserverclient/models/reference_item.py | 2 +- tableauserverclient/models/user_item.py | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tableauserverclient/models/group_item.py b/tableauserverclient/models/group_item.py index aa0a9895..6dfce438 100644 --- a/tableauserverclient/models/group_item.py +++ b/tableauserverclient/models/group_item.py @@ -1,6 +1,7 @@ -from typing import Callable, Optional, Self, TYPE_CHECKING +from typing import Callable, Optional, TYPE_CHECKING from defusedxml.ElementTree import fromstring +from typing_extensions import Self from .exceptions import UnpopulatedPropertyError from .property_decorators import property_not_empty, property_is_enum diff --git a/tableauserverclient/models/groupset_item.py b/tableauserverclient/models/groupset_item.py index f971131d..e586b266 100644 --- a/tableauserverclient/models/groupset_item.py +++ b/tableauserverclient/models/groupset_item.py @@ -1,7 +1,8 @@ -from typing import Optional, Self +from typing import Optional import xml.etree.ElementTree as ET from defusedxml.ElementTree import fromstring +from typing_extensions import Self from tableauserverclient.models.group_item import GroupItem from tableauserverclient.models.reference_item import ResourceReference diff --git a/tableauserverclient/models/reference_item.py b/tableauserverclient/models/reference_item.py index 6e60dae1..30536b4d 100644 --- a/tableauserverclient/models/reference_item.py +++ b/tableauserverclient/models/reference_item.py @@ -1,4 +1,4 @@ -from typing import Self +from typing_extensions import Self class ResourceReference: diff --git a/tableauserverclient/models/user_item.py b/tableauserverclient/models/user_item.py index c9b25c2c..04ccf9ef 100644 --- a/tableauserverclient/models/user_item.py +++ b/tableauserverclient/models/user_item.py @@ -2,9 +2,10 @@ import xml.etree.ElementTree as ET from datetime import datetime from enum import IntEnum -from typing import Optional, TYPE_CHECKING, Self +from typing import Optional, TYPE_CHECKING from defusedxml.ElementTree import fromstring +from typing_extensions import Self from tableauserverclient.datetime_helpers import parse_datetime from tableauserverclient.models.site_item import SiteAuthConfiguration From 91a39fcf6127e92b175bc84a46985f26e21004cd Mon Sep 17 00:00:00 2001 From: Jordan Woods <13803242+jorwoods@users.noreply.github.com> Date: Mon, 13 Oct 2025 21:37:43 -0500 Subject: [PATCH 3/5] fix: py3.9 typing syntax --- tableauserverclient/models/reference_item.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tableauserverclient/models/reference_item.py b/tableauserverclient/models/reference_item.py index 30536b4d..b16c8128 100644 --- a/tableauserverclient/models/reference_item.py +++ b/tableauserverclient/models/reference_item.py @@ -1,8 +1,9 @@ +from typing import Optional from typing_extensions import Self class ResourceReference: - def __init__(self, id_: str | None, tag_name: str) -> None: + def __init__(self, id_: Optional[str], tag_name: str) -> None: self.id = id_ self.tag_name = tag_name @@ -20,11 +21,11 @@ def __hash__(self: Self) -> int: return hash((self.id, self.tag_name)) @property - def id(self) -> str | None: + def id(self) -> Optional[str] : return self._id @id.setter - def id(self, value: str | None) -> None: + def id(self, value: Optional[str]) -> None: self._id = value @property From c39ba83459a0a9bb1b7d17eb68a8ba992905d898 Mon Sep 17 00:00:00 2001 From: Jordan Woods <13803242+jorwoods@users.noreply.github.com> Date: Mon, 13 Oct 2025 21:39:11 -0500 Subject: [PATCH 4/5] style: black --- tableauserverclient/models/reference_item.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tableauserverclient/models/reference_item.py b/tableauserverclient/models/reference_item.py index b16c8128..b1b94f6d 100644 --- a/tableauserverclient/models/reference_item.py +++ b/tableauserverclient/models/reference_item.py @@ -21,7 +21,7 @@ def __hash__(self: Self) -> int: return hash((self.id, self.tag_name)) @property - def id(self) -> Optional[str] : + def id(self) -> Optional[str]: return self._id @id.setter From adc5a2ed9cc59d1fd7012a2b1e77ef882cfe4abc Mon Sep 17 00:00:00 2001 From: Jordan Woods <13803242+jorwoods@users.noreply.github.com> Date: Tue, 21 Oct 2025 07:57:13 -0500 Subject: [PATCH 5/5] chore: upgrade to py3.10 syntax --- tableauserverclient/models/reference_item.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tableauserverclient/models/reference_item.py b/tableauserverclient/models/reference_item.py index b1b94f6d..30536b4d 100644 --- a/tableauserverclient/models/reference_item.py +++ b/tableauserverclient/models/reference_item.py @@ -1,9 +1,8 @@ -from typing import Optional from typing_extensions import Self class ResourceReference: - def __init__(self, id_: Optional[str], tag_name: str) -> None: + def __init__(self, id_: str | None, tag_name: str) -> None: self.id = id_ self.tag_name = tag_name @@ -21,11 +20,11 @@ def __hash__(self: Self) -> int: return hash((self.id, self.tag_name)) @property - def id(self) -> Optional[str]: + def id(self) -> str | None: return self._id @id.setter - def id(self, value: Optional[str]) -> None: + def id(self, value: str | None) -> None: self._id = value @property