tableauserverclient 0.37__py3-none-any.whl → 0.39__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 (118) hide show
  1. tableauserverclient/bin/_version.py → _version.py +3 -3
  2. bin/__init__.py +3 -0
  3. bin/_version.py +21 -0
  4. {tableauserverclient/helpers → helpers}/strings.py +25 -1
  5. {tableauserverclient/models → models}/__init__.py +15 -1
  6. models/collection_item.py +52 -0
  7. {tableauserverclient/models → models}/connection_item.py +16 -2
  8. {tableauserverclient/models → models}/custom_view_item.py +8 -0
  9. {tableauserverclient/models → models}/data_freshness_policy_item.py +3 -3
  10. {tableauserverclient/models → models}/datasource_item.py +113 -3
  11. models/extensions_item.py +186 -0
  12. models/extract_item.py +82 -0
  13. {tableauserverclient/models → models}/favorites_item.py +21 -8
  14. {tableauserverclient/models → models}/flow_item.py +3 -3
  15. {tableauserverclient/models → models}/group_item.py +18 -1
  16. {tableauserverclient/models → models}/groupset_item.py +14 -0
  17. {tableauserverclient/models → models}/interval_item.py +42 -1
  18. models/location_item.py +53 -0
  19. models/oidc_item.py +82 -0
  20. {tableauserverclient/models → models}/permissions_item.py +2 -0
  21. {tableauserverclient/models → models}/project_item.py +141 -29
  22. {tableauserverclient/models → models}/property_decorators.py +2 -2
  23. {tableauserverclient/models → models}/reference_item.py +12 -6
  24. {tableauserverclient/models → models}/schedule_item.py +67 -1
  25. {tableauserverclient/models → models}/site_item.py +54 -0
  26. {tableauserverclient/models → models}/table_item.py +7 -3
  27. {tableauserverclient/models → models}/tableau_auth.py +13 -6
  28. {tableauserverclient/models → models}/tableau_types.py +13 -1
  29. {tableauserverclient/models → models}/user_item.py +111 -4
  30. {tableauserverclient/models → models}/view_item.py +79 -5
  31. {tableauserverclient/models → models}/workbook_item.py +153 -3
  32. {tableauserverclient/server → server}/endpoint/__init__.py +4 -0
  33. {tableauserverclient/server → server}/endpoint/databases_endpoint.py +101 -18
  34. {tableauserverclient/server → server}/endpoint/datasources_endpoint.py +155 -25
  35. {tableauserverclient/server → server}/endpoint/dqw_endpoint.py +16 -6
  36. {tableauserverclient/server → server}/endpoint/endpoint.py +39 -0
  37. server/endpoint/extensions_endpoint.py +79 -0
  38. {tableauserverclient/server → server}/endpoint/flow_task_endpoint.py +1 -1
  39. {tableauserverclient/server → server}/endpoint/flows_endpoint.py +5 -4
  40. server/endpoint/oidc_endpoint.py +157 -0
  41. {tableauserverclient/server → server}/endpoint/projects_endpoint.py +12 -0
  42. server/endpoint/schedules_endpoint.py +328 -0
  43. {tableauserverclient/server → server}/endpoint/sites_endpoint.py +18 -1
  44. {tableauserverclient/server → server}/endpoint/tables_endpoint.py +140 -17
  45. {tableauserverclient/server → server}/endpoint/users_endpoint.py +296 -10
  46. {tableauserverclient/server → server}/endpoint/views_endpoint.py +23 -0
  47. {tableauserverclient/server → server}/endpoint/workbooks_endpoint.py +124 -9
  48. {tableauserverclient/server → server}/query.py +36 -0
  49. {tableauserverclient/server → server}/request_factory.py +286 -2
  50. {tableauserverclient/server → server}/request_options.py +139 -3
  51. {tableauserverclient/server → server}/server.py +46 -0
  52. {tableauserverclient-0.37.dist-info → tableauserverclient-0.39.dist-info}/METADATA +5 -26
  53. tableauserverclient-0.39.dist-info/RECORD +107 -0
  54. {tableauserverclient-0.37.dist-info → tableauserverclient-0.39.dist-info}/WHEEL +1 -1
  55. tableauserverclient-0.39.dist-info/top_level.txt +4 -0
  56. tableauserverclient/__init__.py +0 -141
  57. tableauserverclient/config.py +0 -27
  58. tableauserverclient/datetime_helpers.py +0 -45
  59. tableauserverclient/exponential_backoff.py +0 -30
  60. tableauserverclient/filesys_helpers.py +0 -63
  61. tableauserverclient/namespace.py +0 -37
  62. tableauserverclient/py.typed +0 -0
  63. tableauserverclient/server/endpoint/schedules_endpoint.py +0 -151
  64. tableauserverclient-0.37.dist-info/RECORD +0 -106
  65. tableauserverclient-0.37.dist-info/licenses/LICENSE.versioneer +0 -7
  66. tableauserverclient-0.37.dist-info/top_level.txt +0 -1
  67. {tableauserverclient/helpers → helpers}/__init__.py +0 -0
  68. {tableauserverclient/helpers → helpers}/headers.py +0 -0
  69. {tableauserverclient/helpers → helpers}/logging.py +0 -0
  70. {tableauserverclient/models → models}/column_item.py +0 -0
  71. {tableauserverclient/models → models}/connection_credentials.py +0 -0
  72. {tableauserverclient/models → models}/data_acceleration_report_item.py +0 -0
  73. {tableauserverclient/models → models}/data_alert_item.py +0 -0
  74. {tableauserverclient/models → models}/database_item.py +0 -0
  75. {tableauserverclient/models → models}/dqw_item.py +0 -0
  76. {tableauserverclient/models → models}/exceptions.py +0 -0
  77. {tableauserverclient/models → models}/fileupload_item.py +0 -0
  78. {tableauserverclient/models → models}/flow_run_item.py +0 -0
  79. {tableauserverclient/models → models}/job_item.py +0 -0
  80. {tableauserverclient/models → models}/linked_tasks_item.py +0 -0
  81. {tableauserverclient/models → models}/metric_item.py +0 -0
  82. {tableauserverclient/models → models}/pagination_item.py +0 -0
  83. {tableauserverclient/models → models}/revision_item.py +0 -0
  84. {tableauserverclient/models → models}/server_info_item.py +0 -0
  85. {tableauserverclient/models → models}/subscription_item.py +0 -0
  86. {tableauserverclient/models → models}/tag_item.py +0 -0
  87. {tableauserverclient/models → models}/target.py +0 -0
  88. {tableauserverclient/models → models}/task_item.py +0 -0
  89. {tableauserverclient/models → models}/virtual_connection_item.py +0 -0
  90. {tableauserverclient/models → models}/webhook_item.py +0 -0
  91. {tableauserverclient/server → server}/__init__.py +0 -0
  92. {tableauserverclient/server → server}/endpoint/auth_endpoint.py +0 -0
  93. {tableauserverclient/server → server}/endpoint/custom_views_endpoint.py +0 -0
  94. {tableauserverclient/server → server}/endpoint/data_acceleration_report_endpoint.py +0 -0
  95. {tableauserverclient/server → server}/endpoint/data_alert_endpoint.py +0 -0
  96. {tableauserverclient/server → server}/endpoint/default_permissions_endpoint.py +0 -0
  97. {tableauserverclient/server → server}/endpoint/exceptions.py +0 -0
  98. {tableauserverclient/server → server}/endpoint/favorites_endpoint.py +0 -0
  99. {tableauserverclient/server → server}/endpoint/fileuploads_endpoint.py +0 -0
  100. {tableauserverclient/server → server}/endpoint/flow_runs_endpoint.py +0 -0
  101. {tableauserverclient/server → server}/endpoint/groups_endpoint.py +0 -0
  102. {tableauserverclient/server → server}/endpoint/groupsets_endpoint.py +0 -0
  103. {tableauserverclient/server → server}/endpoint/jobs_endpoint.py +0 -0
  104. {tableauserverclient/server → server}/endpoint/linked_tasks_endpoint.py +0 -0
  105. {tableauserverclient/server → server}/endpoint/metadata_endpoint.py +0 -0
  106. {tableauserverclient/server → server}/endpoint/metrics_endpoint.py +0 -0
  107. {tableauserverclient/server → server}/endpoint/permissions_endpoint.py +0 -0
  108. {tableauserverclient/server → server}/endpoint/resource_tagger.py +0 -0
  109. {tableauserverclient/server → server}/endpoint/server_info_endpoint.py +0 -0
  110. {tableauserverclient/server → server}/endpoint/subscriptions_endpoint.py +0 -0
  111. {tableauserverclient/server → server}/endpoint/tasks_endpoint.py +0 -0
  112. {tableauserverclient/server → server}/endpoint/virtual_connections_endpoint.py +0 -0
  113. {tableauserverclient/server → server}/endpoint/webhooks_endpoint.py +0 -0
  114. {tableauserverclient/server → server}/exceptions.py +0 -0
  115. {tableauserverclient/server → server}/filter.py +0 -0
  116. {tableauserverclient/server → server}/pager.py +0 -0
  117. {tableauserverclient/server → server}/sort.py +0 -0
  118. {tableauserverclient-0.37.dist-info → tableauserverclient-0.39.dist-info}/licenses/LICENSE +0 -0
