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
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
import xml.etree.ElementTree as ET
|
|
3
|
-
from typing import Optional
|
|
2
|
+
from typing import Optional, overload
|
|
4
3
|
|
|
5
4
|
from defusedxml.ElementTree import fromstring
|
|
6
5
|
|
|
7
6
|
from tableauserverclient.models.exceptions import UnpopulatedPropertyError
|
|
8
|
-
from tableauserverclient.models.property_decorators import property_is_enum
|
|
7
|
+
from tableauserverclient.models.property_decorators import property_is_enum
|
|
8
|
+
from tableauserverclient.models.user_item import UserItem
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class ProjectItem:
|
|
@@ -39,12 +39,32 @@ class ProjectItem:
|
|
|
39
39
|
|
|
40
40
|
Attributes
|
|
41
41
|
----------
|
|
42
|
+
datasource_count : int
|
|
43
|
+
The number of data sources in the project.
|
|
44
|
+
|
|
42
45
|
id : str
|
|
43
46
|
The unique identifier for the project.
|
|
44
47
|
|
|
48
|
+
owner: Optional[UserItem]
|
|
49
|
+
The UserItem owner of the project.
|
|
50
|
+
|
|
45
51
|
owner_id : str
|
|
46
52
|
The unique identifier for the UserItem owner of the project.
|
|
47
53
|
|
|
54
|
+
project_count : int
|
|
55
|
+
The number of projects in the project.
|
|
56
|
+
|
|
57
|
+
top_level_project : bool
|
|
58
|
+
True if the project is a top-level project.
|
|
59
|
+
|
|
60
|
+
view_count : int
|
|
61
|
+
The number of views in the project.
|
|
62
|
+
|
|
63
|
+
workbook_count : int
|
|
64
|
+
The number of workbooks in the project.
|
|
65
|
+
|
|
66
|
+
writeable : bool
|
|
67
|
+
True if the project is writeable.
|
|
48
68
|
"""
|
|
49
69
|
|
|
50
70
|
ERROR_MSG = "Project item must be populated with permissions first."
|
|
@@ -66,15 +86,18 @@ class ProjectItem:
|
|
|
66
86
|
content_permissions: Optional[str] = None,
|
|
67
87
|
parent_id: Optional[str] = None,
|
|
68
88
|
samples: Optional[bool] = None,
|
|
89
|
+
id: Optional[str] = None,
|
|
69
90
|
) -> None:
|
|
70
91
|
self._content_permissions = None
|
|
71
|
-
self._id: Optional[str] =
|
|
92
|
+
self._id: Optional[str] = id
|
|
72
93
|
self.description: Optional[str] = description
|
|
73
94
|
self.name: str = name
|
|
74
95
|
self.content_permissions: Optional[str] = content_permissions
|
|
75
96
|
self.parent_id: Optional[str] = parent_id
|
|
76
97
|
self._samples: Optional[bool] = samples
|
|
77
98
|
self._owner_id: Optional[str] = None
|
|
99
|
+
self._top_level_project: Optional[bool] = None
|
|
100
|
+
self._writeable: Optional[bool] = None
|
|
78
101
|
|
|
79
102
|
self._permissions = None
|
|
80
103
|
self._default_workbook_permissions = None
|
|
@@ -87,6 +110,13 @@ class ProjectItem:
|
|
|
87
110
|
self._default_database_permissions = None
|
|
88
111
|
self._default_table_permissions = None
|
|
89
112
|
|
|
113
|
+
self._project_count: Optional[int] = None
|
|
114
|
+
self._workbook_count: Optional[int] = None
|
|
115
|
+
self._view_count: Optional[int] = None
|
|
116
|
+
self._datasource_count: Optional[int] = None
|
|
117
|
+
|
|
118
|
+
self._owner: Optional[UserItem] = None
|
|
119
|
+
|
|
90
120
|
@property
|
|
91
121
|
def content_permissions(self):
|
|
92
122
|
return self._content_permissions
|
|
@@ -165,7 +195,7 @@ class ProjectItem:
|
|
|
165
195
|
return self._name
|
|
166
196
|
|
|
167
197
|
@name.setter
|
|
168
|
-
def name(self, value: str) -> None:
|
|
198
|
+
def name(self, value: Optional[str]) -> None:
|
|
169
199
|
self._name = value
|
|
170
200
|
|
|
171
201
|
@property
|
|
@@ -176,25 +206,53 @@ class ProjectItem:
|
|
|
176
206
|
def owner_id(self, value: str) -> None:
|
|
177
207
|
self._owner_id = value
|
|
178
208
|
|
|
209
|
+
@property
|
|
210
|
+
def top_level_project(self) -> Optional[bool]:
|
|
211
|
+
return self._top_level_project
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def writeable(self) -> Optional[bool]:
|
|
215
|
+
return self._writeable
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def project_count(self) -> Optional[int]:
|
|
219
|
+
return self._project_count
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def workbook_count(self) -> Optional[int]:
|
|
223
|
+
return self._workbook_count
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def view_count(self) -> Optional[int]:
|
|
227
|
+
return self._view_count
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def datasource_count(self) -> Optional[int]:
|
|
231
|
+
return self._datasource_count
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def owner(self) -> Optional[UserItem]:
|
|
235
|
+
return self._owner
|
|
236
|
+
|
|
179
237
|
def is_default(self):
|
|
180
238
|
return self.name.lower() == "default"
|
|
181
239
|
|
|
182
|
-
def
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
240
|
+
def _set_values(
|
|
241
|
+
self,
|
|
242
|
+
project_id,
|
|
243
|
+
name,
|
|
244
|
+
description,
|
|
245
|
+
content_permissions,
|
|
246
|
+
parent_id,
|
|
247
|
+
owner_id,
|
|
248
|
+
top_level_project,
|
|
249
|
+
writeable,
|
|
250
|
+
project_count,
|
|
251
|
+
workbook_count,
|
|
252
|
+
view_count,
|
|
253
|
+
datasource_count,
|
|
254
|
+
owner,
|
|
255
|
+
):
|
|
198
256
|
if project_id is not None:
|
|
199
257
|
self._id = project_id
|
|
200
258
|
if name:
|
|
@@ -207,6 +265,20 @@ class ProjectItem:
|
|
|
207
265
|
self.parent_id = parent_id
|
|
208
266
|
if owner_id:
|
|
209
267
|
self._owner_id = owner_id
|
|
268
|
+
if project_count is not None:
|
|
269
|
+
self._project_count = project_count
|
|
270
|
+
if workbook_count is not None:
|
|
271
|
+
self._workbook_count = workbook_count
|
|
272
|
+
if view_count is not None:
|
|
273
|
+
self._view_count = view_count
|
|
274
|
+
if datasource_count is not None:
|
|
275
|
+
self._datasource_count = datasource_count
|
|
276
|
+
if top_level_project is not None:
|
|
277
|
+
self._top_level_project = top_level_project
|
|
278
|
+
if writeable is not None:
|
|
279
|
+
self._writeable = writeable
|
|
280
|
+
if owner is not None:
|
|
281
|
+
self._owner = owner
|
|
210
282
|
|
|
211
283
|
def _set_permissions(self, permissions):
|
|
212
284
|
self._permissions = permissions
|
|
@@ -220,31 +292,71 @@ class ProjectItem:
|
|
|
220
292
|
)
|
|
221
293
|
|
|
222
294
|
@classmethod
|
|
223
|
-
def from_response(cls, resp, ns) -> list["ProjectItem"]:
|
|
295
|
+
def from_response(cls, resp: bytes, ns: Optional[dict]) -> list["ProjectItem"]:
|
|
224
296
|
all_project_items = list()
|
|
225
297
|
parsed_response = fromstring(resp)
|
|
226
298
|
all_project_xml = parsed_response.findall(".//t:project", namespaces=ns)
|
|
227
299
|
|
|
228
300
|
for project_xml in all_project_xml:
|
|
229
|
-
project_item = cls.from_xml(project_xml)
|
|
301
|
+
project_item = cls.from_xml(project_xml, namespace=ns)
|
|
230
302
|
all_project_items.append(project_item)
|
|
231
303
|
return all_project_items
|
|
232
304
|
|
|
233
305
|
@classmethod
|
|
234
|
-
def from_xml(cls, project_xml, namespace=None) -> "ProjectItem":
|
|
306
|
+
def from_xml(cls, project_xml: ET.Element, namespace: Optional[dict] = None) -> "ProjectItem":
|
|
235
307
|
project_item = cls()
|
|
236
|
-
project_item._set_values(*cls._parse_element(project_xml))
|
|
308
|
+
project_item._set_values(*cls._parse_element(project_xml, namespace))
|
|
237
309
|
return project_item
|
|
238
310
|
|
|
239
311
|
@staticmethod
|
|
240
|
-
def _parse_element(project_xml):
|
|
312
|
+
def _parse_element(project_xml: ET.Element, namespace: Optional[dict]) -> tuple:
|
|
241
313
|
id = project_xml.get("id", None)
|
|
242
314
|
name = project_xml.get("name", None)
|
|
243
315
|
description = project_xml.get("description", None)
|
|
244
316
|
content_permissions = project_xml.get("contentPermissions", None)
|
|
245
317
|
parent_id = project_xml.get("parentProjectId", None)
|
|
318
|
+
top_level_project = str_to_bool(project_xml.get("topLevelProject", None))
|
|
319
|
+
writeable = str_to_bool(project_xml.get("writeable", None))
|
|
246
320
|
owner_id = None
|
|
247
|
-
|
|
248
|
-
|
|
321
|
+
owner = None
|
|
322
|
+
if (owner_elem := project_xml.find(".//t:owner", namespaces=namespace)) is not None:
|
|
323
|
+
owner = UserItem.from_xml(owner_elem, namespace)
|
|
324
|
+
owner_id = owner_elem.get("id", None)
|
|
325
|
+
|
|
326
|
+
project_count = None
|
|
327
|
+
workbook_count = None
|
|
328
|
+
view_count = None
|
|
329
|
+
datasource_count = None
|
|
330
|
+
if (count_elem := project_xml.find(".//t:contentsCounts", namespaces=namespace)) is not None:
|
|
331
|
+
project_count = int(count_elem.get("projectCount", 0))
|
|
332
|
+
workbook_count = int(count_elem.get("workbookCount", 0))
|
|
333
|
+
view_count = int(count_elem.get("viewCount", 0))
|
|
334
|
+
datasource_count = int(count_elem.get("dataSourceCount", 0))
|
|
335
|
+
|
|
336
|
+
return (
|
|
337
|
+
id,
|
|
338
|
+
name,
|
|
339
|
+
description,
|
|
340
|
+
content_permissions,
|
|
341
|
+
parent_id,
|
|
342
|
+
owner_id,
|
|
343
|
+
top_level_project,
|
|
344
|
+
writeable,
|
|
345
|
+
project_count,
|
|
346
|
+
workbook_count,
|
|
347
|
+
view_count,
|
|
348
|
+
datasource_count,
|
|
349
|
+
owner,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
@overload
|
|
354
|
+
def str_to_bool(value: str) -> bool: ...
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@overload
|
|
358
|
+
def str_to_bool(value: None) -> None: ...
|
|
359
|
+
|
|
249
360
|
|
|
250
|
-
|
|
361
|
+
def str_to_bool(value):
|
|
362
|
+
return value.lower() == "true" if value is not None else None
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import re
|
|
3
3
|
from functools import wraps
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any
|
|
5
5
|
from collections.abc import Container
|
|
6
6
|
|
|
7
7
|
from tableauserverclient.datetime_helpers import parse_datetime
|
|
@@ -67,7 +67,7 @@ def property_is_valid_time(func):
|
|
|
67
67
|
return wrapper
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
def property_is_int(range: tuple[int, int], allowed:
|
|
70
|
+
def property_is_int(range: tuple[int, int], allowed: Container[Any] | None = None):
|
|
71
71
|
"""Takes a range of ints and a list of exemptions to check against
|
|
72
72
|
when setting a property on a model. The range is a tuple of (min, max) and the
|
|
73
73
|
allowed list (empty by default) allows values outside that range.
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
from typing_extensions import Self
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
class ResourceReference:
|
|
2
|
-
def __init__(self, id_, tag_name):
|
|
5
|
+
def __init__(self, id_: str | None, tag_name: str) -> None:
|
|
3
6
|
self.id = id_
|
|
4
7
|
self.tag_name = tag_name
|
|
5
8
|
|
|
6
|
-
def __str__(self):
|
|
9
|
+
def __str__(self) -> str:
|
|
7
10
|
return f"<ResourceReference id={self._id} tag={self._tag_name}>"
|
|
8
11
|
|
|
9
12
|
__repr__ = __str__
|
|
@@ -13,18 +16,21 @@ class ResourceReference:
|
|
|
13
16
|
return False
|
|
14
17
|
return (self.id == other.id) and (self.tag_name == other.tag_name)
|
|
15
18
|
|
|
19
|
+
def __hash__(self: Self) -> int:
|
|
20
|
+
return hash((self.id, self.tag_name))
|
|
21
|
+
|
|
16
22
|
@property
|
|
17
|
-
def id(self):
|
|
23
|
+
def id(self) -> str | None:
|
|
18
24
|
return self._id
|
|
19
25
|
|
|
20
26
|
@id.setter
|
|
21
|
-
def id(self, value):
|
|
27
|
+
def id(self, value: str | None) -> None:
|
|
22
28
|
self._id = value
|
|
23
29
|
|
|
24
30
|
@property
|
|
25
|
-
def tag_name(self):
|
|
31
|
+
def tag_name(self) -> str:
|
|
26
32
|
return self._tag_name
|
|
27
33
|
|
|
28
34
|
@tag_name.setter
|
|
29
|
-
def tag_name(self, value):
|
|
35
|
+
def tag_name(self, value: str) -> None:
|
|
30
36
|
self._tag_name = value
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import xml.etree.ElementTree as ET
|
|
2
2
|
from datetime import datetime
|
|
3
|
-
from typing import Optional, Union
|
|
3
|
+
from typing import Optional, Union, TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from defusedxml.ElementTree import fromstring
|
|
6
6
|
|
|
@@ -16,10 +16,71 @@ from .property_decorators import (
|
|
|
16
16
|
property_is_enum,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from requests import Response
|
|
21
|
+
|
|
22
|
+
|
|
19
23
|
Interval = Union[HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval]
|
|
20
24
|
|
|
21
25
|
|
|
22
26
|
class ScheduleItem:
|
|
27
|
+
"""
|
|
28
|
+
Using the TSC library, you can schedule extract refresh or subscription
|
|
29
|
+
tasks on Tableau Server. You can also get and update information about the
|
|
30
|
+
scheduled tasks, or delete scheduled tasks.
|
|
31
|
+
|
|
32
|
+
If you have the identifier of the job, you can use the TSC library to find
|
|
33
|
+
out the status of the asynchronous job.
|
|
34
|
+
|
|
35
|
+
The schedule properties are defined in the ScheduleItem class. The class
|
|
36
|
+
corresponds to the properties for schedules you can access in Tableau
|
|
37
|
+
Server or by using the Tableau Server REST API. The Schedule methods are
|
|
38
|
+
based upon the endpoints for jobs in the REST API and operate on the JobItem
|
|
39
|
+
class.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
name : str
|
|
44
|
+
The name of the schedule.
|
|
45
|
+
|
|
46
|
+
priority : int
|
|
47
|
+
The priority of the schedule. Lower values represent higher priority,
|
|
48
|
+
with 0 indicating the highest priority.
|
|
49
|
+
|
|
50
|
+
schedule_type : str
|
|
51
|
+
The type of task schedule. See ScheduleItem.Type for the possible values.
|
|
52
|
+
|
|
53
|
+
execution_order : str
|
|
54
|
+
Specifies how the scheduled tasks should run. The choices are Parallel
|
|
55
|
+
which uses all avaiable background processes for a scheduled task, or
|
|
56
|
+
Serial, which limits the schedule to one background process.
|
|
57
|
+
|
|
58
|
+
interval_item : Interval
|
|
59
|
+
Specifies the frequency that the scheduled task should run. The
|
|
60
|
+
interval_item is an instance of the IntervalItem class. The
|
|
61
|
+
interval_item has properties for frequency (hourly, daily, weekly,
|
|
62
|
+
monthly), and what time and date the scheduled item runs. You set this
|
|
63
|
+
value by declaring an IntervalItem object that is one of the following:
|
|
64
|
+
HourlyInterval, DailyInterval, WeeklyInterval, or MonthlyInterval.
|
|
65
|
+
|
|
66
|
+
Attributes
|
|
67
|
+
----------
|
|
68
|
+
created_at : datetime
|
|
69
|
+
The date and time the schedule was created.
|
|
70
|
+
|
|
71
|
+
end_schedule_at : datetime
|
|
72
|
+
The date and time the schedule ends.
|
|
73
|
+
|
|
74
|
+
id : str
|
|
75
|
+
The unique identifier for the schedule.
|
|
76
|
+
|
|
77
|
+
next_run_at : datetime
|
|
78
|
+
The date and time the schedule is next run.
|
|
79
|
+
|
|
80
|
+
state : str
|
|
81
|
+
The state of the schedule. See ScheduleItem.State for the possible values.
|
|
82
|
+
"""
|
|
83
|
+
|
|
23
84
|
class Type:
|
|
24
85
|
Extract = "Extract"
|
|
25
86
|
Flow = "Flow"
|
|
@@ -350,3 +411,8 @@ class ScheduleItem:
|
|
|
350
411
|
for warning_xml in all_warning_xml:
|
|
351
412
|
warnings.append(warning_xml.get("message", None))
|
|
352
413
|
return warnings
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def parse_batch_schedule_state(response: "Response", ns) -> list[str]:
|
|
417
|
+
xml = fromstring(response.content)
|
|
418
|
+
return [text for tag in xml.findall(".//t:scheduleLuid", namespaces=ns) if (text := tag.text)]
|
|
@@ -85,6 +85,9 @@ class SiteItem:
|
|
|
85
85
|
state: str
|
|
86
86
|
Shows the current state of the site (Active or Suspended).
|
|
87
87
|
|
|
88
|
+
attribute_capture_enabled: Optional[str]
|
|
89
|
+
Enables user attributes for all Tableau Server embedding workflows.
|
|
90
|
+
|
|
88
91
|
"""
|
|
89
92
|
|
|
90
93
|
_user_quota: Optional[int] = None
|
|
@@ -164,6 +167,7 @@ class SiteItem:
|
|
|
164
167
|
time_zone=None,
|
|
165
168
|
auto_suspend_refresh_enabled: bool = True,
|
|
166
169
|
auto_suspend_refresh_inactivity_window: int = 30,
|
|
170
|
+
attribute_capture_enabled: Optional[bool] = None,
|
|
167
171
|
):
|
|
168
172
|
self._admin_mode = None
|
|
169
173
|
self._id: Optional[str] = None
|
|
@@ -217,6 +221,7 @@ class SiteItem:
|
|
|
217
221
|
self.time_zone = time_zone
|
|
218
222
|
self.auto_suspend_refresh_enabled = auto_suspend_refresh_enabled
|
|
219
223
|
self.auto_suspend_refresh_inactivity_window = auto_suspend_refresh_inactivity_window
|
|
224
|
+
self.attribute_capture_enabled = attribute_capture_enabled
|
|
220
225
|
|
|
221
226
|
@property
|
|
222
227
|
def admin_mode(self) -> Optional[str]:
|
|
@@ -720,6 +725,7 @@ class SiteItem:
|
|
|
720
725
|
time_zone,
|
|
721
726
|
auto_suspend_refresh_enabled,
|
|
722
727
|
auto_suspend_refresh_inactivity_window,
|
|
728
|
+
attribute_capture_enabled,
|
|
723
729
|
) = self._parse_element(site_xml, ns)
|
|
724
730
|
|
|
725
731
|
self._set_values(
|
|
@@ -774,6 +780,7 @@ class SiteItem:
|
|
|
774
780
|
time_zone,
|
|
775
781
|
auto_suspend_refresh_enabled,
|
|
776
782
|
auto_suspend_refresh_inactivity_window,
|
|
783
|
+
attribute_capture_enabled,
|
|
777
784
|
)
|
|
778
785
|
return self
|
|
779
786
|
|
|
@@ -830,6 +837,7 @@ class SiteItem:
|
|
|
830
837
|
time_zone,
|
|
831
838
|
auto_suspend_refresh_enabled,
|
|
832
839
|
auto_suspend_refresh_inactivity_window,
|
|
840
|
+
attribute_capture_enabled,
|
|
833
841
|
):
|
|
834
842
|
if id is not None:
|
|
835
843
|
self._id = id
|
|
@@ -937,6 +945,7 @@ class SiteItem:
|
|
|
937
945
|
self.auto_suspend_refresh_enabled = auto_suspend_refresh_enabled
|
|
938
946
|
if auto_suspend_refresh_inactivity_window is not None:
|
|
939
947
|
self.auto_suspend_refresh_inactivity_window = auto_suspend_refresh_inactivity_window
|
|
948
|
+
self.attribute_capture_enabled = attribute_capture_enabled
|
|
940
949
|
|
|
941
950
|
@classmethod
|
|
942
951
|
def from_response(cls, resp, ns) -> list["SiteItem"]:
|
|
@@ -996,6 +1005,7 @@ class SiteItem:
|
|
|
996
1005
|
time_zone,
|
|
997
1006
|
auto_suspend_refresh_enabled,
|
|
998
1007
|
auto_suspend_refresh_inactivity_window,
|
|
1008
|
+
attribute_capture_enabled,
|
|
999
1009
|
) = cls._parse_element(site_xml, ns)
|
|
1000
1010
|
|
|
1001
1011
|
site_item = cls(name, content_url)
|
|
@@ -1051,6 +1061,7 @@ class SiteItem:
|
|
|
1051
1061
|
time_zone,
|
|
1052
1062
|
auto_suspend_refresh_enabled,
|
|
1053
1063
|
auto_suspend_refresh_inactivity_window,
|
|
1064
|
+
attribute_capture_enabled,
|
|
1054
1065
|
)
|
|
1055
1066
|
all_site_items.append(site_item)
|
|
1056
1067
|
return all_site_items
|
|
@@ -1132,6 +1143,9 @@ class SiteItem:
|
|
|
1132
1143
|
|
|
1133
1144
|
flows_enabled = string_to_bool(site_xml.get("flowsEnabled", ""))
|
|
1134
1145
|
cataloging_enabled = string_to_bool(site_xml.get("catalogingEnabled", ""))
|
|
1146
|
+
attribute_capture_enabled = (
|
|
1147
|
+
string_to_bool(ace) if (ace := site_xml.get("attributeCaptureEnabled")) is not None else None
|
|
1148
|
+
)
|
|
1135
1149
|
|
|
1136
1150
|
return (
|
|
1137
1151
|
id,
|
|
@@ -1185,8 +1199,48 @@ class SiteItem:
|
|
|
1185
1199
|
time_zone,
|
|
1186
1200
|
auto_suspend_refresh_enabled,
|
|
1187
1201
|
auto_suspend_refresh_inactivity_window,
|
|
1202
|
+
attribute_capture_enabled,
|
|
1203
|
+
)
|
|
1204
|
+
|
|
1205
|
+
|
|
1206
|
+
class SiteAuthConfiguration:
|
|
1207
|
+
"""
|
|
1208
|
+
Authentication configuration for a site.
|
|
1209
|
+
"""
|
|
1210
|
+
|
|
1211
|
+
def __init__(self):
|
|
1212
|
+
self.auth_setting: Optional[str] = None
|
|
1213
|
+
self.enabled: Optional[bool] = None
|
|
1214
|
+
self.idp_configuration_id: Optional[str] = None
|
|
1215
|
+
self.idp_configuration_name: Optional[str] = None
|
|
1216
|
+
self.known_provider_alias: Optional[str] = None
|
|
1217
|
+
|
|
1218
|
+
@classmethod
|
|
1219
|
+
def from_response(cls, resp: bytes, ns: dict) -> list["SiteAuthConfiguration"]:
|
|
1220
|
+
all_auth_configs = list()
|
|
1221
|
+
parsed_response = fromstring(resp)
|
|
1222
|
+
all_auth_xml = parsed_response.findall(".//t:siteAuthConfiguration", namespaces=ns)
|
|
1223
|
+
for auth_xml in all_auth_xml:
|
|
1224
|
+
auth_config = cls()
|
|
1225
|
+
auth_config.auth_setting = auth_xml.get("authSetting", None)
|
|
1226
|
+
auth_config.enabled = string_to_bool(auth_xml.get("enabled", ""))
|
|
1227
|
+
auth_config.idp_configuration_id = auth_xml.get("idpConfigurationId", None)
|
|
1228
|
+
auth_config.idp_configuration_name = auth_xml.get("idpConfigurationName", None)
|
|
1229
|
+
auth_config.known_provider_alias = auth_xml.get("knownProviderAlias", None)
|
|
1230
|
+
all_auth_configs.append(auth_config)
|
|
1231
|
+
return all_auth_configs
|
|
1232
|
+
|
|
1233
|
+
def __str__(self):
|
|
1234
|
+
return (
|
|
1235
|
+
f"{self.__class__.__qualname__}(auth_setting={self.auth_setting}, "
|
|
1236
|
+
f"enabled={self.enabled}, "
|
|
1237
|
+
f"idp_configuration_id={self.idp_configuration_id}, "
|
|
1238
|
+
f"idp_configuration_name={self.idp_configuration_name})"
|
|
1188
1239
|
)
|
|
1189
1240
|
|
|
1241
|
+
def __repr__(self):
|
|
1242
|
+
return f"<{str(self)}>"
|
|
1243
|
+
|
|
1190
1244
|
|
|
1191
1245
|
# Used to convert string represented boolean to a boolean type
|
|
1192
1246
|
def string_to_bool(s: str) -> bool:
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
from typing import Callable, Optional, TYPE_CHECKING
|
|
1
2
|
from defusedxml.ElementTree import fromstring
|
|
2
3
|
|
|
3
4
|
from .exceptions import UnpopulatedPropertyError
|
|
4
5
|
from .property_decorators import property_not_empty, property_is_boolean
|
|
5
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from tableauserverclient.models import DQWItem
|
|
9
|
+
|
|
6
10
|
|
|
7
11
|
class TableItem:
|
|
8
12
|
def __init__(self, name, description=None):
|
|
@@ -40,7 +44,7 @@ class TableItem:
|
|
|
40
44
|
return self._data_quality_warnings()
|
|
41
45
|
|
|
42
46
|
@property
|
|
43
|
-
def id(self):
|
|
47
|
+
def id(self) -> Optional[str]:
|
|
44
48
|
return self._id
|
|
45
49
|
|
|
46
50
|
@property
|
|
@@ -100,8 +104,8 @@ class TableItem:
|
|
|
100
104
|
def _set_columns(self, columns):
|
|
101
105
|
self._columns = columns
|
|
102
106
|
|
|
103
|
-
def _set_data_quality_warnings(self,
|
|
104
|
-
self._data_quality_warnings =
|
|
107
|
+
def _set_data_quality_warnings(self, dqw: Callable[[], list["DQWItem"]]) -> None:
|
|
108
|
+
self._data_quality_warnings = dqw
|
|
105
109
|
|
|
106
110
|
def _set_values(self, table_values):
|
|
107
111
|
if "id" in table_values:
|
|
@@ -87,7 +87,7 @@ class TableauAuth(Credentials):
|
|
|
87
87
|
uid = f", user_id_to_impersonate=f{self.user_id_to_impersonate}"
|
|
88
88
|
else:
|
|
89
89
|
uid = ""
|
|
90
|
-
return f"<
|
|
90
|
+
return f"<{self.__class__.__qualname__} username={self.username} password=redacted (site={self.site_id}{uid})>"
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
# A Tableau-generated Personal Access Token
|
|
@@ -155,8 +155,8 @@ class PersonalAccessTokenAuth(Credentials):
|
|
|
155
155
|
else:
|
|
156
156
|
uid = ""
|
|
157
157
|
return (
|
|
158
|
-
f"<
|
|
159
|
-
f"
|
|
158
|
+
f"<{self.__class__.__qualname__}(name={self.token_name} token={self.personal_access_token[:2]}..."
|
|
159
|
+
f"site={self.site_id}{uid}) >"
|
|
160
160
|
)
|
|
161
161
|
|
|
162
162
|
|
|
@@ -198,19 +198,26 @@ class JWTAuth(Credentials):
|
|
|
198
198
|
|
|
199
199
|
"""
|
|
200
200
|
|
|
201
|
-
def __init__(
|
|
201
|
+
def __init__(
|
|
202
|
+
self,
|
|
203
|
+
jwt: str,
|
|
204
|
+
isUat: bool = False,
|
|
205
|
+
site_id: Optional[str] = None,
|
|
206
|
+
user_id_to_impersonate: Optional[str] = None,
|
|
207
|
+
) -> None:
|
|
202
208
|
if jwt is None:
|
|
203
209
|
raise TabError("Must provide a JWT token when using JWT authentication")
|
|
204
210
|
super().__init__(site_id, user_id_to_impersonate)
|
|
205
211
|
self.jwt = jwt
|
|
212
|
+
self.isUat = isUat
|
|
206
213
|
|
|
207
214
|
@property
|
|
208
215
|
def credentials(self) -> dict[str, str]:
|
|
209
|
-
return {"jwt": self.jwt}
|
|
216
|
+
return {"jwt": self.jwt, "isUat": str(self.isUat).lower()}
|
|
210
217
|
|
|
211
218
|
def __repr__(self):
|
|
212
219
|
if self.user_id_to_impersonate:
|
|
213
220
|
uid = f", user_id_to_impersonate=f{self.user_id_to_impersonate}"
|
|
214
221
|
else:
|
|
215
222
|
uid = ""
|
|
216
|
-
return f"<{self.__class__.__qualname__} jwt={self.jwt[:5]}... (site={self.site_id}{uid})>"
|
|
223
|
+
return f"<{self.__class__.__qualname__} jwt={self.jwt[:5]}... isUat={self.isUat} (site={self.site_id}{uid})>"
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from typing import Union
|
|
2
2
|
|
|
3
|
+
from tableauserverclient.models.database_item import DatabaseItem
|
|
3
4
|
from tableauserverclient.models.datasource_item import DatasourceItem
|
|
4
5
|
from tableauserverclient.models.flow_item import FlowItem
|
|
5
6
|
from tableauserverclient.models.project_item import ProjectItem
|
|
7
|
+
from tableauserverclient.models.table_item import TableItem
|
|
6
8
|
from tableauserverclient.models.view_item import ViewItem
|
|
7
9
|
from tableauserverclient.models.workbook_item import WorkbookItem
|
|
8
10
|
from tableauserverclient.models.metric_item import MetricItem
|
|
@@ -25,7 +27,17 @@ class Resource:
|
|
|
25
27
|
|
|
26
28
|
# resource types that have permissions, can be renamed, etc
|
|
27
29
|
# todo: refactoring: should actually define TableauItem as an interface and let all these implement it
|
|
28
|
-
TableauItem = Union[
|
|
30
|
+
TableauItem = Union[
|
|
31
|
+
DatasourceItem,
|
|
32
|
+
FlowItem,
|
|
33
|
+
MetricItem,
|
|
34
|
+
ProjectItem,
|
|
35
|
+
ViewItem,
|
|
36
|
+
WorkbookItem,
|
|
37
|
+
VirtualConnectionItem,
|
|
38
|
+
DatabaseItem,
|
|
39
|
+
TableItem,
|
|
40
|
+
]
|
|
29
41
|
|
|
30
42
|
|
|
31
43
|
def plural_type(content_type: Union[Resource, str]) -> str:
|