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.
- tableauserverclient/bin/_version.py → _version.py +3 -3
- bin/__init__.py +3 -0
- bin/_version.py +21 -0
- {tableauserverclient/helpers → helpers}/strings.py +25 -1
- {tableauserverclient/models → models}/__init__.py +15 -1
- models/collection_item.py +52 -0
- {tableauserverclient/models → models}/connection_item.py +16 -2
- {tableauserverclient/models → models}/custom_view_item.py +8 -0
- {tableauserverclient/models → models}/data_freshness_policy_item.py +3 -3
- {tableauserverclient/models → models}/datasource_item.py +113 -3
- models/extensions_item.py +186 -0
- models/extract_item.py +82 -0
- {tableauserverclient/models → models}/favorites_item.py +21 -8
- {tableauserverclient/models → models}/flow_item.py +3 -3
- {tableauserverclient/models → models}/group_item.py +18 -1
- {tableauserverclient/models → models}/groupset_item.py +14 -0
- {tableauserverclient/models → models}/interval_item.py +42 -1
- models/location_item.py +53 -0
- models/oidc_item.py +82 -0
- {tableauserverclient/models → models}/permissions_item.py +2 -0
- {tableauserverclient/models → models}/project_item.py +141 -29
- {tableauserverclient/models → models}/property_decorators.py +2 -2
- {tableauserverclient/models → models}/reference_item.py +12 -6
- {tableauserverclient/models → models}/schedule_item.py +67 -1
- {tableauserverclient/models → models}/site_item.py +54 -0
- {tableauserverclient/models → models}/table_item.py +7 -3
- {tableauserverclient/models → models}/tableau_auth.py +13 -6
- {tableauserverclient/models → models}/tableau_types.py +13 -1
- {tableauserverclient/models → models}/user_item.py +111 -4
- {tableauserverclient/models → models}/view_item.py +79 -5
- {tableauserverclient/models → models}/workbook_item.py +153 -3
- {tableauserverclient/server → server}/endpoint/__init__.py +4 -0
- {tableauserverclient/server → server}/endpoint/databases_endpoint.py +101 -18
- {tableauserverclient/server → server}/endpoint/datasources_endpoint.py +155 -25
- {tableauserverclient/server → server}/endpoint/dqw_endpoint.py +16 -6
- {tableauserverclient/server → server}/endpoint/endpoint.py +39 -0
- server/endpoint/extensions_endpoint.py +79 -0
- {tableauserverclient/server → server}/endpoint/flow_task_endpoint.py +1 -1
- {tableauserverclient/server → server}/endpoint/flows_endpoint.py +5 -4
- server/endpoint/oidc_endpoint.py +157 -0
- {tableauserverclient/server → server}/endpoint/projects_endpoint.py +12 -0
- server/endpoint/schedules_endpoint.py +328 -0
- {tableauserverclient/server → server}/endpoint/sites_endpoint.py +18 -1
- {tableauserverclient/server → server}/endpoint/tables_endpoint.py +140 -17
- {tableauserverclient/server → server}/endpoint/users_endpoint.py +296 -10
- {tableauserverclient/server → server}/endpoint/views_endpoint.py +23 -0
- {tableauserverclient/server → server}/endpoint/workbooks_endpoint.py +124 -9
- {tableauserverclient/server → server}/query.py +36 -0
- {tableauserverclient/server → server}/request_factory.py +286 -2
- {tableauserverclient/server → server}/request_options.py +139 -3
- {tableauserverclient/server → server}/server.py +46 -0
- {tableauserverclient-0.37.dist-info → tableauserverclient-0.39.dist-info}/METADATA +5 -26
- tableauserverclient-0.39.dist-info/RECORD +107 -0
- {tableauserverclient-0.37.dist-info → tableauserverclient-0.39.dist-info}/WHEEL +1 -1
- tableauserverclient-0.39.dist-info/top_level.txt +4 -0
- tableauserverclient/__init__.py +0 -141
- tableauserverclient/config.py +0 -27
- tableauserverclient/datetime_helpers.py +0 -45
- tableauserverclient/exponential_backoff.py +0 -30
- tableauserverclient/filesys_helpers.py +0 -63
- tableauserverclient/namespace.py +0 -37
- tableauserverclient/py.typed +0 -0
- tableauserverclient/server/endpoint/schedules_endpoint.py +0 -151
- tableauserverclient-0.37.dist-info/RECORD +0 -106
- tableauserverclient-0.37.dist-info/licenses/LICENSE.versioneer +0 -7
- tableauserverclient-0.37.dist-info/top_level.txt +0 -1
- {tableauserverclient/helpers → helpers}/__init__.py +0 -0
- {tableauserverclient/helpers → helpers}/headers.py +0 -0
- {tableauserverclient/helpers → helpers}/logging.py +0 -0
- {tableauserverclient/models → models}/column_item.py +0 -0
- {tableauserverclient/models → models}/connection_credentials.py +0 -0
- {tableauserverclient/models → models}/data_acceleration_report_item.py +0 -0
- {tableauserverclient/models → models}/data_alert_item.py +0 -0
- {tableauserverclient/models → models}/database_item.py +0 -0
- {tableauserverclient/models → models}/dqw_item.py +0 -0
- {tableauserverclient/models → models}/exceptions.py +0 -0
- {tableauserverclient/models → models}/fileupload_item.py +0 -0
- {tableauserverclient/models → models}/flow_run_item.py +0 -0
- {tableauserverclient/models → models}/job_item.py +0 -0
- {tableauserverclient/models → models}/linked_tasks_item.py +0 -0
- {tableauserverclient/models → models}/metric_item.py +0 -0
- {tableauserverclient/models → models}/pagination_item.py +0 -0
- {tableauserverclient/models → models}/revision_item.py +0 -0
- {tableauserverclient/models → models}/server_info_item.py +0 -0
- {tableauserverclient/models → models}/subscription_item.py +0 -0
- {tableauserverclient/models → models}/tag_item.py +0 -0
- {tableauserverclient/models → models}/target.py +0 -0
- {tableauserverclient/models → models}/task_item.py +0 -0
- {tableauserverclient/models → models}/virtual_connection_item.py +0 -0
- {tableauserverclient/models → models}/webhook_item.py +0 -0
- {tableauserverclient/server → server}/__init__.py +0 -0
- {tableauserverclient/server → server}/endpoint/auth_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/custom_views_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/data_acceleration_report_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/data_alert_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/default_permissions_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/exceptions.py +0 -0
- {tableauserverclient/server → server}/endpoint/favorites_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/fileuploads_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/flow_runs_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/groups_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/groupsets_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/jobs_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/linked_tasks_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/metadata_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/metrics_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/permissions_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/resource_tagger.py +0 -0
- {tableauserverclient/server → server}/endpoint/server_info_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/subscriptions_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/tasks_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/virtual_connections_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/webhooks_endpoint.py +0 -0
- {tableauserverclient/server → server}/exceptions.py +0 -0
- {tableauserverclient/server → server}/filter.py +0 -0
- {tableauserverclient/server → server}/pager.py +0 -0
- {tableauserverclient/server → server}/sort.py +0 -0
- {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
|
-
|
|
17
|
-
|
|
18
|
-
list[
|
|
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,
|
|
150
|
-
self._data_quality_warnings =
|
|
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]
|
models/location_item.py
ADDED
|
@@ -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) >"
|