@@ -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
models/extract_item.py ADDED
@@ -0,0 +1,82 @@
1
+ from typing import Optional, List
2
+ from defusedxml.ElementTree import fromstring
3
+ import xml.etree.ElementTree as ET
4
+
5
+
6
+ class ExtractItem:
7
+ """
8
+ An extract refresh task item.
9
+
10
+ Attributes
11
+ ----------
12
+ id : str
13
+ The ID of the extract refresh task
14
+ priority : int
15
+ The priority of the task
16
+ type : str
17
+ The type of extract refresh (incremental or full)
18
+ workbook_id : str, optional
19
+ The ID of the workbook if this is a workbook extract
20
+ datasource_id : str, optional
21
+ The ID of the datasource if this is a datasource extract
22
+ """
23
+
24
+ def __init__(
25
+ self, priority: int, refresh_type: str, workbook_id: Optional[str] = None, datasource_id: Optional[str] = None
26
+ ):
27
+ self._id: Optional[str] = None
28
+ self._priority = priority
29
+ self._type = refresh_type
30
+ self._workbook_id = workbook_id
31
+ self._datasource_id = datasource_id
32
+
33
+ @property
34
+ def id(self) -> Optional[str]:
35
+ return self._id
36
+
37
+ @property
38
+ def priority(self) -> int:
39
+ return self._priority
40
+
41
+ @property
42
+ def type(self) -> str:
43
+ return self._type
44
+
45
+ @property
46
+ def workbook_id(self) -> Optional[str]:
47
+ return self._workbook_id
48
+
49
+ @property
50
+ def datasource_id(self) -> Optional[str]:
51
+ return self._datasource_id
52
+
53
+ @classmethod
54
+ def from_response(cls, resp: str, ns: dict) -> List["ExtractItem"]:
55
+ """Create ExtractItem objects from XML response."""
56
+ parsed_response = fromstring(resp)
57
+ return cls.from_xml_element(parsed_response, ns)
58
+
59
+ @classmethod
60
+ def from_xml_element(cls, parsed_response: ET.Element, ns: dict) -> List["ExtractItem"]:
61
+ """Create ExtractItem objects from XML element."""
62
+ all_extract_items = []
63
+ all_extract_xml = parsed_response.findall(".//t:extract", namespaces=ns)
64
+
65
+ for extract_xml in all_extract_xml:
66
+ extract_id = extract_xml.get("id", None)
67
+ priority = int(extract_xml.get("priority", 0))
68
+ refresh_type = extract_xml.get("type", "")
69
+
70
+ # Check for workbook or datasource
71
+ workbook_elem = extract_xml.find(".//t:workbook", namespaces=ns)
72
+ datasource_elem = extract_xml.find(".//t:datasource", namespaces=ns)
73
+
74
+ workbook_id = workbook_elem.get("id", None) if workbook_elem is not None else None
75
+ datasource_id = datasource_elem.get("id", None) if datasource_elem is not None else None
76
+
77
+ extract_item = cls(priority, refresh_type, workbook_id, datasource_id)
78
+ extract_item._id = extract_id
79
+
80
+ all_extract_items.append(extract_item)
81
+
82
+ return all_extract_items
@@ -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
@@ -146,8 +146,8 @@ class FlowItem:
146
146
  def _set_permissions(self, permissions):
