tableauserverclient 0.38__py3-none-any.whl → 0.40__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. _version.py +21 -0
  2. tableauserverclient/__init__.py +10 -5
  3. tableauserverclient/bin/__init__.py +3 -0
  4. tableauserverclient/bin/_version.py +3 -3
  5. tableauserverclient/models/__init__.py +9 -0
  6. tableauserverclient/models/collection_item.py +52 -0
  7. tableauserverclient/models/connection_item.py +16 -2
  8. tableauserverclient/models/custom_view_item.py +8 -0
  9. tableauserverclient/models/data_freshness_policy_item.py +3 -3
  10. tableauserverclient/models/datasource_item.py +3 -1
  11. tableauserverclient/models/extensions_item.py +186 -0
  12. tableauserverclient/models/favorites_item.py +21 -8
  13. tableauserverclient/models/flow_item.py +1 -1
  14. tableauserverclient/models/group_item.py +7 -1
  15. tableauserverclient/models/groupset_item.py +14 -0
  16. tableauserverclient/models/interval_item.py +2 -1
  17. tableauserverclient/models/oidc_item.py +82 -0
  18. tableauserverclient/models/permissions_item.py +2 -0
  19. tableauserverclient/models/project_item.py +3 -2
  20. tableauserverclient/models/property_decorators.py +2 -2
  21. tableauserverclient/models/reference_item.py +12 -6
  22. tableauserverclient/models/schedule_item.py +10 -1
  23. tableauserverclient/models/site_item.py +26 -0
  24. tableauserverclient/models/tableau_auth.py +13 -6
  25. tableauserverclient/models/user_item.py +10 -3
  26. tableauserverclient/models/workbook_item.py +2 -2
  27. tableauserverclient/server/endpoint/__init__.py +4 -0
  28. tableauserverclient/server/endpoint/datasources_endpoint.py +152 -22
  29. tableauserverclient/server/endpoint/extensions_endpoint.py +79 -0
  30. tableauserverclient/server/endpoint/flow_task_endpoint.py +1 -1
  31. tableauserverclient/server/endpoint/flows_endpoint.py +5 -4
  32. tableauserverclient/server/endpoint/oidc_endpoint.py +157 -0
  33. tableauserverclient/server/endpoint/projects_endpoint.py +12 -0
  34. tableauserverclient/server/endpoint/schedules_endpoint.py +48 -1
  35. tableauserverclient/server/endpoint/users_endpoint.py +274 -5
  36. tableauserverclient/server/endpoint/views_endpoint.py +23 -0
  37. tableauserverclient/server/endpoint/workbooks_endpoint.py +124 -9
  38. tableauserverclient/server/request_factory.py +281 -2
  39. tableauserverclient/server/request_options.py +12 -2
  40. tableauserverclient/server/server.py +4 -0
  41. {tableauserverclient-0.38.dist-info → tableauserverclient-0.40.dist-info}/METADATA +5 -26
  42. {tableauserverclient-0.38.dist-info → tableauserverclient-0.40.dist-info}/RECORD +45 -39
  43. {tableauserverclient-0.38.dist-info → tableauserverclient-0.40.dist-info}/WHEEL +1 -1
  44. tableauserverclient-0.38.dist-info/licenses/LICENSE.versioneer +0 -7
  45. {tableauserverclient-0.38.dist-info → tableauserverclient-0.40.dist-info}/licenses/LICENSE +0 -0
  46. {tableauserverclient-0.38.dist-info → tableauserverclient-0.40.dist-info}/top_level.txt +0 -0
_version.py ADDED
@@ -0,0 +1,21 @@
1
+
2
+ # This file was generated by 'versioneer.py' (0.29) from
3
+ # revision-control system data, or from the parent directory name of an
4
+ # unpacked source archive. Distribution tarballs contain a pre-generated copy
5
+ # of this file.
6
+
7
+ import json
8
+
9
+ version_json = '''
10
+ {
11
+ "date": "2026-02-03T12:18:13-0800",
12
+ "dirty": false,
13
+ "error": null,
14
+ "full-revisionid": "d4b27f42d897883f8b8d0cb5b7edb558df2cbfb6",
15
+ "version": "0.40"
16
+ }
17
+ ''' # END VERSION_JSON
18
+
19
+
20
+ def get_versions():
21
+ return json.loads(version_json)
@@ -2,6 +2,7 @@ from tableauserverclient.bin._version import get_versions
2
2
  from tableauserverclient.namespace import NEW_NAMESPACE as DEFAULT_NAMESPACE
