tableauserverclient 0.33__py3-none-any.whl → 0.35__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/__init__.py +33 -23
- tableauserverclient/{_version.py → bin/_version.py} +3 -3
- tableauserverclient/config.py +5 -3
- tableauserverclient/models/column_item.py +1 -1
- tableauserverclient/models/connection_credentials.py +18 -2
- tableauserverclient/models/connection_item.py +44 -6
- tableauserverclient/models/custom_view_item.py +78 -11
- tableauserverclient/models/data_acceleration_report_item.py +2 -2
- tableauserverclient/models/data_alert_item.py +5 -5
- tableauserverclient/models/data_freshness_policy_item.py +6 -6
- tableauserverclient/models/database_item.py +3 -3
- tableauserverclient/models/datasource_item.py +10 -10
- tableauserverclient/models/dqw_item.py +1 -1
- tableauserverclient/models/favorites_item.py +5 -6
- tableauserverclient/models/fileupload_item.py +1 -1
- tableauserverclient/models/flow_item.py +54 -9
- tableauserverclient/models/flow_run_item.py +3 -3
- tableauserverclient/models/group_item.py +44 -4
- tableauserverclient/models/groupset_item.py +4 -4
- tableauserverclient/models/interval_item.py +9 -9
- tableauserverclient/models/job_item.py +73 -8
- tableauserverclient/models/linked_tasks_item.py +5 -5
- tableauserverclient/models/metric_item.py +5 -5
- tableauserverclient/models/pagination_item.py +1 -1
- tableauserverclient/models/permissions_item.py +12 -10
- tableauserverclient/models/project_item.py +73 -19
- tableauserverclient/models/property_decorators.py +12 -11
- tableauserverclient/models/reference_item.py +2 -2
- tableauserverclient/models/revision_item.py +3 -3
- tableauserverclient/models/schedule_item.py +2 -2
- tableauserverclient/models/server_info_item.py +26 -6
- tableauserverclient/models/site_item.py +69 -3
- tableauserverclient/models/subscription_item.py +3 -3
- tableauserverclient/models/table_item.py +1 -1
- tableauserverclient/models/tableau_auth.py +115 -5
- tableauserverclient/models/tableau_types.py +2 -2
- tableauserverclient/models/tag_item.py +3 -4
- tableauserverclient/models/task_item.py +34 -4
- tableauserverclient/models/user_item.py +47 -17
- tableauserverclient/models/view_item.py +66 -13
- tableauserverclient/models/virtual_connection_item.py +6 -5
- tableauserverclient/models/webhook_item.py +39 -6
- tableauserverclient/models/workbook_item.py +116 -13
- tableauserverclient/namespace.py +1 -1
- tableauserverclient/server/__init__.py +2 -1
- tableauserverclient/server/endpoint/auth_endpoint.py +69 -10
- tableauserverclient/server/endpoint/custom_views_endpoint.py +258 -29
- tableauserverclient/server/endpoint/data_acceleration_report_endpoint.py +2 -2
- tableauserverclient/server/endpoint/data_alert_endpoint.py +14 -14
- tableauserverclient/server/endpoint/databases_endpoint.py +13 -12
- tableauserverclient/server/endpoint/datasources_endpoint.py +61 -62
- tableauserverclient/server/endpoint/default_permissions_endpoint.py +19 -18
- tableauserverclient/server/endpoint/dqw_endpoint.py +9 -9
- tableauserverclient/server/endpoint/endpoint.py +19 -21
- tableauserverclient/server/endpoint/exceptions.py +23 -7
- tableauserverclient/server/endpoint/favorites_endpoint.py +31 -31
- tableauserverclient/server/endpoint/fileuploads_endpoint.py +9 -11
- tableauserverclient/server/endpoint/flow_runs_endpoint.py +15 -13
- tableauserverclient/server/endpoint/flow_task_endpoint.py +2 -2
- tableauserverclient/server/endpoint/flows_endpoint.py +344 -29
- tableauserverclient/server/endpoint/groups_endpoint.py +342 -27
- tableauserverclient/server/endpoint/groupsets_endpoint.py +2 -2
- tableauserverclient/server/endpoint/jobs_endpoint.py +116 -7
- tableauserverclient/server/endpoint/linked_tasks_endpoint.py +2 -2
- tableauserverclient/server/endpoint/metadata_endpoint.py +2 -2
- tableauserverclient/server/endpoint/metrics_endpoint.py +10 -10
- tableauserverclient/server/endpoint/permissions_endpoint.py +13 -15
- tableauserverclient/server/endpoint/projects_endpoint.py +681 -30
- tableauserverclient/server/endpoint/resource_tagger.py +14 -13
- tableauserverclient/server/endpoint/schedules_endpoint.py +17 -18
- tableauserverclient/server/endpoint/server_info_endpoint.py +40 -5
- tableauserverclient/server/endpoint/sites_endpoint.py +282 -17
- tableauserverclient/server/endpoint/subscriptions_endpoint.py +10 -10
- tableauserverclient/server/endpoint/tables_endpoint.py +15 -14
- tableauserverclient/server/endpoint/tasks_endpoint.py +86 -8
- tableauserverclient/server/endpoint/users_endpoint.py +366 -19
- tableauserverclient/server/endpoint/views_endpoint.py +262 -20
- tableauserverclient/server/endpoint/virtual_connections_endpoint.py +6 -5
- tableauserverclient/server/endpoint/webhooks_endpoint.py +88 -11
- tableauserverclient/server/endpoint/workbooks_endpoint.py +653 -65
- tableauserverclient/server/filter.py +2 -2
- tableauserverclient/server/pager.py +29 -6
- tableauserverclient/server/query.py +68 -19
- tableauserverclient/server/request_factory.py +57 -37
- tableauserverclient/server/request_options.py +243 -141
- tableauserverclient/server/server.py +76 -10
- tableauserverclient/server/sort.py +16 -2
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.35.dist-info}/METADATA +7 -7
- tableauserverclient-0.35.dist-info/RECORD +106 -0
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.35.dist-info}/WHEEL +1 -1
- tableauserverclient-0.33.dist-info/RECORD +0 -106
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.35.dist-info}/LICENSE +0 -0
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.35.dist-info}/LICENSE.versioneer +0 -0
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.35.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from .request_options import RequestOptions
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
class Filter
|
|
4
|
+
class Filter:
|
|
5
5
|
def __init__(self, field, operator, value):
|
|
6
6
|
self.field = field
|
|
7
7
|
self.operator = operator
|
|
@@ -16,7 +16,7 @@ class Filter(object):
|
|
|
16
16
|
# to [<string1>,<string2>]
|
|
17
17
|
# so effectively, remove any spaces between "," and "'" and then remove all "'"
|
|
18
18
|
value_string = value_string.replace(", '", ",'").replace("'", "")
|
|
19
|
-
return "{
|
|
19
|
+
return f"{self.field}:{self.operator}:{value_string}"
|
|
20
20
|
|
|
21
21
|
@property
|
|
22
22
|
def value(self):
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
from functools import partial
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Optional, Protocol, TypeVar, Union, runtime_checkable
|
|
4
|
+
from collections.abc import Iterable, Iterator
|
|
4
5
|
|
|
5
6
|
from tableauserverclient.models.pagination_item import PaginationItem
|
|
6
7
|
from tableauserverclient.server.request_options import RequestOptions
|
|
@@ -11,14 +12,12 @@ T = TypeVar("T")
|
|
|
11
12
|
|
|
12
13
|
@runtime_checkable
|
|
13
14
|
class Endpoint(Protocol[T]):
|
|
14
|
-
def get(self, req_options: Optional[RequestOptions]) ->
|
|
15
|
-
...
|
|
15
|
+
def get(self, req_options: Optional[RequestOptions]) -> tuple[list[T], PaginationItem]: ...
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
@runtime_checkable
|
|
19
19
|
class CallableEndpoint(Protocol[T]):
|
|
20
|
-
def __call__(self, __req_options: Optional[RequestOptions], **kwargs) ->
|
|
21
|
-
...
|
|
20
|
+
def __call__(self, __req_options: Optional[RequestOptions], **kwargs) -> tuple[list[T], PaginationItem]: ...
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
class Pager(Iterable[T]):
|
|
@@ -27,7 +26,31 @@ class Pager(Iterable[T]):
|
|
|
27
26
|
Supports all `RequestOptions` including starting on any page. Also used by models to load sub-models
|
|
28
27
|
(users in a group, views in a workbook, etc) by passing a different endpoint.
|
|
29
28
|
|
|
30
|
-
Will loop over anything that returns (
|
|
29
|
+
Will loop over anything that returns (list[ModelItem], PaginationItem).
|
|
30
|
+
|
|
31
|
+
Will make a copy of the `RequestOptions` object passed in so it can be reused.
|
|
32
|
+
|
|
33
|
+
Makes a call to the Server for each page of items, then yields each item in the list.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
endpoint: CallableEndpoint[T] or Endpoint[T]
|
|
38
|
+
The endpoint to call to get the items. Can be a callable or an Endpoint object.
|
|
39
|
+
Expects a tuple of (list[T], PaginationItem) to be returned.
|
|
40
|
+
|
|
41
|
+
request_opts: RequestOptions, optional
|
|
42
|
+
The request options to pass to the endpoint. If not provided, will use default RequestOptions.
|
|
43
|
+
Filters, sorts, page size, starting page number, etc can be set here.
|
|
44
|
+
|
|
45
|
+
Yields
|
|
46
|
+
------
|
|
47
|
+
T
|
|
48
|
+
The items returned from the endpoint.
|
|
49
|
+
|
|
50
|
+
Raises
|
|
51
|
+
------
|
|
52
|
+
ValueError
|
|
53
|
+
If the endpoint is not a callable or an Endpoint object.
|
|
31
54
|
"""
|
|
32
55
|
|
|
33
56
|
def __init__(
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
from collections.abc import Sized
|
|
1
|
+
from collections.abc import Iterable, Iterator, Sized
|
|
2
2
|
from itertools import count
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Optional, Protocol, TYPE_CHECKING, TypeVar, overload
|
|
4
|
+
import sys
|
|
4
5
|
from tableauserverclient.config import config
|
|
5
6
|
from tableauserverclient.models.pagination_item import PaginationItem
|
|
7
|
+
from tableauserverclient.server.endpoint.exceptions import ServerResponseError
|
|
6
8
|
from tableauserverclient.server.filter import Filter
|
|
7
9
|
from tableauserverclient.server.request_options import RequestOptions
|
|
8
10
|
from tableauserverclient.server.sort import Sort
|
|
@@ -34,10 +36,36 @@ see pagination_sample
|
|
|
34
36
|
|
|
35
37
|
|
|
36
38
|
class QuerySet(Iterable[T], Sized):
|
|
39
|
+
"""
|
|
40
|
+
QuerySet is a class that allows easy filtering, sorting, and iterating over
|
|
41
|
+
many endpoints in TableauServerClient. It is designed to be used in a similar
|
|
42
|
+
way to Django QuerySets, but with a more limited feature set.
|
|
43
|
+
|
|
44
|
+
QuerySet is an iterable, and can be used in for loops, list comprehensions,
|
|
45
|
+
and other places where iterables are expected.
|
|
46
|
+
|
|
47
|
+
QuerySet is also Sized, and can be used in places where the length of the
|
|
48
|
+
QuerySet is needed. The length of the QuerySet is the total number of items
|
|
49
|
+
available in the QuerySet, not just the number of items that have been
|
|
50
|
+
fetched. If the endpoint does not return a total count of items, the length
|
|
51
|
+
of the QuerySet will be sys.maxsize. If there is no total count, the
|
|
52
|
+
QuerySet will continue to fetch items until there are no more items to
|
|
53
|
+
fetch.
|
|
54
|
+
|
|
55
|
+
QuerySet is not re-entrant. It is not designed to be used in multiple places
|
|
56
|
+
at the same time. If you need to use a QuerySet in multiple places, you
|
|
57
|
+
should create a new QuerySet for each place you need to use it, convert it
|
|
58
|
+
to a list, or create a deep copy of the QuerySet.
|
|
59
|
+
|
|
60
|
+
QuerySets are also indexable, and can be sliced. If you try to access an
|
|
61
|
+
index that has not been fetched, the QuerySet will fetch the page that
|
|
62
|
+
contains the item you are looking for.
|
|
63
|
+
"""
|
|
64
|
+
|
|
37
65
|
def __init__(self, model: "QuerysetEndpoint[T]", page_size: Optional[int] = None) -> None:
|
|
38
66
|
self.model = model
|
|
39
67
|
self.request_options = RequestOptions(pagesize=page_size or config.PAGE_SIZE)
|
|
40
|
-
self._result_cache:
|
|
68
|
+
self._result_cache: list[T] = []
|
|
41
69
|
self._pagination_item = PaginationItem()
|
|
42
70
|
|
|
43
71
|
def __iter__(self: Self) -> Iterator[T]:
|
|
@@ -49,19 +77,30 @@ class QuerySet(Iterable[T], Sized):
|
|
|
49
77
|
for page in count(1):
|
|
50
78
|
self.request_options.pagenumber = page
|
|
51
79
|
self._result_cache = []
|
|
52
|
-
self.
|
|
80
|
+
self._pagination_item._page_number = None
|
|
81
|
+
try:
|
|
82
|
+
self._fetch_all()
|
|
83
|
+
except ServerResponseError as e:
|
|
84
|
+
if e.code == "400006":
|
|
85
|
+
# If the endpoint does not support pagination, it will end
|
|
86
|
+
# up overrunning the total number of pages. Catch the
|
|
87
|
+
# error and break out of the loop.
|
|
88
|
+
raise StopIteration
|
|
89
|
+
if len(self._result_cache) == 0:
|
|
90
|
+
return
|
|
53
91
|
yield from self._result_cache
|
|
54
|
-
#
|
|
55
|
-
|
|
92
|
+
# If the length of the QuerySet is unknown, continue fetching until
|
|
93
|
+
# the result cache is empty.
|
|
94
|
+
if (size := len(self)) == 0:
|
|
95
|
+
continue
|
|
96
|
+
if (page * self.page_size) >= size:
|
|
56
97
|
return
|
|
57
98
|
|
|
58
99
|
@overload
|
|
59
|
-
def __getitem__(self: Self, k: Slice) ->
|
|
60
|
-
...
|
|
100
|
+
def __getitem__(self: Self, k: Slice) -> list[T]: ...
|
|
61
101
|
|
|
62
102
|
@overload
|
|
63
|
-
def __getitem__(self: Self, k: int) -> T:
|
|
64
|
-
...
|
|
103
|
+
def __getitem__(self: Self, k: int) -> T: ...
|
|
65
104
|
|
|
66
105
|
def __getitem__(self, k):
|
|
67
106
|
page = self.page_number
|
|
@@ -103,6 +142,7 @@ class QuerySet(Iterable[T], Sized):
|
|
|
103
142
|
elif k in range(self.total_available):
|
|
104
143
|
# Otherwise, check if k is even sensible to return
|
|
105
144
|
self._result_cache = []
|
|
145
|
+
self._pagination_item._page_number = None
|
|
106
146
|
# Add one to k, otherwise it gets stuck at page boundaries, e.g. 100
|
|
107
147
|
self.request_options.pagenumber = max(1, math.ceil((k + 1) / size))
|
|
108
148
|
return self[k]
|
|
@@ -114,11 +154,16 @@ class QuerySet(Iterable[T], Sized):
|
|
|
114
154
|
"""
|
|
115
155
|
Retrieve the data and store result and pagination item in cache
|
|
116
156
|
"""
|
|
117
|
-
if not self._result_cache:
|
|
118
|
-
|
|
157
|
+
if not self._result_cache and self._pagination_item._page_number is None:
|
|
158
|
+
response = self.model.get(self.request_options)
|
|
159
|
+
if isinstance(response, tuple):
|
|
160
|
+
self._result_cache, self._pagination_item = response
|
|
161
|
+
else:
|
|
162
|
+
self._result_cache = response
|
|
163
|
+
self._pagination_item = PaginationItem()
|
|
119
164
|
|
|
120
165
|
def __len__(self: Self) -> int:
|
|
121
|
-
return self.total_available
|
|
166
|
+
return sys.maxsize if self.total_available is None else self.total_available
|
|
122
167
|
|
|
123
168
|
@property
|
|
124
169
|
def total_available(self: Self) -> int:
|
|
@@ -128,12 +173,16 @@ class QuerySet(Iterable[T], Sized):
|
|
|
128
173
|
@property
|
|
129
174
|
def page_number(self: Self) -> int:
|
|
130
175
|
self._fetch_all()
|
|
131
|
-
|
|
176
|
+
# If the PaginationItem is not returned from the endpoint, use the
|
|
177
|
+
# pagenumber from the RequestOptions.
|
|
178
|
+
return self._pagination_item.page_number or self.request_options.pagenumber
|
|
132
179
|
|
|
133
180
|
@property
|
|
134
181
|
def page_size(self: Self) -> int:
|
|
135
182
|
self._fetch_all()
|
|
136
|
-
|
|
183
|
+
# If the PaginationItem is not returned from the endpoint, use the
|
|
184
|
+
# pagesize from the RequestOptions.
|
|
185
|
+
return self._pagination_item.page_size or self.request_options.pagesize
|
|
137
186
|
|
|
138
187
|
def filter(self: Self, *invalid, page_size: Optional[int] = None, **kwargs) -> Self:
|
|
139
188
|
if invalid:
|
|
@@ -160,22 +209,22 @@ class QuerySet(Iterable[T], Sized):
|
|
|
160
209
|
return self
|
|
161
210
|
|
|
162
211
|
@staticmethod
|
|
163
|
-
def _parse_shorthand_filter(key: str) ->
|
|
212
|
+
def _parse_shorthand_filter(key: str) -> tuple[str, str]:
|
|
164
213
|
tokens = key.split("__", 1)
|
|
165
214
|
if len(tokens) == 1:
|
|
166
215
|
operator = RequestOptions.Operator.Equals
|
|
167
216
|
else:
|
|
168
217
|
operator = tokens[1]
|
|
169
218
|
if operator not in RequestOptions.Operator.__dict__.values():
|
|
170
|
-
raise ValueError("Operator `{}` is not valid."
|
|
219
|
+
raise ValueError(f"Operator `{operator}` is not valid.")
|
|
171
220
|
|
|
172
221
|
field = to_camel_case(tokens[0])
|
|
173
222
|
if field not in RequestOptions.Field.__dict__.values():
|
|
174
|
-
raise ValueError("Field name `{}` is not valid."
|
|
223
|
+
raise ValueError(f"Field name `{field}` is not valid.")
|
|
175
224
|
return (field, operator)
|
|
176
225
|
|
|
177
226
|
@staticmethod
|
|
178
|
-
def _parse_shorthand_sort(key: str) ->
|
|
227
|
+
def _parse_shorthand_sort(key: str) -> tuple[str, str]:
|
|
179
228
|
direction = RequestOptions.Direction.Asc
|
|
180
229
|
if key.startswith("-"):
|
|
181
230
|
direction = RequestOptions.Direction.Desc
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import xml.etree.ElementTree as ET
|
|
2
|
-
from typing import Any, Callable,
|
|
2
|
+
from typing import Any, Callable, Optional, TypeVar, TYPE_CHECKING, Union
|
|
3
|
+
from collections.abc import Iterable
|
|
3
4
|
|
|
4
5
|
from typing_extensions import ParamSpec
|
|
5
6
|
|
|
@@ -15,7 +16,7 @@ if TYPE_CHECKING:
|
|
|
15
16
|
# this file could be largely replaced if we were willing to import the huge file from generateDS
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
def _add_multipart(parts:
|
|
19
|
+
def _add_multipart(parts: dict) -> tuple[Any, str]:
|
|
19
20
|
mime_multipart_parts = list()
|
|
20
21
|
for name, (filename, data, content_type) in parts.items():
|
|
21
22
|
multipart_part = RequestField(name=name, data=data, filename=filename)
|
|
@@ -80,7 +81,7 @@ def _add_credentials_element(parent_element, connection_credentials):
|
|
|
80
81
|
credentials_element.attrib["oAuth"] = "true"
|
|
81
82
|
|
|
82
83
|
|
|
83
|
-
class AuthRequest
|
|
84
|
+
class AuthRequest:
|
|
84
85
|
def signin_req(self, auth_item):
|
|
85
86
|
xml_request = ET.Element("tsRequest")
|
|
86
87
|
|
|
@@ -104,7 +105,7 @@ class AuthRequest(object):
|
|
|
104
105
|
return ET.tostring(xml_request)
|
|
105
106
|
|
|
106
107
|
|
|
107
|
-
class ColumnRequest
|
|
108
|
+
class ColumnRequest:
|
|
108
109
|
def update_req(self, column_item):
|
|
109
110
|
xml_request = ET.Element("tsRequest")
|
|
110
111
|
column_element = ET.SubElement(xml_request, "column")
|
|
@@ -115,7 +116,7 @@ class ColumnRequest(object):
|
|
|
115
116
|
return ET.tostring(xml_request)
|
|
116
117
|
|
|
117
118
|
|
|
118
|
-
class DataAlertRequest
|
|
119
|
+
class DataAlertRequest:
|
|
119
120
|
def add_user_to_alert(self, alert_item: "DataAlertItem", user_id: str) -> bytes:
|
|
120
121
|
xml_request = ET.Element("tsRequest")
|
|
121
122
|
user_element = ET.SubElement(xml_request, "user")
|
|
@@ -140,7 +141,7 @@ class DataAlertRequest(object):
|
|
|
140
141
|
return ET.tostring(xml_request)
|
|
141
142
|
|
|
142
143
|
|
|
143
|
-
class DatabaseRequest
|
|
144
|
+
class DatabaseRequest:
|
|
144
145
|
def update_req(self, database_item):
|
|
145
146
|
xml_request = ET.Element("tsRequest")
|
|
146
147
|
database_element = ET.SubElement(xml_request, "database")
|
|
@@ -159,7 +160,7 @@ class DatabaseRequest(object):
|
|
|
159
160
|
return ET.tostring(xml_request)
|
|
160
161
|
|
|
161
162
|
|
|
162
|
-
class DatasourceRequest
|
|
163
|
+
class DatasourceRequest:
|
|
163
164
|
def _generate_xml(self, datasource_item: DatasourceItem, connection_credentials=None, connections=None):
|
|
164
165
|
xml_request = ET.Element("tsRequest")
|
|
165
166
|
datasource_element = ET.SubElement(xml_request, "datasource")
|
|
@@ -244,7 +245,7 @@ class DatasourceRequest(object):
|
|
|
244
245
|
return _add_multipart(parts)
|
|
245
246
|
|
|
246
247
|
|
|
247
|
-
class DQWRequest
|
|
248
|
+
class DQWRequest:
|
|
248
249
|
def add_req(self, dqw_item):
|
|
249
250
|
xml_request = ET.Element("tsRequest")
|
|
250
251
|
dqw_element = ET.SubElement(xml_request, "dataQualityWarning")
|
|
@@ -274,7 +275,7 @@ class DQWRequest(object):
|
|
|
274
275
|
return ET.tostring(xml_request)
|
|
275
276
|
|
|
276
277
|
|
|
277
|
-
class FavoriteRequest
|
|
278
|
+
class FavoriteRequest:
|
|
278
279
|
def add_request(self, id_: Optional[str], target_type: str, label: Optional[str]) -> bytes:
|
|
279
280
|
"""
|
|
280
281
|
<favorite label="...">
|
|
@@ -329,7 +330,7 @@ class FavoriteRequest(object):
|
|
|
329
330
|
return self.add_request(id_, Resource.Workbook, name)
|
|
330
331
|
|
|
331
332
|
|
|
332
|
-
class FileuploadRequest
|
|
333
|
+
class FileuploadRequest:
|
|
333
334
|
def chunk_req(self, chunk):
|
|
334
335
|
parts = {
|
|
335
336
|
"request_payload": ("", "", "text/xml"),
|
|
@@ -338,8 +339,8 @@ class FileuploadRequest(object):
|
|
|
338
339
|
return _add_multipart(parts)
|
|
339
340
|
|
|
340
341
|
|
|
341
|
-
class FlowRequest
|
|
342
|
-
def _generate_xml(self, flow_item: "FlowItem", connections: Optional[
|
|
342
|
+
class FlowRequest:
|
|
343
|
+
def _generate_xml(self, flow_item: "FlowItem", connections: Optional[list["ConnectionItem"]] = None) -> bytes:
|
|
343
344
|
xml_request = ET.Element("tsRequest")
|
|
344
345
|
flow_element = ET.SubElement(xml_request, "flow")
|
|
345
346
|
if flow_item.name is not None:
|
|
@@ -370,8 +371,8 @@ class FlowRequest(object):
|
|
|
370
371
|
flow_item: "FlowItem",
|
|
371
372
|
filename: str,
|
|
372
373
|
file_contents: bytes,
|
|
373
|
-
connections: Optional[
|
|
374
|
-
) ->
|
|
374
|
+
connections: Optional[list["ConnectionItem"]] = None,
|
|
375
|
+
) -> tuple[Any, str]:
|
|
375
376
|
xml_request = self._generate_xml(flow_item, connections)
|
|
376
377
|
|
|
377
378
|
parts = {
|
|
@@ -380,14 +381,14 @@ class FlowRequest(object):
|
|
|
380
381
|
}
|
|
381
382
|
return _add_multipart(parts)
|
|
382
383
|
|
|
383
|
-
def publish_req_chunked(self, flow_item, connections=None) ->
|
|
384
|
+
def publish_req_chunked(self, flow_item, connections=None) -> tuple[Any, str]:
|
|
384
385
|
xml_request = self._generate_xml(flow_item, connections)
|
|
385
386
|
|
|
386
387
|
parts = {"request_payload": ("", xml_request, "text/xml")}
|
|
387
388
|
return _add_multipart(parts)
|
|
388
389
|
|
|
389
390
|
|
|
390
|
-
class GroupRequest
|
|
391
|
+
class GroupRequest:
|
|
391
392
|
def add_user_req(self, user_id: str) -> bytes:
|
|
392
393
|
xml_request = ET.Element("tsRequest")
|
|
393
394
|
user_element = ET.SubElement(xml_request, "user")
|
|
@@ -477,7 +478,7 @@ class GroupRequest(object):
|
|
|
477
478
|
return ET.tostring(xml_request)
|
|
478
479
|
|
|
479
480
|
|
|
480
|
-
class PermissionRequest
|
|
481
|
+
class PermissionRequest:
|
|
481
482
|
def add_req(self, rules: Iterable[PermissionsRule]) -> bytes:
|
|
482
483
|
xml_request = ET.Element("tsRequest")
|
|
483
484
|
permissions_element = ET.SubElement(xml_request, "permissions")
|
|
@@ -499,7 +500,7 @@ class PermissionRequest(object):
|
|
|
499
500
|
capability_element.attrib["mode"] = mode
|
|
500
501
|
|
|
501
502
|
|
|
502
|
-
class ProjectRequest
|
|
503
|
+
class ProjectRequest:
|
|
503
504
|
def update_req(self, project_item: "ProjectItem") -> bytes:
|
|
504
505
|
xml_request = ET.Element("tsRequest")
|
|
505
506
|
project_element = ET.SubElement(xml_request, "project")
|
|
@@ -530,7 +531,7 @@ class ProjectRequest(object):
|
|
|
530
531
|
return ET.tostring(xml_request)
|
|
531
532
|
|
|
532
533
|
|
|
533
|
-
class ScheduleRequest
|
|
534
|
+
class ScheduleRequest:
|
|
534
535
|
def create_req(self, schedule_item):
|
|
535
536
|
xml_request = ET.Element("tsRequest")
|
|
536
537
|
schedule_element = ET.SubElement(xml_request, "schedule")
|
|
@@ -609,7 +610,7 @@ class ScheduleRequest(object):
|
|
|
609
610
|
return self._add_to_req(id_, "flow", task_type)
|
|
610
611
|
|
|
611
612
|
|
|
612
|
-
class SiteRequest
|
|
613
|
+
class SiteRequest:
|
|
613
614
|
def update_req(self, site_item: "SiteItem", parent_srv: Optional["Server"] = None):
|
|
614
615
|
xml_request = ET.Element("tsRequest")
|
|
615
616
|
site_element = ET.SubElement(xml_request, "site")
|
|
@@ -848,7 +849,7 @@ class SiteRequest(object):
|
|
|
848
849
|
warnings.warn("In version 3.10 and earlier there is only one option: FlowsEnabled")
|
|
849
850
|
|
|
850
851
|
|
|
851
|
-
class TableRequest
|
|
852
|
+
class TableRequest:
|
|
852
853
|
def update_req(self, table_item):
|
|
853
854
|
xml_request = ET.Element("tsRequest")
|
|
854
855
|
table_element = ET.SubElement(xml_request, "table")
|
|
@@ -871,7 +872,7 @@ class TableRequest(object):
|
|
|
871
872
|
content_types = Iterable[Union["ColumnItem", "DatabaseItem", "DatasourceItem", "FlowItem", "TableItem", "WorkbookItem"]]
|
|
872
873
|
|
|
873
874
|
|
|
874
|
-
class TagRequest
|
|
875
|
+
class TagRequest:
|
|
875
876
|
def add_req(self, tag_set):
|
|
876
877
|
xml_request = ET.Element("tsRequest")
|
|
877
878
|
tags_element = ET.SubElement(xml_request, "tags")
|
|
@@ -881,7 +882,7 @@ class TagRequest(object):
|
|
|
881
882
|
return ET.tostring(xml_request)
|
|
882
883
|
|
|
883
884
|
@_tsrequest_wrapped
|
|
884
|
-
def batch_create(self, element: ET.Element, tags:
|
|
885
|
+
def batch_create(self, element: ET.Element, tags: set[str], content: content_types) -> bytes:
|
|
885
886
|
tag_batch = ET.SubElement(element, "tagBatch")
|
|
886
887
|
tags_element = ET.SubElement(tag_batch, "tags")
|
|
887
888
|
for tag in tags:
|
|
@@ -897,7 +898,7 @@ class TagRequest(object):
|
|
|
897
898
|
return ET.tostring(element)
|
|
898
899
|
|
|
899
900
|
|
|
900
|
-
class UserRequest
|
|
901
|
+
class UserRequest:
|
|
901
902
|
def update_req(self, user_item: UserItem, password: Optional[str]) -> bytes:
|
|
902
903
|
xml_request = ET.Element("tsRequest")
|
|
903
904
|
user_element = ET.SubElement(xml_request, "user")
|
|
@@ -931,7 +932,7 @@ class UserRequest(object):
|
|
|
931
932
|
return ET.tostring(xml_request)
|
|
932
933
|
|
|
933
934
|
|
|
934
|
-
class WorkbookRequest
|
|
935
|
+
class WorkbookRequest:
|
|
935
936
|
def _generate_xml(
|
|
936
937
|
self,
|
|
937
938
|
workbook_item,
|
|
@@ -957,9 +958,15 @@ class WorkbookRequest(object):
|
|
|
957
958
|
views_element = ET.SubElement(workbook_element, "views")
|
|
958
959
|
for view_name in workbook_item.hidden_views:
|
|
959
960
|
_add_hiddenview_element(views_element, view_name)
|
|
961
|
+
|
|
962
|
+
if workbook_item.thumbnails_user_id is not None:
|
|
963
|
+
workbook_element.attrib["thumbnailsUserId"] = workbook_item.thumbnails_user_id
|
|
964
|
+
elif workbook_item.thumbnails_group_id is not None:
|
|
965
|
+
workbook_element.attrib["thumbnailsGroupId"] = workbook_item.thumbnails_group_id
|
|
966
|
+
|
|
960
967
|
return ET.tostring(xml_request)
|
|
961
968
|
|
|
962
|
-
def update_req(self, workbook_item):
|
|
969
|
+
def update_req(self, workbook_item, parent_srv: Optional["Server"] = None):
|
|
963
970
|
xml_request = ET.Element("tsRequest")
|
|
964
971
|
workbook_element = ET.SubElement(xml_request, "workbook")
|
|
965
972
|
if workbook_item.name:
|
|
@@ -972,6 +979,12 @@ class WorkbookRequest(object):
|
|
|
972
979
|
if workbook_item.owner_id:
|
|
973
980
|
owner_element = ET.SubElement(workbook_element, "owner")
|
|
974
981
|
owner_element.attrib["id"] = workbook_item.owner_id
|
|
982
|
+
if (
|
|
983
|
+
workbook_item.description is not None
|
|
984
|
+
and parent_srv is not None
|
|
985
|
+
and parent_srv.check_at_least_version("3.21")
|
|
986
|
+
):
|
|
987
|
+
workbook_element.attrib["description"] = workbook_item.description
|
|
975
988
|
if workbook_item._views is not None:
|
|
976
989
|
views_element = ET.SubElement(workbook_element, "views")
|
|
977
990
|
for view in workbook_item.views:
|
|
@@ -995,9 +1008,9 @@ class WorkbookRequest(object):
|
|
|
995
1008
|
if data_freshness_policy_config.option == "FreshEvery":
|
|
996
1009
|
if data_freshness_policy_config.fresh_every_schedule is not None:
|
|
997
1010
|
fresh_every_element = ET.SubElement(data_freshness_policy_element, "freshEverySchedule")
|
|
998
|
-
fresh_every_element.attrib[
|
|
999
|
-
|
|
1000
|
-
|
|
1011
|
+
fresh_every_element.attrib["frequency"] = (
|
|
1012
|
+
data_freshness_policy_config.fresh_every_schedule.frequency
|
|
1013
|
+
)
|
|
1001
1014
|
fresh_every_element.attrib["value"] = str(data_freshness_policy_config.fresh_every_schedule.value)
|
|
1002
1015
|
else:
|
|
1003
1016
|
raise ValueError(f"data_freshness_policy_config.fresh_every_schedule must be populated.")
|
|
@@ -1075,7 +1088,7 @@ class WorkbookRequest(object):
|
|
|
1075
1088
|
datasource_element.attrib["id"] = id_
|
|
1076
1089
|
|
|
1077
1090
|
|
|
1078
|
-
class Connection
|
|
1091
|
+
class Connection:
|
|
1079
1092
|
@_tsrequest_wrapped
|
|
1080
1093
|
def update_req(self, xml_request: ET.Element, connection_item: "ConnectionItem") -> None:
|
|
1081
1094
|
connection_element = ET.SubElement(xml_request, "connection")
|
|
@@ -1098,12 +1111,19 @@ class Connection(object):
|
|
|
1098
1111
|
connection_element.attrib["queryTaggingEnabled"] = str(connection_item.query_tagging).lower()
|
|
1099
1112
|
|
|
1100
1113
|
|
|
1101
|
-
class TaskRequest
|
|
1114
|
+
class TaskRequest:
|
|
1102
1115
|
@_tsrequest_wrapped
|
|
1103
1116
|
def run_req(self, xml_request: ET.Element, task_item: Any) -> None:
|
|
1104
1117
|
# Send an empty tsRequest
|
|
1105
1118
|
pass
|
|
1106
1119
|
|
|
1120
|
+
@_tsrequest_wrapped
|
|
1121
|
+
def refresh_req(self, xml_request: ET.Element, incremental: bool = False) -> bytes:
|
|
1122
|
+
task_element = ET.SubElement(xml_request, "extractRefresh")
|
|
1123
|
+
if incremental:
|
|
1124
|
+
task_element.attrib["incremental"] = "true"
|
|
1125
|
+
return ET.tostring(xml_request)
|
|
1126
|
+
|
|
1107
1127
|
@_tsrequest_wrapped
|
|
1108
1128
|
def create_extract_req(self, xml_request: ET.Element, extract_item: "TaskItem") -> bytes:
|
|
1109
1129
|
extract_element = ET.SubElement(xml_request, "extractRefresh")
|
|
@@ -1137,7 +1157,7 @@ class TaskRequest(object):
|
|
|
1137
1157
|
return ET.tostring(xml_request)
|
|
1138
1158
|
|
|
1139
1159
|
|
|
1140
|
-
class FlowTaskRequest
|
|
1160
|
+
class FlowTaskRequest:
|
|
1141
1161
|
@_tsrequest_wrapped
|
|
1142
1162
|
def create_flow_task_req(self, xml_request: ET.Element, flow_item: "TaskItem") -> bytes:
|
|
1143
1163
|
flow_element = ET.SubElement(xml_request, "runFlow")
|
|
@@ -1171,7 +1191,7 @@ class FlowTaskRequest(object):
|
|
|
1171
1191
|
return ET.tostring(xml_request)
|
|
1172
1192
|
|
|
1173
1193
|
|
|
1174
|
-
class SubscriptionRequest
|
|
1194
|
+
class SubscriptionRequest:
|
|
1175
1195
|
@_tsrequest_wrapped
|
|
1176
1196
|
def create_req(self, xml_request: ET.Element, subscription_item: "SubscriptionItem") -> bytes:
|
|
1177
1197
|
subscription_element = ET.SubElement(xml_request, "subscription")
|
|
@@ -1235,13 +1255,13 @@ class SubscriptionRequest(object):
|
|
|
1235
1255
|
return ET.tostring(xml_request)
|
|
1236
1256
|
|
|
1237
1257
|
|
|
1238
|
-
class EmptyRequest
|
|
1258
|
+
class EmptyRequest:
|
|
1239
1259
|
@_tsrequest_wrapped
|
|
1240
1260
|
def empty_req(self, xml_request: ET.Element) -> None:
|
|
1241
1261
|
pass
|
|
1242
1262
|
|
|
1243
1263
|
|
|
1244
|
-
class WebhookRequest
|
|
1264
|
+
class WebhookRequest:
|
|
1245
1265
|
@_tsrequest_wrapped
|
|
1246
1266
|
def create_req(self, xml_request: ET.Element, webhook_item: "WebhookItem") -> bytes:
|
|
1247
1267
|
webhook = ET.SubElement(xml_request, "webhook")
|
|
@@ -1287,7 +1307,7 @@ class MetricRequest:
|
|
|
1287
1307
|
return ET.tostring(xml_request)
|
|
1288
1308
|
|
|
1289
1309
|
|
|
1290
|
-
class CustomViewRequest
|
|
1310
|
+
class CustomViewRequest:
|
|
1291
1311
|
@_tsrequest_wrapped
|
|
1292
1312
|
def update_req(self, xml_request: ET.Element, custom_view_item: CustomViewItem):
|
|
1293
1313
|
updating_element = ET.SubElement(xml_request, "customView")
|
|
@@ -1415,7 +1435,7 @@ class VirtualConnectionRequest:
|
|
|
1415
1435
|
return ET.tostring(xml_request)
|
|
1416
1436
|
|
|
1417
1437
|
|
|
1418
|
-
class RequestFactory
|
|
1438
|
+
class RequestFactory:
|
|
1419
1439
|
Auth = AuthRequest()
|
|
1420
1440
|
Connection = Connection()
|
|
1421
1441
|
Column = ColumnRequest()
|