147
147
  self._permissions = permissions
148
148
 
149
- def _set_data_quality_warnings(self, dqws):
150
- self._data_quality_warnings = dqws
149
+ def _set_data_quality_warnings(self, dqw):
150
+ self._data_quality_warnings = dqw
151
151
 
152
152
  def _parse_common_elements(self, flow_xml, ns):
153
153
  if not isinstance(flow_xml, ET.Element):
@@ -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
@@ -44,6 +45,11 @@ class GroupItem:
44
45
  login to a site. When the mode is onSync, a license is granted for group
45
46
  members each time the domain is synced.
46
47
 
48
+ Attributes
49
+ ----------
50
+ user_count: Optional[int]
51
+ The number of users in the group.
52
+
47
53
  Examples
48
54
  --------
49
55
  >>> # Create a new group item
@@ -65,6 +71,7 @@ class GroupItem:
65
71
  self._users: Optional[Callable[..., "Pager"]] = None
66
72
  self.name: Optional[str] = name
67
73
  self.domain_name: Optional[str] = domain_name
74
+ self._user_count: Optional[int] = None
68
75
 
69
76
  def __repr__(self):
70
77
  return f"{self.__class__.__name__}({self.__dict__!r})"
@@ -86,7 +93,7 @@ class GroupItem:
86
93
  return self._name