3
3
  from tableauserverclient.models import (
4
4
  BackgroundJobItem,
5
+ CollectionItem,
5
6
  ColumnItem,
6
7
  ConnectionCredentials,
7
8
  ConnectionItem,
@@ -12,6 +13,8 @@ from tableauserverclient.models import (
12
13
  DatabaseItem,
13
14
  DataFreshnessPolicyItem,
14
15
  DatasourceItem,
16
+ ExtensionsServer,
17
+ ExtensionsSiteSettings,
15
18
  FavoriteItem,
16
19
  FlowItem,
17
20
  FlowRunItem,
@@ -35,8 +38,10 @@ from tableauserverclient.models import (
35
38
  ProjectItem,
36
39
  Resource,
37
40
  RevisionItem,
41
+ SafeExtension,
38
42
  ScheduleItem,
39
43
  SiteAuthConfiguration,
44
+ SiteOIDCConfiguration,
40
45
  SiteItem,
41
46
  ServerInfoItem,
42
47
  SubscriptionItem,
@@ -72,7 +77,7 @@ from tableauserverclient.server import (
72
77
 
73
78
  __all__ = [
74
79
  "BackgroundJobItem",
75
- "BackgroundJobItem",
80
+ "CollectionItem",
76
81
  "ColumnItem",
77
82
  "ConnectionCredentials",
78
83
  "ConnectionItem",
@@ -86,6 +91,8 @@ __all__ = [
86
91
  "DEFAULT_NAMESPACE",
87
92
  "DQWItem",
88
93
  "ExcelRequestOptions",
94
+ "ExtensionsServer",
95
+ "ExtensionsSiteSettings",
89
96
  "FailedSignInError",
90
97
  "FavoriteItem",
91
98
  "FileuploadItem",
@@ -119,12 +126,14 @@ __all__ = [
119
126
  "RequestOptions",
120
127
  "Resource",
121
128
  "RevisionItem",
129
+ "SafeExtension",
122
130
  "ScheduleItem",
123
131
  "Server",
124
132
  "ServerInfoItem",
125
133
  "ServerResponseError",
126
134
  "SiteItem",
127
135
  "SiteAuthConfiguration",
136
+ "SiteOIDCConfiguration",
128
137
  "Sort",
129
138
  "SubscriptionItem",
130
139
  "TableauAuth",
@@ -139,7 +148,3 @@ __all__ = [
139
148
  "WeeklyInterval",
140
149
  "WorkbookItem",
141
150
  ]
142
-
143
- from .bin import _version
144
-
145
- __version__ = _version.get_versions()["version"]
@@ -0,0 +1,3 @@
1
+ # generated during initial setup of versioneer
2
+ from . import _version
3
+ __version__ = _version.get_versions()['version']
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-05-15T14:21:50-0700",
11
+ "date": "2026-02-03T12:18:13-0800",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "cda018b684c98200f3f9de11e379ba2ca3b1b3fe",
15
- "version": "0.38"
14
+ "full-revisionid": "d4b27f42d897883f8b8d0cb5b7edb558df2cbfb6",
15
+ "version": "0.40"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -1,3 +1,4 @@
1
+ from tableauserverclient.models.collection_item import CollectionItem
1
2
  from tableauserverclient.models.column_item import ColumnItem
2
3
  from tableauserverclient.models.connection_credentials import ConnectionCredentials
3
4
  from tableauserverclient.models.connection_item import ConnectionItem
@@ -9,6 +10,7 @@ from tableauserverclient.models.data_freshness_policy_item import DataFreshnessP
9
10
  from tableauserverclient.models.datasource_item import DatasourceItem
10
11
  from tableauserverclient.models.dqw_item import DQWItem
11
12
  from tableauserverclient.models.exceptions import UnpopulatedPropertyError
13
+ from tableauserverclient.models.extensions_item import ExtensionsServer, ExtensionsSiteSettings, SafeExtension
12
14
  from tableauserverclient.models.favorites_item import FavoriteItem
13
15
  from tableauserverclient.models.fileupload_item import FileuploadItem
14
16
  from tableauserverclient.models.flow_item import FlowItem
@@ -30,6 +32,7 @@ from tableauserverclient.models.linked_tasks_item import (
30
32
  )
31
33
  from tableauserverclient.models.location_item import LocationItem
32
34
  from tableauserverclient.models.metric_item import MetricItem
35
+ from tableauserverclient.models.oidc_item import SiteOIDCConfiguration
33
36
  from tableauserverclient.models.pagination_item import PaginationItem
34
37
  from tableauserverclient.models.permissions_item import PermissionsRule, Permission
35
38
  from tableauserverclient.models.project_item import ProjectItem
@@ -52,6 +55,7 @@ from tableauserverclient.models.workbook_item import WorkbookItem
52
55
  from tableauserverclient.models.extract_item import ExtractItem
53
56
 
54
57
  __all__ = [
58
+ "CollectionItem",
55
59
  "ColumnItem",
56
60
  "ConnectionCredentials",
57
61
  "ConnectionItem",
@@ -79,6 +83,7 @@ __all__ = [
79
83
  "BackgroundJobItem",
80
84
  "LocationItem",
81
85
  "MetricItem",
86
+ "SiteOIDCConfiguration",
82
87
  "PaginationItem",
83
88
  "Permission",
84
89
  "PermissionsRule",
@@ -88,6 +93,7 @@ __all__ = [
88
93
  "ServerInfoItem",
89
94
  "SiteAuthConfiguration",
90
95
  "SiteItem",
96
+ "SiteOIDCConfiguration",
91
97
  "SubscriptionItem",
92
98
  "TableItem",
93
99
  "TableauAuth",
@@ -108,4 +114,7 @@ __all__ = [
108
114
  "LinkedTaskStepItem",
109
115
  "LinkedTaskFlowRunItem",
110
116
  "ExtractItem",
117
+ "ExtensionsServer",
118
+ "ExtensionsSiteSettings",
119
+ "SafeExtension",
111
120
  ]
@@ -0,0 +1,52 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+ from xml.etree.ElementTree import Element
4
+
5
+ from defusedxml.ElementTree import fromstring
6
+ from typing_extensions import Self
7
+
8
+ from tableauserverclient.datetime_helpers import parse_datetime
9
+ from tableauserverclient.models.user_item import UserItem
10
+
11
+
12
+ class CollectionItem:
13
+ def __init__(self) -> None:
14
+ self.id: Optional[str] = None
15
+ self.name: Optional[str] = None
16
+ self.description: Optional[str] = None
17
+ self.created_at: Optional[datetime] = None
18
+ self.updated_at: Optional[datetime] = None
19
+ self.owner: Optional[UserItem] = None
20
+ self.total_item_count: Optional[int] = None
21
+ self.permissioned_item_count: Optional[int] = None
22
+ self.visibility: Optional[str] = None # Assuming visibility is a string, adjust as necessary
23
+
24
+ @classmethod
25
+ def from_response(cls, response: bytes, ns) -> list[Self]:
26
+ parsed_response = fromstring(response)
27
+
28
+ collection_elements = parsed_response.findall(".//t:collection", namespaces=ns)
29
+ if not collection_elements:
30
+ raise ValueError("No collection element found in the response")
31
+
32
+ collections = [cls.from_xml(c, ns) for c in collection_elements]
33
+ return collections
34
+
35
+ @classmethod
36
+ def from_xml(cls, xml: Element, ns) -> Self:
37
+ collection_item = cls()
38
+ collection_item.id = xml.get("id")
39
+ collection_item.name = xml.get("name")
40
+ collection_item.description = xml.get("description")
41
+ collection_item.created_at = parse_datetime(xml.get("createdAt"))
42
+ collection_item.updated_at = parse_datetime(xml.get("updatedAt"))
43
+ owner_element = xml.find(".//t:owner", namespaces=ns)
44
+ if owner_element is not None:
45
+ collection_item.owner = UserItem.from_xml(owner_element, ns)
46
+ else:
47
+ collection_item.owner = None
48
+ collection_item.total_item_count = int(xml.get("totalItemCount", 0))
49
+ collection_item.permissioned_item_count = int(xml.get("permissionedItemCount", 0))
50
+ collection_item.visibility = xml.get("visibility")
51
+
52
+ return collection_item
@@ -41,6 +41,9 @@ class ConnectionItem:
41
41
  server_port: str
42
42
  The port used for the connection.
43
43
 
44
+ auth_type: str
45
+ Specifies the type of authentication used by the connection.
46
+
44
47
  connection_credentials: ConnectionCredentials
45
48
  The Connection Credentials object containing authentication details for
46
49
  the connection. Replaces username/password/embed_password when
@@ -59,6 +62,7 @@ class ConnectionItem:
59
62
  self.username: Optional[str] = None
60
63
  self.connection_credentials: Optional[ConnectionCredentials] = None
61
64
  self._query_tagging: Optional[bool] = None
65
+ self._auth_type: Optional[str] = None
62
66
 
63
67
  @property
64
68
  def datasource_id(self) -> Optional[str]:
@@ -91,8 +95,16 @@ class ConnectionItem:
91
95
  return
92
96
  self._query_tagging = value
93
97
 
98
+ @property
99
+ def auth_type(self) -> Optional[str]:
100
+ return self._auth_type
101
+
102
+ @auth_type.setter
103
+ def auth_type(self, value: Optional[str]):
104
+ self._auth_type = value
105
+
94
106
  def __repr__(self):
95
- return "<ConnectionItem#{_id} embed={embed_password} type={_connection_type} username={username}>".format(
107
+ return "<ConnectionItem#{_id} embed={embed_password} type={_connection_type} auth={_auth_type} username={username}>".format(
96
108
  **self.__dict__
97
109
  )
98
110
 
@@ -108,10 +120,11 @@ class ConnectionItem:
108
120
  connection_item.embed_password = string_to_bool(connection_xml.get("embedPassword", ""))
109
121
  connection_item.server_address = connection_xml.get("serverAddress", connection_xml.get("server", None))
110
122
  connection_item.server_port = connection_xml.get("serverPort", connection_xml.get("port", None))
111
- connection_item.username = connection_xml.get("userName", None)
123
+ connection_item.username = connection_xml.get("userName", connection_xml.get("username", None))
112
124
  connection_item._query_tagging = (
113
125
  string_to_bool(s) if (s := connection_xml.get("queryTagging", None)) else None
114
126
  )
127
+ connection_item._auth_type = connection_xml.get("authenticationType", None)
115
128
  datasource_elem = connection_xml.find(".//t:datasource", namespaces=ns)
116
129
  if datasource_elem is not None:
117
130
  connection_item._datasource_id = datasource_elem.get("id", None)
@@ -139,6 +152,7 @@ class ConnectionItem:
139
152
 
140
153
  connection_item.server_address = connection_xml.get("serverAddress", None)
141
154
  connection_item.server_port = connection_xml.get("serverPort", None)
155
+ connection_item._auth_type = connection_xml.get("authenticationType", None)
142
156
 
143
157
  connection_credentials = connection_xml.find(".//t:connectionCredentials", namespaces=ns)
144
158
 
@@ -158,10 +158,18 @@ class CustomViewItem:
158
158
  def workbook(self) -> Optional[WorkbookItem]:
159
159
  return self._workbook
160
160
 
161
+ @workbook.setter
162
+ def workbook(self, value: WorkbookItem) -> None:
163
+ self._workbook = value
164
+
161
165
  @property
162
166
  def view(self) -> Optional[ViewItem]:
163
167
  return self._view
164
168
 
169
+ @view.setter
170
+ def view(self, value: ViewItem) -> None:
171
+ self._view = value
172
+
165
173
  @classmethod
166
174
  def from_response(cls, resp, ns, workbook_id="") -> Optional["CustomViewItem"]:
167
175
  item = cls.list_from_response(resp, ns, workbook_id)
@@ -66,7 +66,7 @@ class DataFreshnessPolicyItem:
66
66
  return self._interval_item
67
67
 
68
68
  @interval_item.setter
69
- def interval_item(self, value: list[str]):
69
+ def interval_item(self, value: Optional[list[str]]):
70
70
  self._interval_item = value
71
71
 
72
72
  @property
@@ -127,7 +127,7 @@ class DataFreshnessPolicyItem:
127
127
  return self._fresh_every_schedule
128
128
 
129
129
  @fresh_every_schedule.setter
130
- def fresh_every_schedule(self, value: FreshEvery):
130
+ def fresh_every_schedule(self, value: Optional[FreshEvery]):
131
131
  self._fresh_every_schedule = value
132
132
 
133
133
  @property
@@ -135,7 +135,7 @@ class DataFreshnessPolicyItem:
135
135
  return self._fresh_at_schedule
136
136
 
137
137
  @fresh_at_schedule.setter
138
- def fresh_at_schedule(self, value: FreshAt):
138
+ def fresh_at_schedule(self, value: Optional[FreshAt]):
139
139
  self._fresh_at_schedule = value
140
140
 
141
141
  @classmethod
@@ -490,7 +490,7 @@ class DatasourceItem:
490
490
  self._owner = owner
491
491
 
492
492
  @classmethod
493
- def from_response(cls, resp: str, ns: dict) -> list["DatasourceItem"]:
493
+ def from_response(cls, resp: bytes, ns: dict) -> list["DatasourceItem"]:
494
494
  all_datasource_items = list()
495
495
  parsed_response = fromstring(resp)
496
496
  all_datasource_xml = parsed_response.findall(".//t:datasource", namespaces=ns)
@@ -535,6 +535,7 @@ class DatasourceItem:
535
535
 
536
536
  project_id = None
537
537
  project_name = None
538
+ project = None
538
539
  project_elem = datasource_xml.find(".//t:project", namespaces=ns)
539
540
  if project_elem is not None:
540
541
  project = ProjectItem.from_xml(project_elem, ns)
@@ -542,6 +543,7 @@ class DatasourceItem:
542
543
  project_name = project_elem.get("name", None)
543
544
 
544
545
  owner_id = None
546
+ owner = None
545
547
  owner_elem = datasource_xml.find(".//t:owner", namespaces=ns)
546
548
  if owner_elem is not None:
547
549
  owner = UserItem.from_xml(owner_elem, ns)
@@ -0,0 +1,186 @@
1
+ from typing import overload
2
+ from typing_extensions import Self
3
+
4
+ from defusedxml.ElementTree import fromstring
5
+
6
+ from tableauserverclient.models.property_decorators import property_is_boolean
7
+
8
+
9
+ class ExtensionsServer:
10
+ def __init__(self) -> None:
11
+ self._enabled: bool | None = None
12
+ self._block_list: list[str] | None = None
13
+
14
+ @property
15
+ def enabled(self) -> bool | None:
16
+ """Indicates whether the extensions server is enabled."""
17
+ return self._enabled
18
+
19
+ @enabled.setter
20
+ @property_is_boolean
21
+ def enabled(self, value: bool | None) -> None:
22
+ self._enabled = value
23
+
24
+ @property
25
+ def block_list(self) -> list[str] | None:
26
+ """List of blocked extensions."""
27
+ return self._block_list
28
+
29
+ @block_list.setter
30
+ def block_list(self, value: list[str] | None) -> None:
31
+ self._block_list = value
32
+
33
+ @classmethod
34
+ def from_response(cls: type[Self], response, ns) -> Self:
35
+ xml = fromstring(response)
36
+ obj = cls()
37
+ element = xml.find(".//t:extensionsServerSettings", namespaces=ns)
38
+ if element is None:
39
+ raise ValueError("Missing extensionsServerSettings element in response")
40
+
41
+ if (enabled_element := element.find("./t:extensionsGloballyEnabled", namespaces=ns)) is not None:
42
+ obj.enabled = string_to_bool(enabled_element.text)
43
+ obj.block_list = [e.text for e in element.findall("./t:blockList", namespaces=ns) if e.text is not None]
44
+
45
+ return obj
46
+
47
+
48
+ class SafeExtension:
49
+ def __init__(
50
+ self, url: str | None = None, full_data_allowed: bool | None = None, prompt_needed: bool | None = None
51
+ ) -> None:
52
+ self.url = url
53
+ self._full_data_allowed = full_data_allowed
54
+ self._prompt_needed = prompt_needed
55
+
56
+ @property
57
+ def full_data_allowed(self) -> bool | None:
58
+ return self._full_data_allowed
59
+
60
+ @full_data_allowed.setter
61
+ @property_is_boolean
62
+ def full_data_allowed(self, value: bool | None) -> None:
63
+ self._full_data_allowed = value
64
+
65
+ @property
66
+ def prompt_needed(self) -> bool | None:
67
+ return self._prompt_needed
68
+
69
+ @prompt_needed.setter
70
+ @property_is_boolean
71
+ def prompt_needed(self, value: bool | None) -> None:
72
+ self._prompt_needed = value
73
+
74
+
75
+ class ExtensionsSiteSettings:
76
+ def __init__(self) -> None:
77
+ self._enabled: bool | None = None
78
+ self._use_default_setting: bool | None = None
79
+ self.safe_list: list[SafeExtension] | None = None
80
+ self._allow_trusted: bool | None = None
81
+ self._include_tableau_built: bool | None = None
82
+ self._include_partner_built: bool | None = None
83
+ self._include_sandboxed: bool | None = None
84
+
85
+ @property
86
+ def enabled(self) -> bool | None:
87
+ return self._enabled
88
+
89
+ @enabled.setter
90
+ @property_is_boolean
91
+ def enabled(self, value: bool | None) -> None:
92
+ self._enabled = value
93
+
94
+ @property
95
+ def use_default_setting(self) -> bool | None:
96
+ return self._use_default_setting
97
+
98
+ @use_default_setting.setter
99
+ @property_is_boolean
100
+ def use_default_setting(self, value: bool | None) -> None:
101
+ self._use_default_setting = value
102
+
103
+ @property
104
+ def allow_trusted(self) -> bool | None:
105
+ return self._allow_trusted
106
+
107
+ @allow_trusted.setter
108
+ @property_is_boolean
109
+ def allow_trusted(self, value: bool | None) -> None:
110
+ self._allow_trusted = value
111
+
112
+ @property
113
+ def include_tableau_built(self) -> bool | None:
114
+ return self._include_tableau_built
115
+
116
+ @include_tableau_built.setter
117
+ @property_is_boolean
118
+ def include_tableau_built(self, value: bool | None) -> None:
119
+ self._include_tableau_built = value
120
+
121
+ @property
122
+ def include_partner_built(self) -> bool | None:
123
+ return self._include_partner_built
124
+
125
+ @include_partner_built.setter
126
+ @property_is_boolean
127
+ def include_partner_built(self, value: bool | None) -> None:
128
+ self._include_partner_built = value
129
+
130
+ @property
131
+ def include_sandboxed(self) -> bool | None:
132
+ return self._include_sandboxed
133
+
134
+ @include_sandboxed.setter
135
+ @property_is_boolean
136
+ def include_sandboxed(self, value: bool | None) -> None:
137
+ self._include_sandboxed = value
138
+
139
+ @classmethod
140
+ def from_response(cls: type[Self], response, ns) -> Self:
141
+ xml = fromstring(response)
142
+ element = xml.find(".//t:extensionsSiteSettings", namespaces=ns)
143
+ obj = cls()
144
+ if element is None:
145
+ raise ValueError("Missing extensionsSiteSettings element in response")
146
+
147
+ if (enabled_element := element.find("./t:extensionsEnabled", namespaces=ns)) is not None:
148
+ obj.enabled = string_to_bool(enabled_element.text)
149
+ if (default_settings_element := element.find("./t:useDefaultSetting", namespaces=ns)) is not None:
150
+ obj.use_default_setting = string_to_bool(default_settings_element.text)
151
+ if (allow_trusted_element := element.find("./t:allowTrusted", namespaces=ns)) is not None:
152
+ obj.allow_trusted = string_to_bool(allow_trusted_element.text)
153
+ if (include_tableau_built_element := element.find("./t:includeTableauBuilt", namespaces=ns)) is not None:
154
+ obj.include_tableau_built = string_to_bool(include_tableau_built_element.text)
155
+ if (include_partner_built_element := element.find("./t:includePartnerBuilt", namespaces=ns)) is not None:
156
+ obj.include_partner_built = string_to_bool(include_partner_built_element.text)
157
+ if (include_sandboxed_element := element.find("./t:includeSandboxed", namespaces=ns)) is not None:
158
+ obj.include_sandboxed = string_to_bool(include_sandboxed_element.text)
159
+
160
+ safe_list = []
161
+ for safe_extension_element in element.findall("./t:safeList", namespaces=ns):
162
+ url = safe_extension_element.find("./t:url", namespaces=ns)
163
+ full_data_allowed = safe_extension_element.find("./t:fullDataAllowed", namespaces=ns)
164
+ prompt_needed = safe_extension_element.find("./t:promptNeeded", namespaces=ns)
165
+
166
+ safe_extension = SafeExtension(
167
+ url=url.text if url is not None else None,
168
+ full_data_allowed=string_to_bool(full_data_allowed.text) if full_data_allowed is not None else None,
169
+ prompt_needed=string_to_bool(prompt_needed.text) if prompt_needed is not None else None,
170
+ )
171
+ safe_list.append(safe_extension)
172
+
173
+ obj.safe_list = safe_list
174
+ return obj
175
+
176
+
177
+ @overload
178
+ def string_to_bool(s: str) -> bool: ...
179
+
180
+
181
+ @overload
182
+ def string_to_bool(s: None) -> None: ...
183
+
184
+
185
+ def string_to_bool(s):
186
+ return s.lower() == "true" if s is not None else None
@@ -1,9 +1,8 @@
1
1
  import logging
2
2
 
3
- from typing import Union
3
+ from typing import TypedDict, Union
4
4
  from defusedxml.ElementTree import fromstring
5
-
6
- from tableauserverclient.models.tableau_types import TableauItem
5
+ from tableauserverclient.models.collection_item import CollectionItem
7
6
  from tableauserverclient.models.datasource_item import DatasourceItem
8
7
  from tableauserverclient.models.flow_item import FlowItem
9
8
  from tableauserverclient.models.project_item import ProjectItem
@@ -13,16 +12,22 @@ from tableauserverclient.models.workbook_item import WorkbookItem
13
12
 
14
13
  from tableauserverclient.helpers.logging import logger
15
14
 
16
- FavoriteType = dict[
17
- str,
18
- list[TableauItem],
19
- ]
15
+
16
+ class FavoriteType(TypedDict):
17
+ collections: list[CollectionItem]
18
+ datasources: list[DatasourceItem]
19
+ flows: list[FlowItem]
20
+ projects: list[ProjectItem]
21
+ metrics: list[MetricItem]
22
+ views: list[ViewItem]
23
+ workbooks: list[WorkbookItem]
20
24
 
21
25
 
22
26
  class FavoriteItem:
23
27
  @classmethod
24
28
  def from_response(cls, xml: Union[str, bytes], namespace: dict) -> FavoriteType:
25
29
  favorites: FavoriteType = {
30
+ "collections": [],
26
31
  "datasources": [],
27
32
  "flows": [],
28
33
  "projects": [],
@@ -32,6 +37,7 @@ class FavoriteItem:
32
37
  }
33
38
  parsed_response = fromstring(xml)
34
39
 
40
+ collections_xml = parsed_response.findall(".//t:favorite/t:collection", namespace)
35
41
  datasources_xml = parsed_response.findall(".//t:favorite/t:datasource", namespace)
36
42
  flows_xml = parsed_response.findall(".//t:favorite/t:flow", namespace)
37
43
  metrics_xml = parsed_response.findall(".//t:favorite/t:metric", namespace)
@@ -40,13 +46,14 @@ class FavoriteItem:
40
46
  workbooks_xml = parsed_response.findall(".//t:favorite/t:workbook", namespace)
41
47
 
42
48
  logger.debug(
43
- "ds: {}, flows: {}, metrics: {}, projects: {}, views: {}, wbs: {}".format(
49
+ "ds: {}, flows: {}, metrics: {}, projects: {}, views: {}, wbs: {}, collections: {}".format(
44
50
  len(datasources_xml),
45
51
  len(flows_xml),
46
52
  len(metrics_xml),
47
53
  len(projects_xml),
48
54
  len(views_xml),
49
55
  len(workbooks_xml),
56
+ len(collections_xml),
50
57
  )
51
58
  )
52
59
  for datasource in datasources_xml:
@@ -85,5 +92,11 @@ class FavoriteItem:
85
92
  logger.debug(fav_workbook)
86
93
  favorites["workbooks"].append(fav_workbook)
87
94
 
95
+ for collection in collections_xml:
96
+ fav_collection = CollectionItem.from_xml(collection, namespace)
97
+ if fav_collection:
98
+ logger.debug(fav_collection)
99
+ favorites["collections"].append(fav_collection)
100
+
88
101
  logger.debug(favorites)
89
102
  return favorites
@@ -129,7 +129,7 @@ class FlowItem:
129
129
  return self._description
130
130
 
131
131
  @description.setter
132
- def description(self, value: str) -> None:
132
+ def description(self, value: Optional[str]) -> None:
133
133
  self._description = value
134
134
 
135
135
  @property
@@ -1,6 +1,7 @@
1
1
  from typing import Callable, Optional, TYPE_CHECKING
2
2
 
3
3
  from defusedxml.ElementTree import fromstring
4
+ from typing_extensions import Self
4
5
 
5
6
  from .exceptions import UnpopulatedPropertyError
6
7
  from .property_decorators import property_not_empty, property_is_enum
@@ -92,7 +93,7 @@ class GroupItem:
92
93
  return self._name
93
94
 
94
95
  @name.setter
95
- def name(self, value: str) -> None:
96
+ def name(self, value: Optional[str]) -> None:
96
97
  self._name = value
97
98
 
98
99
  @property
@@ -157,3 +158,8 @@ class GroupItem:
157
158
  @staticmethod
158
159
  def as_reference(id_: str) -> ResourceReference:
159
160
  return ResourceReference(id_, GroupItem.tag_name)
161
+
162
+ def to_reference(self: Self) -> ResourceReference:
163
+ if self.id is None:
164
+ raise ValueError(f"{self.__class__.__qualname__} must have id to be converted to reference")
165
+ return ResourceReference(self.id, self.tag_name)
@@ -2,6 +2,7 @@ from typing import Optional
2
2
  import xml.etree.ElementTree as ET
3
3
 
4
4
  from defusedxml.ElementTree import fromstring
5
+ from typing_extensions import Self
5
6
 
6
7
  from tableauserverclient.models.group_item import GroupItem
7
8
  from tableauserverclient.models.reference_item import ResourceReference
@@ -24,6 +25,14 @@ class GroupSetItem:
24
25
  def __repr__(self) -> str:
25
26
  return self.__str__()
26
27
 
28
+ @property
29
+ def name(self) -> Optional[str]:
30
+ return self._name
31
+
32
+ @name.setter
33
+ def name(self, value: Optional[str]) -> None:
34
+ self._name = value
35
+
27
36
  @classmethod
28
37
  def from_response(cls, response: bytes, ns: dict[str, str]) -> list["GroupSetItem"]:
29
38
  parsed_response = fromstring(response)
@@ -51,3 +60,8 @@ class GroupSetItem:
51
60
  @staticmethod
52
61
  def as_reference(id_: str) -> ResourceReference:
53
62
  return ResourceReference(id_, GroupSetItem.tag_name)
63
+
64
+ def to_reference(self: Self) -> ResourceReference:
65
+ if self.id is None:
66
+ raise ValueError(f"{self.__class__.__qualname__} must have id to be converted to reference")
67
+ return ResourceReference(self.id, self.tag_name)