87
94
 
88
95
  @name.setter
89
- def name(self, value: str) -> None:
96
+ def name(self, value: Optional[str]) -> None:
90
97
  self._name = value
91
98
 
92
99
  @property
@@ -118,6 +125,10 @@ class GroupItem:
118
125
  def _set_users(self, users: Callable[..., "Pager"]) -> None:
119
126
  self._users = users
120
127
 
128
+ @property
129
+ def user_count(self) -> Optional[int]:
130
+ return self._user_count
131
+
121
132
  @classmethod
122
133
  def from_response(cls, resp, ns) -> list["GroupItem"]:
123
134
  all_group_items = list()
@@ -127,6 +138,7 @@ class GroupItem:
127
138
  name = group_xml.get("name", None)
128
139
  group_item = cls(name)
129
140
  group_item._id = group_xml.get("id", None)
141
+ group_item._user_count = int(count) if (count := group_xml.get("userCount", None)) else None
130
142
 
131
143
  # Domain name is returned in a domain element for some calls
132
144
  domain_elem = group_xml.find(".//t:domain", namespaces=ns)
@@ -146,3 +158,8 @@ class GroupItem:
146
158
  @staticmethod
147
159
  def as_reference(id_: str) -> ResourceReference:
148
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)
@@ -2,6 +2,13 @@ from .property_decorators import property_is_valid_time, property_not_nullable
2
2
 
3
3
 
4
4
  class IntervalItem:
5
+ """
6
+ This class sets the frequency and start time of the scheduled item. This
7
+ class contains the classes for the hourly, daily, weekly, and monthly
8
+ intervals. This class mirrors the options you can set using the REST API and
9
+ the Tableau Server interface.
10
+ """
11
+
5
12
  class Frequency:
6
13
  Hourly = "Hourly"
7
14
  Daily = "Daily"
@@ -26,6 +33,19 @@ class IntervalItem:
26
33
 
27
34
 
28
35
  class HourlyInterval:
36
+ """
37
+ Runs scheduled item hourly. To set the hourly interval, you create an
38
+ instance of the HourlyInterval class and assign the following values:
39
+ start_time, end_time, and interval_value. To set the start_time and
40
+ end_time, assign the time value using this syntax: start_time=time(hour, minute)
41
+ and end_time=time(hour, minute). The hour is specified in 24 hour time.
42
+ The interval_value specifies how often the to run the task within the
43
+ start and end time. The options are expressed in hours. For example,
44
+ interval_value=.25 is every 15 minutes. The values are .25, .5, 1, 2, 4, 6,
45
+ 8, 12. Hourly schedules that run more frequently than every 60 minutes must
46
+ have start and end times that are on the hour.
47
+ """
48
+
29
49
  def __init__(self, start_time, end_time, interval_value):
30
50
  self.start_time = start_time
31
51
  self.end_time = end_time
@@ -109,6 +129,12 @@ class HourlyInterval:
109
129
 
110
130
 
111
131
  class DailyInterval:
132
+ """
133
+ Runs the scheduled item daily. To set the daily interval, you create an
134
+ instance of the DailyInterval and assign the start_time. The start time uses
135
+ the syntax start_time=time(hour, minute).
136
+ """
137
+
112
138
  def __init__(self, start_time, *interval_values):
113
139
  self.start_time = start_time
114
140
  self.interval = interval_values
@@ -177,6 +203,15 @@ class DailyInterval:
177
203
 
178
204
 
179
205
  class WeeklyInterval:
206
+ """
207
+ Runs the scheduled item once a week. To set the weekly interval, you create
208
+ an instance of the WeeklyInterval and assign the start time and multiple
209
+ instances for the interval_value (days of week and start time). The start
210
+ time uses the syntax time(hour, minute). The interval_value is the day of
211
+ the week, expressed as a IntervalItem. For example
212
+ TSC.IntervalItem.Day.Monday for Monday.
213
+ """
214
+
180
215
  def __init__(self, start_time, *interval_values):
181
216
  self.start_time = start_time
182
217
  self.interval = interval_values
@@ -214,6 +249,11 @@ class WeeklyInterval:
214
249
 
215
250
 
216
251
  class MonthlyInterval:
252
+ """
253
+ Runs the scheduled item once a month. To set the monthly interval, you
254
+ create an instance of the MonthlyInterval and assign the start time and day.
255
+ """
256
+
217
257
  def __init__(self, start_time, interval_value):
218
258
  self.start_time = start_time
219
259
 
@@ -265,6 +305,7 @@ class MonthlyInterval:
265
305
  "Fourth",
266
306
  "Fifth",
267
307
  "Last",
308
+ "Customized Monthly",
268
309
  ]
269
310
  for value in range(1, 32):
270
311
  VALID_INTERVALS.append(str(value))
@@ -278,4 +319,4 @@ class MonthlyInterval:
278
319
  self._interval = interval_values
279
320
 
280
321
  def _interval_type_pairs(self):
281
- return [(IntervalItem.Occurrence.MonthDay, self.interval)]
322
+ return [(IntervalItem.Occurrence.MonthDay, str(day)) for day in self.interval]
@@ -0,0 +1,53 @@
1
+ from typing import Optional
2
+ import xml.etree.ElementTree as ET
3
+
4
+
5
+ class LocationItem:
6
+ """
7
+ Details of where an item is located, such as a personal space or project.
8
+
9
+ Attributes
10
+ ----------
11
+ id : str | None
12
+ The ID of the location.
13
+
14
+ type : str | None
15
+ The type of location, such as PersonalSpace or Project.
16
+
17
+ name : str | None
18
+ The name of the location.
19
+ """
20
+
21
+ class Type:
22
+ PersonalSpace = "PersonalSpace"
23
+ Project = "Project"
24
+
25
+ def __init__(self):
26
+ self._id: Optional[str] = None
27
+ self._type: Optional[str] = None
28
+ self._name: Optional[str] = None
29
+
30
+ def __repr__(self):
31
+ return f"{self.__class__.__name__}({self.__dict__!r})"
32
+
33
+ @property
34
+ def id(self) -> Optional[str]:
35
+ return self._id
36
+
37
+ @property
38
+ def type(self) -> Optional[str]:
39
+ return self._type
40
+
41
+ @property
42
+ def name(self) -> Optional[str]:
43
+ return self._name
44
+
45
+ @classmethod
46
+ def from_xml(cls, xml: ET.Element, ns: Optional[dict] = None) -> "LocationItem":
47
+ if ns is None:
48
+ ns = {}
49
+ location = cls()
50
+ location._id = xml.get("id", None)
51
+ location._type = xml.get("type", None)
52
+ location._name = xml.get("name", None)
53
+ return location
models/oidc_item.py ADDED
@@ -0,0 +1,82 @@
1
+ from typing import Optional
2
+ from defusedxml.ElementTree import fromstring
3
+
4
+
5
+ class SiteOIDCConfiguration:
6
+ def __init__(self) -> None:
7
+ self.enabled: bool = False
8
+ self.test_login_url: Optional[str] = None
9
+ self.known_provider_alias: Optional[str] = None
10
+ self.allow_embedded_authentication: bool = False
11
+ self.use_full_name: bool = False
12
+ self.idp_configuration_name: Optional[str] = None
13
+ self.idp_configuration_id: Optional[str] = None
14
+ self.client_id: Optional[str] = None
15
+ self.client_secret: Optional[str] = None
16
+ self.authorization_endpoint: Optional[str] = None
17
+ self.token_endpoint: Optional[str] = None
18
+ self.userinfo_endpoint: Optional[str] = None
19
+ self.jwks_uri: Optional[str] = None
20
+ self.end_session_endpoint: Optional[str] = None
21
+ self.custom_scope: Optional[str] = None
22
+ self.essential_acr_values: Optional[str] = None
23
+ self.email_mapping: Optional[str] = None
24
+ self.first_name_mapping: Optional[str] = None
25
+ self.last_name_mapping: Optional[str] = None
26
+ self.full_name_mapping: Optional[str] = None
27
+ self.prompt: Optional[str] = None
28
+ self.client_authentication: Optional[str] = None
29
+ self.voluntary_acr_values: Optional[str] = None
30
+
31
+ def __str__(self) -> str:
32
+ return (
33
+ f"{self.__class__.__qualname__}(enabled={self.enabled}, "
34
+ f"test_login_url={self.test_login_url}, "
35
+ f"idp_configuration_name={self.idp_configuration_name}, "
36
+ f"idp_configuration_id={self.idp_configuration_id}, "
37
+ f"client_id={self.client_id})"
38
+ )
39
+
40
+ def __repr__(self) -> str:
41
+ return f"<{str(self)}>"
42
+
43
+ @classmethod
44
+ def from_response(cls, raw_xml: bytes, ns) -> "SiteOIDCConfiguration":
45
+ """
46
+ Parses the raw XML bytes and returns a SiteOIDCConfiguration object.
47
+ """
48
+ root = fromstring(raw_xml)
49
+ elem = root.find("t:siteOIDCConfiguration", namespaces=ns)
50
+ if elem is None:
51
+ raise ValueError("No siteOIDCConfiguration element found in the XML.")
52
+ config = cls()
53
+
54
+ config.enabled = str_to_bool(elem.get("enabled", "false"))
55
+ config.test_login_url = elem.get("testLoginUrl")
56
+ config.known_provider_alias = elem.get("knownProviderAlias")
57
+ config.allow_embedded_authentication = str_to_bool(elem.get("allowEmbeddedAuthentication", "false").lower())
58
+ config.use_full_name = str_to_bool(elem.get("useFullName", "false").lower())
59
+ config.idp_configuration_name = elem.get("idpConfigurationName")
60
+ config.idp_configuration_id = elem.get("idpConfigurationId")
61
+ config.client_id = elem.get("clientId")
62
+ config.client_secret = elem.get("clientSecret")
63
+ config.authorization_endpoint = elem.get("authorizationEndpoint")
64
+ config.token_endpoint = elem.get("tokenEndpoint")
65
+ config.userinfo_endpoint = elem.get("userinfoEndpoint")
66
+ config.jwks_uri = elem.get("jwksUri")
67
+ config.end_session_endpoint = elem.get("endSessionEndpoint")
68
+ config.custom_scope = elem.get("customScope")
69
+ config.essential_acr_values = elem.get("essentialAcrValues")
70
+ config.email_mapping = elem.get("emailMapping")
71
+ config.first_name_mapping = elem.get("firstNameMapping")
72
+ config.last_name_mapping = elem.get("lastNameMapping")
73
+ config.full_name_mapping = elem.get("fullNameMapping")
74
+ config.prompt = elem.get("prompt")
75
+ config.client_authentication = elem.get("clientAuthentication")
76
+ config.voluntary_acr_values = elem.get("voluntaryAcrValues")
77
+
78
+ return config
79
+
80
+
81
+ def str_to_bool(s: str) -> bool:
82
+ return s == "true"
@@ -43,6 +43,8 @@ class Permission:
43
43
  CreateRefreshMetrics = "CreateRefreshMetrics"
44
44
  SaveAs = "SaveAs"
45
45
  PulseMetricDefine = "PulseMetricDefine"
46
+ ExtractRefresh = "ExtractRefresh"
47
+ WebAuthoringForFlows = "WebAuthoringForFlows"
46
48
 
47
49
  def __repr__(self):
48
50
  return "<Enum Capability: AddComment | ChangeHierarchy | ChangePermission ... (17 more) >"