tableauserverclient 0.28__py3-none-any.whl → 0.30__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/_version.py +3 -3
- tableauserverclient/config.py +1 -1
- tableauserverclient/helpers/headers.py +17 -0
- tableauserverclient/helpers/strings.py +0 -2
- tableauserverclient/models/interval_item.py +96 -30
- tableauserverclient/models/project_item.py +0 -3
- tableauserverclient/models/schedule_item.py +27 -9
- tableauserverclient/models/task_item.py +18 -16
- tableauserverclient/server/endpoint/datasources_endpoint.py +3 -0
- tableauserverclient/server/endpoint/endpoint.py +9 -47
- tableauserverclient/server/endpoint/flows_endpoint.py +3 -0
- tableauserverclient/server/endpoint/tasks_endpoint.py +18 -12
- tableauserverclient/server/endpoint/workbooks_endpoint.py +6 -3
- tableauserverclient/server/request_factory.py +11 -7
- tableauserverclient/server/request_options.py +40 -0
- {tableauserverclient-0.28.dist-info → tableauserverclient-0.30.dist-info}/METADATA +2 -2
- {tableauserverclient-0.28.dist-info → tableauserverclient-0.30.dist-info}/RECORD +21 -20
- {tableauserverclient-0.28.dist-info → tableauserverclient-0.30.dist-info}/WHEEL +1 -1
- {tableauserverclient-0.28.dist-info → tableauserverclient-0.30.dist-info}/LICENSE +0 -0
- {tableauserverclient-0.28.dist-info → tableauserverclient-0.30.dist-info}/LICENSE.versioneer +0 -0
- {tableauserverclient-0.28.dist-info → tableauserverclient-0.30.dist-info}/top_level.txt +0 -0
tableauserverclient/_version.py
CHANGED
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "
|
|
11
|
+
"date": "2024-01-19T11:30:24-0800",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "0.
|
|
14
|
+
"full-revisionid": "f84d7d5302344f0a4ac369c501b122931ae7e79e",
|
|
15
|
+
"version": "0.30"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
tableauserverclient/config.py
CHANGED
|
@@ -7,7 +7,7 @@ BYTES_PER_MB = 1024 * 1024
|
|
|
7
7
|
# For when a datasource is over 64MB, break it into 5MB(standard chunk size) chunks
|
|
8
8
|
CHUNK_SIZE_MB = 5 * 10 # 5MB felt too slow, upped it to 50
|
|
9
9
|
|
|
10
|
-
DELAY_SLEEP_SECONDS =
|
|
10
|
+
DELAY_SLEEP_SECONDS = 0.1
|
|
11
11
|
|
|
12
12
|
# The maximum size of a file that can be published in a single request is 64MB
|
|
13
13
|
FILESIZE_LIMIT_MB = 64
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from urllib.parse import unquote_plus
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def fix_filename(params):
|
|
6
|
+
if "filename*" not in params:
|
|
7
|
+
return params
|
|
8
|
+
|
|
9
|
+
params = deepcopy(params)
|
|
10
|
+
filename = params["filename*"]
|
|
11
|
+
prefix = "UTF-8''"
|
|
12
|
+
if filename.startswith(prefix):
|
|
13
|
+
filename = filename[len(prefix) :]
|
|
14
|
+
|
|
15
|
+
params["filename"] = unquote_plus(filename)
|
|
16
|
+
del params["filename*"]
|
|
17
|
+
return params
|
|
@@ -9,8 +9,6 @@ from typing import TypeVar
|
|
|
9
9
|
T = TypeVar("T", str, bytes)
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
# usage: _redact_any_type("<xml workbook password= cooliothesecond>")
|
|
13
|
-
# -> b"<xml workbook password =***************">
|
|
14
12
|
def _redact_any_type(xml: T, sensitive_word: T, replacement: T, encoding=None) -> T:
|
|
15
13
|
try:
|
|
16
14
|
root = fromstring(xml)
|
|
@@ -29,7 +29,12 @@ class HourlyInterval(object):
|
|
|
29
29
|
def __init__(self, start_time, end_time, interval_value):
|
|
30
30
|
self.start_time = start_time
|
|
31
31
|
self.end_time = end_time
|
|
32
|
-
|
|
32
|
+
|
|
33
|
+
# interval should be a tuple, if it is not, assign as a tuple with single value
|
|
34
|
+
if isinstance(interval_value, tuple):
|
|
35
|
+
self.interval = interval_value
|
|
36
|
+
else:
|
|
37
|
+
self.interval = (interval_value,)
|
|
33
38
|
|
|
34
39
|
def __repr__(self):
|
|
35
40
|
return f"<{self.__class__.__name__} start={self.start_time} end={self.end_time} interval={self.interval}>"
|
|
@@ -63,25 +68,44 @@ class HourlyInterval(object):
|
|
|
63
68
|
return self._interval
|
|
64
69
|
|
|
65
70
|
@interval.setter
|
|
66
|
-
def interval(self,
|
|
67
|
-
VALID_INTERVALS = {0.25, 0.5, 1, 2, 4, 6, 8, 12}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
def interval(self, intervals):
|
|
72
|
+
VALID_INTERVALS = {0.25, 0.5, 1, 2, 4, 6, 8, 12, 24}
|
|
73
|
+
for interval in intervals:
|
|
74
|
+
# if an hourly interval is a string, then it is a weekDay interval
|
|
75
|
+
if isinstance(interval, str) and not interval.isnumeric() and not hasattr(IntervalItem.Day, interval):
|
|
76
|
+
error = "Invalid weekDay interval {}".format(interval)
|
|
77
|
+
raise ValueError(error)
|
|
71
78
|
|
|
72
|
-
|
|
79
|
+
# if an hourly interval is a number, it is an hours or minutes interval
|
|
80
|
+
if isinstance(interval, (int, float)) and float(interval) not in VALID_INTERVALS:
|
|
81
|
+
error = "Invalid interval {} not in {}".format(interval, str(VALID_INTERVALS))
|
|
82
|
+
raise ValueError(error)
|
|
83
|
+
|
|
84
|
+
self._interval = intervals
|
|
73
85
|
|
|
74
86
|
def _interval_type_pairs(self):
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
interval_type_pairs = []
|
|
88
|
+
for interval in self.interval:
|
|
89
|
+
# We use fractional hours for the two minute-based intervals.
|
|
90
|
+
# Need to convert to minutes from hours here
|
|
91
|
+
if interval in {0.25, 0.5}:
|
|
92
|
+
calculated_interval = int(interval * 60)
|
|
93
|
+
interval_type = IntervalItem.Occurrence.Minutes
|
|
94
|
+
|
|
95
|
+
interval_type_pairs.append((interval_type, str(calculated_interval)))
|
|
96
|
+
else:
|
|
97
|
+
# if the interval is a non-numeric string, it will always be a weekDay
|
|
98
|
+
if isinstance(interval, str) and not interval.isnumeric():
|
|
99
|
+
interval_type = IntervalItem.Occurrence.WeekDay
|
|
83
100
|
|
|
84
|
-
|
|
101
|
+
interval_type_pairs.append((interval_type, str(interval)))
|
|
102
|
+
# otherwise the interval is hours
|
|
103
|
+
else:
|
|
104
|
+
interval_type = IntervalItem.Occurrence.Hours
|
|
105
|
+
|
|
106
|
+
interval_type_pairs.append((interval_type, str(interval)))
|
|
107
|
+
|
|
108
|
+
return interval_type_pairs
|
|
85
109
|
|
|
86
110
|
|
|
87
111
|
class DailyInterval(object):
|
|
@@ -111,8 +135,45 @@ class DailyInterval(object):
|
|
|
111
135
|
return self._interval
|
|
112
136
|
|
|
113
137
|
@interval.setter
|
|
114
|
-
def interval(self,
|
|
115
|
-
|
|
138
|
+
def interval(self, intervals):
|
|
139
|
+
VALID_INTERVALS = {0.25, 0.5, 1, 2, 4, 6, 8, 12}
|
|
140
|
+
|
|
141
|
+
for interval in intervals:
|
|
142
|
+
# if an hourly interval is a string, then it is a weekDay interval
|
|
143
|
+
if isinstance(interval, str) and not interval.isnumeric() and not hasattr(IntervalItem.Day, interval):
|
|
144
|
+
error = "Invalid weekDay interval {}".format(interval)
|
|
145
|
+
raise ValueError(error)
|
|
146
|
+
|
|
147
|
+
# if an hourly interval is a number, it is an hours or minutes interval
|
|
148
|
+
if isinstance(interval, (int, float)) and float(interval) not in VALID_INTERVALS:
|
|
149
|
+
error = "Invalid interval {} not in {}".format(interval, str(VALID_INTERVALS))
|
|
150
|
+
raise ValueError(error)
|
|
151
|
+
|
|
152
|
+
self._interval = intervals
|
|
153
|
+
|
|
154
|
+
def _interval_type_pairs(self):
|
|
155
|
+
interval_type_pairs = []
|
|
156
|
+
for interval in self.interval:
|
|
157
|
+
# We use fractional hours for the two minute-based intervals.
|
|
158
|
+
# Need to convert to minutes from hours here
|
|
159
|
+
if interval in {0.25, 0.5}:
|
|
160
|
+
calculated_interval = int(interval * 60)
|
|
161
|
+
interval_type = IntervalItem.Occurrence.Minutes
|
|
162
|
+
|
|
163
|
+
interval_type_pairs.append((interval_type, str(calculated_interval)))
|
|
164
|
+
else:
|
|
165
|
+
# if the interval is a non-numeric string, it will always be a weekDay
|
|
166
|
+
if isinstance(interval, str) and not interval.isnumeric():
|
|
167
|
+
interval_type = IntervalItem.Occurrence.WeekDay
|
|
168
|
+
|
|
169
|
+
interval_type_pairs.append((interval_type, str(interval)))
|
|
170
|
+
# otherwise the interval is hours
|
|
171
|
+
else:
|
|
172
|
+
interval_type = IntervalItem.Occurrence.Hours
|
|
173
|
+
|
|
174
|
+
interval_type_pairs.append((interval_type, str(interval)))
|
|
175
|
+
|
|
176
|
+
return interval_type_pairs
|
|
116
177
|
|
|
117
178
|
|
|
118
179
|
class WeeklyInterval(object):
|
|
@@ -155,7 +216,12 @@ class WeeklyInterval(object):
|
|
|
155
216
|
class MonthlyInterval(object):
|
|
156
217
|
def __init__(self, start_time, interval_value):
|
|
157
218
|
self.start_time = start_time
|
|
158
|
-
|
|
219
|
+
|
|
220
|
+
# interval should be a tuple, if it is not, assign as a tuple with single value
|
|
221
|
+
if isinstance(interval_value, tuple):
|
|
222
|
+
self.interval = interval_value
|
|
223
|
+
else:
|
|
224
|
+
self.interval = (interval_value,)
|
|
159
225
|
|
|
160
226
|
def __repr__(self):
|
|
161
227
|
return f"<{self.__class__.__name__} start={self.start_time} interval={self.interval}>"
|
|
@@ -179,24 +245,24 @@ class MonthlyInterval(object):
|
|
|
179
245
|
return self._interval
|
|
180
246
|
|
|
181
247
|
@interval.setter
|
|
182
|
-
def interval(self,
|
|
183
|
-
error = "Invalid interval value for a monthly frequency: {}.".format(interval_value)
|
|
184
|
-
|
|
248
|
+
def interval(self, interval_values):
|
|
185
249
|
# This is weird because the value could be a str or an int
|
|
186
250
|
# The only valid str is 'LastDay' so we check that first. If that's not it
|
|
187
251
|
# try to convert it to an int, if that fails because it's an incorrect string
|
|
188
252
|
# like 'badstring' we catch and re-raise. Otherwise we convert to int and check
|
|
189
253
|
# that it's in range 1-31
|
|
254
|
+
for interval_value in interval_values:
|
|
255
|
+
error = "Invalid interval value for a monthly frequency: {}.".format(interval_value)
|
|
190
256
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
257
|
+
if interval_value != "LastDay":
|
|
258
|
+
try:
|
|
259
|
+
if not (1 <= int(interval_value) <= 31):
|
|
260
|
+
raise ValueError(error)
|
|
261
|
+
except ValueError:
|
|
262
|
+
if interval_value != "LastDay":
|
|
263
|
+
raise ValueError(error)
|
|
198
264
|
|
|
199
|
-
self._interval =
|
|
265
|
+
self._interval = interval_values
|
|
200
266
|
|
|
201
267
|
def _interval_type_pairs(self):
|
|
202
268
|
return [(IntervalItem.Occurrence.MonthDay, self.interval)]
|
|
@@ -163,9 +163,6 @@ class ProjectItem(object):
|
|
|
163
163
|
attr,
|
|
164
164
|
permissions,
|
|
165
165
|
)
|
|
166
|
-
fetch_call = getattr(self, attr)
|
|
167
|
-
logging.getLogger().info({"type": attr, "value": fetch_call()})
|
|
168
|
-
return fetch_call()
|
|
169
166
|
|
|
170
167
|
@classmethod
|
|
171
168
|
def from_response(cls, resp, ns) -> List["ProjectItem"]:
|
|
@@ -254,25 +254,43 @@ class ScheduleItem(object):
|
|
|
254
254
|
interval.extend(interval_elem.attrib.items())
|
|
255
255
|
|
|
256
256
|
if frequency == IntervalItem.Frequency.Daily:
|
|
257
|
-
|
|
257
|
+
converted_intervals = []
|
|
258
|
+
|
|
259
|
+
for i in interval:
|
|
260
|
+
# We use fractional hours for the two minute-based intervals.
|
|
261
|
+
# Need to convert to hours from minutes here
|
|
262
|
+
if i[0] == IntervalItem.Occurrence.Minutes:
|
|
263
|
+
converted_intervals.append(float(i[1]) / 60)
|
|
264
|
+
elif i[0] == IntervalItem.Occurrence.Hours:
|
|
265
|
+
converted_intervals.append(float(i[1]))
|
|
266
|
+
else:
|
|
267
|
+
converted_intervals.append(i[1])
|
|
268
|
+
|
|
269
|
+
return DailyInterval(start_time, *converted_intervals)
|
|
258
270
|
|
|
259
271
|
if frequency == IntervalItem.Frequency.Hourly:
|
|
260
|
-
|
|
272
|
+
converted_intervals = []
|
|
261
273
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
274
|
+
for i in interval:
|
|
275
|
+
# We use fractional hours for the two minute-based intervals.
|
|
276
|
+
# Need to convert to hours from minutes here
|
|
277
|
+
if i[0] == IntervalItem.Occurrence.Minutes:
|
|
278
|
+
converted_intervals.append(float(i[1]) / 60)
|
|
279
|
+
elif i[0] == IntervalItem.Occurrence.Hours:
|
|
280
|
+
converted_intervals.append(i[1])
|
|
281
|
+
else:
|
|
282
|
+
converted_intervals.append(i[1])
|
|
266
283
|
|
|
267
|
-
return HourlyInterval(start_time, end_time,
|
|
284
|
+
return HourlyInterval(start_time, end_time, tuple(converted_intervals))
|
|
268
285
|
|
|
269
286
|
if frequency == IntervalItem.Frequency.Weekly:
|
|
270
287
|
interval_values = [i[1] for i in interval]
|
|
271
288
|
return WeeklyInterval(start_time, *interval_values)
|
|
272
289
|
|
|
273
290
|
if frequency == IntervalItem.Frequency.Monthly:
|
|
274
|
-
|
|
275
|
-
|
|
291
|
+
interval_values = [i[1] for i in interval]
|
|
292
|
+
|
|
293
|
+
return MonthlyInterval(start_time, tuple(interval_values))
|
|
276
294
|
|
|
277
295
|
@staticmethod
|
|
278
296
|
def _parse_element(schedule_xml, ns):
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
1
4
|
from defusedxml.ElementTree import fromstring
|
|
2
5
|
|
|
3
6
|
from tableauserverclient.datetime_helpers import parse_datetime
|
|
4
|
-
from .schedule_item import ScheduleItem
|
|
5
|
-
from .target import Target
|
|
7
|
+
from tableauserverclient.models.schedule_item import ScheduleItem
|
|
8
|
+
from tableauserverclient.models.target import Target
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
class TaskItem(object):
|
|
@@ -19,14 +22,14 @@ class TaskItem(object):
|
|
|
19
22
|
|
|
20
23
|
def __init__(
|
|
21
24
|
self,
|
|
22
|
-
id_,
|
|
23
|
-
task_type,
|
|
24
|
-
priority,
|
|
25
|
-
consecutive_failed_count=0,
|
|
26
|
-
schedule_id=None,
|
|
27
|
-
schedule_item=None,
|
|
28
|
-
last_run_at=None,
|
|
29
|
-
target=None,
|
|
25
|
+
id_: str,
|
|
26
|
+
task_type: str,
|
|
27
|
+
priority: int,
|
|
28
|
+
consecutive_failed_count: int = 0,
|
|
29
|
+
schedule_id: Optional[str] = None,
|
|
30
|
+
schedule_item: Optional[ScheduleItem] = None,
|
|
31
|
+
last_run_at: Optional[datetime] = None,
|
|
32
|
+
target: Optional[Target] = None,
|
|
30
33
|
):
|
|
31
34
|
self.id = id_
|
|
32
35
|
self.task_type = task_type
|
|
@@ -37,14 +40,14 @@ class TaskItem(object):
|
|
|
37
40
|
self.last_run_at = last_run_at
|
|
38
41
|
self.target = target
|
|
39
42
|
|
|
40
|
-
def __repr__(self):
|
|
43
|
+
def __repr__(self) -> str:
|
|
41
44
|
return (
|
|
42
45
|
"<Task#{id} {task_type} pri({priority}) failed({consecutive_failed_count}) schedule_id({"
|
|
43
46
|
"schedule_id}) target({target})>".format(**self.__dict__)
|
|
44
47
|
)
|
|
45
48
|
|
|
46
49
|
@classmethod
|
|
47
|
-
def from_response(cls, xml, ns, task_type=Type.ExtractRefresh):
|
|
50
|
+
def from_response(cls, xml, ns, task_type=Type.ExtractRefresh) -> List["TaskItem"]:
|
|
48
51
|
parsed_response = fromstring(xml)
|
|
49
52
|
all_tasks_xml = parsed_response.findall(".//t:task/t:{}".format(task_type), namespaces=ns)
|
|
50
53
|
|
|
@@ -62,8 +65,7 @@ class TaskItem(object):
|
|
|
62
65
|
last_run_at_element = element.find(".//t:lastRunAt", namespaces=ns)
|
|
63
66
|
|
|
64
67
|
schedule_item_list = ScheduleItem.from_element(element, ns)
|
|
65
|
-
|
|
66
|
-
schedule_item = schedule_item_list[0]
|
|
68
|
+
schedule_item = next(iter(schedule_item_list), None)
|
|
67
69
|
|
|
68
70
|
# according to the Tableau Server REST API documentation,
|
|
69
71
|
# there should be only one of workbook or datasource
|
|
@@ -87,14 +89,14 @@ class TaskItem(object):
|
|
|
87
89
|
task_type,
|
|
88
90
|
priority,
|
|
89
91
|
consecutive_failed_count,
|
|
90
|
-
schedule_item.id,
|
|
92
|
+
schedule_item.id if schedule_item is not None else None,
|
|
91
93
|
schedule_item,
|
|
92
94
|
last_run_at,
|
|
93
95
|
target,
|
|
94
96
|
)
|
|
95
97
|
|
|
96
98
|
@staticmethod
|
|
97
|
-
def _translate_task_type(task_type):
|
|
99
|
+
def _translate_task_type(task_type: str) -> str:
|
|
98
100
|
if task_type in TaskItem._TASK_TYPE_MAPPING:
|
|
99
101
|
return TaskItem._TASK_TYPE_MAPPING[task_type]
|
|
100
102
|
else:
|
|
@@ -8,6 +8,8 @@ from contextlib import closing
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import List, Mapping, Optional, Sequence, Tuple, TYPE_CHECKING, Union
|
|
10
10
|
|
|
11
|
+
from tableauserverclient.helpers.headers import fix_filename
|
|
12
|
+
|
|
11
13
|
if TYPE_CHECKING:
|
|
12
14
|
from tableauserverclient.server import Server
|
|
13
15
|
from tableauserverclient.models import PermissionsRule
|
|
@@ -441,6 +443,7 @@ class Datasources(QuerysetEndpoint):
|
|
|
441
443
|
filepath.write(chunk)
|
|
442
444
|
return_path = filepath
|
|
443
445
|
else:
|
|
446
|
+
params = fix_filename(params)
|
|
444
447
|
filename = to_filename(os.path.basename(params["filename"]))
|
|
445
448
|
download_path = make_download_path(filepath, filename)
|
|
446
449
|
with open(download_path, "wb") as f:
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
from threading import Thread
|
|
2
|
-
from time import sleep
|
|
3
1
|
from tableauserverclient import datetime_helpers as datetime
|
|
4
2
|
|
|
5
|
-
import requests
|
|
6
3
|
from packaging.version import Version
|
|
7
4
|
from functools import wraps
|
|
8
5
|
from xml.etree.ElementTree import ParseError
|
|
@@ -76,58 +73,21 @@ class Endpoint(object):
|
|
|
76
73
|
# return explicitly for testing only
|
|
77
74
|
return parameters
|
|
78
75
|
|
|
79
|
-
def _blocking_request(self, method, url, parameters={}) -> Optional["Response"]:
|
|
80
|
-
self.async_response = None
|
|
76
|
+
def _blocking_request(self, method, url, parameters={}) -> Optional[Union["Response", Exception]]:
|
|
81
77
|
response = None
|
|
82
78
|
logger.debug("[{}] Begin blocking request to {}".format(datetime.timestamp(), url))
|
|
83
79
|
try:
|
|
84
80
|
response = method(url, **parameters)
|
|
85
|
-
self.async_response = response
|
|
86
81
|
logger.debug("[{}] Call finished".format(datetime.timestamp()))
|
|
87
82
|
except Exception as e:
|
|
88
83
|
logger.debug("Error making request to server: {}".format(e))
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if response and not self.async_response:
|
|
92
|
-
logger.debug("Request response not saved")
|
|
93
|
-
return None
|
|
94
|
-
logger.debug("[{}] Request complete".format(datetime.timestamp()))
|
|
95
|
-
return self.async_response
|
|
84
|
+
raise e
|
|
85
|
+
return response
|
|
96
86
|
|
|
97
87
|
def send_request_while_show_progress_threaded(
|
|
98
|
-
self, method, url, parameters={}, request_timeout=
|
|
99
|
-
) -> Optional["Response"]:
|
|
100
|
-
|
|
101
|
-
request_thread = Thread(target=self._blocking_request, args=(method, url, parameters))
|
|
102
|
-
request_thread.async_response = -1 # type:ignore # this is an invented attribute for thread comms
|
|
103
|
-
request_thread.start()
|
|
104
|
-
except Exception as e:
|
|
105
|
-
logger.debug("Error starting server request on separate thread: {}".format(e))
|
|
106
|
-
return None
|
|
107
|
-
seconds = 0
|
|
108
|
-
minutes = 0
|
|
109
|
-
sleep(1)
|
|
110
|
-
if self.async_response != -1:
|
|
111
|
-
# a quick return for any immediate responses
|
|
112
|
-
return self.async_response
|
|
113
|
-
while self.async_response == -1 and (request_timeout == 0 or seconds < request_timeout):
|
|
114
|
-
self.log_wait_time_then_sleep(minutes, seconds, url)
|
|
115
|
-
seconds = seconds + DELAY_SLEEP_SECONDS
|
|
116
|
-
if seconds >= 60:
|
|
117
|
-
seconds = 0
|
|
118
|
-
minutes = minutes + 1
|
|
119
|
-
return self.async_response
|
|
120
|
-
|
|
121
|
-
def log_wait_time_then_sleep(self, minutes, seconds, url):
|
|
122
|
-
logger.debug("{} Waiting....".format(datetime.timestamp()))
|
|
123
|
-
if seconds >= 60: # detailed log message ~every minute
|
|
124
|
-
if minutes % 5 == 0:
|
|
125
|
-
logger.info(
|
|
126
|
-
"[{}] Waiting ({} minutes so far) for request to {}".format(datetime.timestamp(), minutes, url)
|
|
127
|
-
)
|
|
128
|
-
else:
|
|
129
|
-
logger.debug("[{}] Waiting for request to {}".format(datetime.timestamp(), url))
|
|
130
|
-
sleep(DELAY_SLEEP_SECONDS)
|
|
88
|
+
self, method, url, parameters={}, request_timeout=None
|
|
89
|
+
) -> Optional[Union["Response", Exception]]:
|
|
90
|
+
return self._blocking_request(method, url, parameters)
|
|
131
91
|
|
|
132
92
|
def _make_request(
|
|
133
93
|
self,
|
|
@@ -151,7 +111,7 @@ class Endpoint(object):
|
|
|
151
111
|
# a request can, for stuff like publishing, spin for ages waiting for a response.
|
|
152
112
|
# we need some user-facing activity so they know it's not dead.
|
|
153
113
|
request_timeout = self.parent_srv.http_options.get("timeout") or 0
|
|
154
|
-
server_response: Optional["Response"] = self.send_request_while_show_progress_threaded(
|
|
114
|
+
server_response: Optional[Union["Response", Exception]] = self.send_request_while_show_progress_threaded(
|
|
155
115
|
method, url, parameters, request_timeout
|
|
156
116
|
)
|
|
157
117
|
logger.debug("[{}] Async request returned: received {}".format(datetime.timestamp(), server_response))
|
|
@@ -163,6 +123,8 @@ class Endpoint(object):
|
|
|
163
123
|
if server_response is None:
|
|
164
124
|
logger.debug("[{}] Request failed".format(datetime.timestamp()))
|
|
165
125
|
raise RuntimeError
|
|
126
|
+
if isinstance(server_response, Exception):
|
|
127
|
+
raise server_response
|
|
166
128
|
self._check_status(server_response, url)
|
|
167
129
|
|
|
168
130
|
loggable_response = self.log_response_safely(server_response)
|
|
@@ -7,6 +7,8 @@ from contextlib import closing
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Iterable, List, Optional, TYPE_CHECKING, Tuple, Union
|
|
9
9
|
|
|
10
|
+
from tableauserverclient.helpers.headers import fix_filename
|
|
11
|
+
|
|
10
12
|
from .dqw_endpoint import _DataQualityWarningEndpoint
|
|
11
13
|
from .endpoint import QuerysetEndpoint, api
|
|
12
14
|
from .exceptions import InternalServerError, MissingRequiredFieldError
|
|
@@ -124,6 +126,7 @@ class Flows(QuerysetEndpoint):
|
|
|
124
126
|
filepath.write(chunk)
|
|
125
127
|
return_path = filepath
|
|
126
128
|
else:
|
|
129
|
+
params = fix_filename(params)
|
|
127
130
|
filename = to_filename(os.path.basename(params["filename"]))
|
|
128
131
|
download_path = make_download_path(filepath, filename)
|
|
129
132
|
with open(download_path, "wb") as f:
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from typing import List, Optional, Tuple, TYPE_CHECKING
|
|
2
3
|
|
|
3
|
-
from .endpoint import Endpoint, api
|
|
4
|
-
from .exceptions import MissingRequiredFieldError
|
|
4
|
+
from tableauserverclient.server.endpoint.endpoint import Endpoint, api
|
|
5
|
+
from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError
|
|
5
6
|
from tableauserverclient.models import TaskItem, PaginationItem
|
|
6
7
|
from tableauserverclient.server import RequestFactory
|
|
7
8
|
|
|
8
9
|
from tableauserverclient.helpers.logging import logger
|
|
9
10
|
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from tableauserverclient.server.request_options import RequestOptions
|
|
13
|
+
|
|
10
14
|
|
|
11
15
|
class Tasks(Endpoint):
|
|
12
16
|
@property
|
|
13
|
-
def baseurl(self):
|
|
17
|
+
def baseurl(self) -> str:
|
|
14
18
|
return "{0}/sites/{1}/tasks".format(self.parent_srv.baseurl, self.parent_srv.site_id)
|
|
15
19
|
|
|
16
|
-
def __normalize_task_type(self, task_type):
|
|
20
|
+
def __normalize_task_type(self, task_type: str) -> str:
|
|
17
21
|
"""
|
|
18
22
|
The word for extract refresh used in API URL is "extractRefreshes".
|
|
19
23
|
It is different than the tag "extractRefresh" used in the request body.
|
|
@@ -24,11 +28,13 @@ class Tasks(Endpoint):
|
|
|
24
28
|
return task_type
|
|
25
29
|
|
|
26
30
|
@api(version="2.6")
|
|
27
|
-
def get(
|
|
31
|
+
def get(
|
|
32
|
+
self, req_options: Optional["RequestOptions"] = None, task_type: str = TaskItem.Type.ExtractRefresh
|
|
33
|
+
) -> Tuple[List[TaskItem], PaginationItem]:
|
|
28
34
|
if task_type == TaskItem.Type.DataAcceleration:
|
|
29
35
|
self.parent_srv.assert_at_least_version("3.8", "Data Acceleration Tasks")
|
|
30
36
|
|
|
31
|
-
logger.info("Querying all
|
|
37
|
+
logger.info("Querying all %s tasks for the site", task_type)
|
|
32
38
|
|
|
33
39
|
url = "{0}/{1}".format(self.baseurl, self.__normalize_task_type(task_type))
|
|
34
40
|
server_response = self.get_request(url, req_options)
|
|
@@ -38,11 +44,11 @@ class Tasks(Endpoint):
|
|
|
38
44
|
return all_tasks, pagination_item
|
|
39
45
|
|
|
40
46
|
@api(version="2.6")
|
|
41
|
-
def get_by_id(self, task_id):
|
|
47
|
+
def get_by_id(self, task_id: str) -> TaskItem:
|
|
42
48
|
if not task_id:
|
|
43
49
|
error = "No Task ID provided"
|
|
44
50
|
raise ValueError(error)
|
|
45
|
-
logger.info("Querying a single task by id
|
|
51
|
+
logger.info("Querying a single task by id %s", task_id)
|
|
46
52
|
url = "{}/{}/{}".format(
|
|
47
53
|
self.baseurl,
|
|
48
54
|
self.__normalize_task_type(TaskItem.Type.ExtractRefresh),
|
|
@@ -56,14 +62,14 @@ class Tasks(Endpoint):
|
|
|
56
62
|
if not extract_item:
|
|
57
63
|
error = "No extract refresh provided"
|
|
58
64
|
raise ValueError(error)
|
|
59
|
-
logger.info("Creating an extract refresh
|
|
65
|
+
logger.info("Creating an extract refresh %s", extract_item)
|
|
60
66
|
url = "{0}/{1}".format(self.baseurl, self.__normalize_task_type(TaskItem.Type.ExtractRefresh))
|
|
61
67
|
create_req = RequestFactory.Task.create_extract_req(extract_item)
|
|
62
68
|
server_response = self.post_request(url, create_req)
|
|
63
69
|
return server_response.content
|
|
64
70
|
|
|
65
71
|
@api(version="2.6")
|
|
66
|
-
def run(self, task_item):
|
|
72
|
+
def run(self, task_item: TaskItem) -> bytes:
|
|
67
73
|
if not task_item.id:
|
|
68
74
|
error = "Task item missing ID."
|
|
69
75
|
raise MissingRequiredFieldError(error)
|
|
@@ -79,7 +85,7 @@ class Tasks(Endpoint):
|
|
|
79
85
|
|
|
80
86
|
# Delete 1 task by id
|
|
81
87
|
@api(version="3.6")
|
|
82
|
-
def delete(self, task_id, task_type=TaskItem.Type.ExtractRefresh):
|
|
88
|
+
def delete(self, task_id: str, task_type: str = TaskItem.Type.ExtractRefresh) -> None:
|
|
83
89
|
if task_type == TaskItem.Type.DataAcceleration:
|
|
84
90
|
self.parent_srv.assert_at_least_version("3.8", "Data Acceleration Tasks")
|
|
85
91
|
|
|
@@ -88,4 +94,4 @@ class Tasks(Endpoint):
|
|
|
88
94
|
raise ValueError(error)
|
|
89
95
|
url = "{0}/{1}/{2}".format(self.baseurl, self.__normalize_task_type(task_type), task_id)
|
|
90
96
|
self.delete_request(url)
|
|
91
|
-
logger.info("Deleted single task (ID:
|
|
97
|
+
logger.info("Deleted single task (ID: %s)", task_id)
|
|
@@ -6,6 +6,8 @@ import os
|
|
|
6
6
|
from contextlib import closing
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
+
from tableauserverclient.helpers.headers import fix_filename
|
|
10
|
+
|
|
9
11
|
from .endpoint import QuerysetEndpoint, api, parameter_added_in
|
|
10
12
|
from .exceptions import InternalServerError, MissingRequiredFieldError
|
|
11
13
|
from .permissions_endpoint import _PermissionsEndpoint
|
|
@@ -88,8 +90,8 @@ class Workbooks(QuerysetEndpoint):
|
|
|
88
90
|
return WorkbookItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
89
91
|
|
|
90
92
|
@api(version="2.8")
|
|
91
|
-
def refresh(self,
|
|
92
|
-
id_ = getattr(
|
|
93
|
+
def refresh(self, workbook_item: Union[WorkbookItem, str]) -> JobItem:
|
|
94
|
+
id_ = getattr(workbook_item, "id", workbook_item)
|
|
93
95
|
url = "{0}/{1}/refresh".format(self.baseurl, id_)
|
|
94
96
|
empty_req = RequestFactory.Empty.empty_req()
|
|
95
97
|
server_response = self.post_request(url, empty_req)
|
|
@@ -455,7 +457,7 @@ class Workbooks(QuerysetEndpoint):
|
|
|
455
457
|
def download_revision(
|
|
456
458
|
self,
|
|
457
459
|
workbook_id: str,
|
|
458
|
-
revision_number: str,
|
|
460
|
+
revision_number: Optional[str],
|
|
459
461
|
filepath: Optional[PathOrFileW] = None,
|
|
460
462
|
include_extract: bool = True,
|
|
461
463
|
no_extract: Optional[bool] = None,
|
|
@@ -487,6 +489,7 @@ class Workbooks(QuerysetEndpoint):
|
|
|
487
489
|
filepath.write(chunk)
|
|
488
490
|
return_path = filepath
|
|
489
491
|
else:
|
|
492
|
+
params = fix_filename(params)
|
|
490
493
|
filename = to_filename(os.path.basename(params["filename"]))
|
|
491
494
|
download_path = make_download_path(filepath, filename)
|
|
492
495
|
with open(download_path, "wb") as f:
|
|
@@ -1032,6 +1032,16 @@ class TaskRequest(object):
|
|
|
1032
1032
|
def create_extract_req(self, xml_request: ET.Element, extract_item: "TaskItem") -> bytes:
|
|
1033
1033
|
extract_element = ET.SubElement(xml_request, "extractRefresh")
|
|
1034
1034
|
|
|
1035
|
+
# Main attributes
|
|
1036
|
+
extract_element.attrib["type"] = extract_item.task_type
|
|
1037
|
+
|
|
1038
|
+
if extract_item.target is not None:
|
|
1039
|
+
target_element = ET.SubElement(extract_element, extract_item.target.type)
|
|
1040
|
+
target_element.attrib["id"] = extract_item.target.id
|
|
1041
|
+
|
|
1042
|
+
if extract_item.schedule_item is None:
|
|
1043
|
+
return ET.tostring(xml_request)
|
|
1044
|
+
|
|
1035
1045
|
# Schedule attributes
|
|
1036
1046
|
schedule_element = ET.SubElement(xml_request, "schedule")
|
|
1037
1047
|
|
|
@@ -1043,17 +1053,11 @@ class TaskRequest(object):
|
|
|
1043
1053
|
frequency_element.attrib["end"] = str(interval_item.end_time)
|
|
1044
1054
|
if hasattr(interval_item, "interval") and interval_item.interval:
|
|
1045
1055
|
intervals_element = ET.SubElement(frequency_element, "intervals")
|
|
1046
|
-
for interval in interval_item._interval_type_pairs():
|
|
1056
|
+
for interval in interval_item._interval_type_pairs(): # type: ignore
|
|
1047
1057
|
expression, value = interval
|
|
1048
1058
|
single_interval_element = ET.SubElement(intervals_element, "interval")
|
|
1049
1059
|
single_interval_element.attrib[expression] = value
|
|
1050
1060
|
|
|
1051
|
-
# Main attributes
|
|
1052
|
-
extract_element.attrib["type"] = extract_item.task_type
|
|
1053
|
-
|
|
1054
|
-
target_element = ET.SubElement(extract_element, extract_item.target.type)
|
|
1055
|
-
target_element.attrib["id"] = extract_item.target.id
|
|
1056
|
-
|
|
1057
1061
|
return ET.tostring(xml_request)
|
|
1058
1062
|
|
|
1059
1063
|
|
|
@@ -37,35 +37,75 @@ class RequestOptions(RequestOptionsBase):
|
|
|
37
37
|
|
|
38
38
|
class Field:
|
|
39
39
|
Args = "args"
|
|
40
|
+
AuthenticationType = "authenticationType"
|
|
41
|
+
Caption = "caption"
|
|
42
|
+
Channel = "channel"
|
|
40
43
|
CompletedAt = "completedAt"
|
|
44
|
+
ConnectedWorkbookType = "connectedWorkbookType"
|
|
45
|
+
ConnectionTo = "connectionTo"
|
|
46
|
+
ConnectionType = "connectionType"
|
|
41
47
|
ContentUrl = "contentUrl"
|
|
42
48
|
CreatedAt = "createdAt"
|
|
49
|
+
DatabaseName = "databaseName"
|
|
50
|
+
DatabaseUserName = "databaseUserName"
|
|
51
|
+
Description = "description"
|
|
52
|
+
DisplayTabs = "displayTabs"
|
|
43
53
|
DomainName = "domainName"
|
|
44
54
|
DomainNickname = "domainNickname"
|
|
55
|
+
FavoritesTotal = "favoritesTotal"
|
|
56
|
+
Fields = "fields"
|
|
57
|
+
FlowId = "flowId"
|
|
58
|
+
FriendlyName = "friendlyName"
|
|
59
|
+
HasAlert = "hasAlert"
|
|
60
|
+
HasAlerts = "hasAlerts"
|
|
61
|
+
HasEmbeddedPassword = "hasEmbeddedPassword"
|
|
62
|
+
HasExtracts = "hasExtracts"
|
|
45
63
|
HitsTotal = "hitsTotal"
|
|
64
|
+
Id = "id"
|
|
65
|
+
IsCertified = "isCertified"
|
|
66
|
+
IsConnectable = "isConnectable"
|
|
67
|
+
IsDefaultPort = "isDefaultPort"
|
|
68
|
+
IsHierarchical = "isHierarchical"
|
|
46
69
|
IsLocal = "isLocal"
|
|
70
|
+
IsPublished = "isPublished"
|
|
47
71
|
JobType = "jobType"
|
|
48
72
|
LastLogin = "lastLogin"
|
|
73
|
+
Luid = "luid"
|
|
49
74
|
MinimumSiteRole = "minimumSiteRole"
|
|
50
75
|
Name = "name"
|
|
51
76
|
Notes = "notes"
|
|
77
|
+
NotificationType = "notificationType"
|
|
52
78
|
OwnerDomain = "ownerDomain"
|
|
53
79
|
OwnerEmail = "ownerEmail"
|
|
54
80
|
OwnerName = "ownerName"
|
|
55
81
|
ParentProjectId = "parentProjectId"
|
|
82
|
+
Priority = "priority"
|
|
56
83
|
Progress = "progress"
|
|
84
|
+
ProjectId = "projectId"
|
|
57
85
|
ProjectName = "projectName"
|
|
58
86
|
PublishSamples = "publishSamples"
|
|
87
|
+
ServerName = "serverName"
|
|
88
|
+
ServerPort = "serverPort"
|
|
89
|
+
SheetCount = "sheetCount"
|
|
90
|
+
SheetNumber = "sheetNumber"
|
|
91
|
+
SheetType = "sheetType"
|
|
59
92
|
SiteRole = "siteRole"
|
|
93
|
+
Size = "size"
|
|
60
94
|
StartedAt = "startedAt"
|
|
61
95
|
Status = "status"
|
|
96
|
+
SubscriptionsTotal = "subscriptionsTotal"
|
|
62
97
|
Subtitle = "subtitle"
|
|
98
|
+
TableName = "tableName"
|
|
63
99
|
Tags = "tags"
|
|
64
100
|
Title = "title"
|
|
65
101
|
TopLevelProject = "topLevelProject"
|
|
66
102
|
Type = "type"
|
|
67
103
|
UpdatedAt = "updatedAt"
|
|
68
104
|
UserCount = "userCount"
|
|
105
|
+
UserId = "userId"
|
|
106
|
+
ViewUrlName = "viewUrlName"
|
|
107
|
+
WorkbookDescription = "workbookDescription"
|
|
108
|
+
WorkbookName = "workbookName"
|
|
69
109
|
|
|
70
110
|
class Direction:
|
|
71
111
|
Desc = "desc"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tableauserverclient
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.30
|
|
4
4
|
Summary: A Python module for working with the Tableau Server REST API.
|
|
5
5
|
Author-email: Tableau <github@tableau.com>
|
|
6
6
|
License: The MIT License (MIT)
|
|
@@ -40,7 +40,7 @@ License-File: LICENSE.versioneer
|
|
|
40
40
|
Requires-Dist: defusedxml >=0.7.1
|
|
41
41
|
Requires-Dist: packaging >=23.1
|
|
42
42
|
Requires-Dist: requests >=2.31
|
|
43
|
-
Requires-Dist: urllib3 ==2.0.
|
|
43
|
+
Requires-Dist: urllib3 ==2.0.7
|
|
44
44
|
Provides-Extra: test
|
|
45
45
|
Requires-Dist: argparse ; extra == 'test'
|
|
46
46
|
Requires-Dist: black ==23.7 ; extra == 'test'
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
tableauserverclient/__init__.py,sha256=hDjLTdwXPmQj4BQBRdUcnP7XdhT7apJ_c7JAPyyYZ7Y,1096
|
|
2
|
-
tableauserverclient/_version.py,sha256=
|
|
3
|
-
tableauserverclient/config.py,sha256=
|
|
2
|
+
tableauserverclient/_version.py,sha256=1DZOBnxMpAUaNuob4jPBOvm74gFOHdcIzHXiKW4g43M,496
|
|
3
|
+
tableauserverclient/config.py,sha256=frumJjDxOQug_R54kjrnHLyaIgg1bV7aUoNPHMJlBhc,430
|
|
4
4
|
tableauserverclient/datetime_helpers.py,sha256=_-gWz5I2_KHT5AzW_boD8meH6loTTKdK-_62h1MA6ko,884
|
|
5
5
|
tableauserverclient/exponential_backoff.py,sha256=HtAfbbVnYtiOe2_ZzKqZmsml40EDZBaC3bttif5qeG8,1474
|
|
6
6
|
tableauserverclient/filesys_helpers.py,sha256=hosTm9fpc9B9_VCDcAuyHA0A-f-MWs9_2utyRweuZ5I,1667
|
|
7
7
|
tableauserverclient/namespace.py,sha256=i0o0T0xO0vd1RX98o54xmIrqCkeVfhtQOGDMGRx_F_o,971
|
|
8
8
|
tableauserverclient/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
tableauserverclient/helpers/__init__.py,sha256=llpqF9zV5dsP5hQt8dyop33Op5OzGmxW0BZibaSlCcM,23
|
|
10
|
+
tableauserverclient/helpers/headers.py,sha256=Pg-ZWSgV2Yp813BV1Tzo8F2Hn4P01zscla2RRM4yqNk,411
|
|
10
11
|
tableauserverclient/helpers/logging.py,sha256=5q_oR3wERHVXFXY6XDnLYML5-HdAPJTmqhH9IZg4VfY,113
|
|
11
|
-
tableauserverclient/helpers/strings.py,sha256=
|
|
12
|
+
tableauserverclient/helpers/strings.py,sha256=HRTP31HIdD3gFxDDHuBsmOl_8cLdh8fa3xLALgYsUAQ,1563
|
|
12
13
|
tableauserverclient/models/__init__.py,sha256=NfVfRDeU8b6zTQ7wzkorhvuGIPabym8lXvNiyEX_AVg,1620
|
|
13
14
|
tableauserverclient/models/column_item.py,sha256=gV2r7xOVmcn9UnMXFOe9_ORZ85oha1KtIH-crKUhgN4,1972
|
|
14
15
|
tableauserverclient/models/connection_credentials.py,sha256=8Wa8B_-cB3INFUup-Pyd5znsUD5opgYYH5TfipCzKTU,1559
|
|
@@ -25,16 +26,16 @@ tableauserverclient/models/fileupload_item.py,sha256=tx3LfgRxVqeHGpCN6kTDPkdBY5i
|
|
|
25
26
|
tableauserverclient/models/flow_item.py,sha256=rPIVDe8_tHied8TDRiGpg2gyTyQWlNiGb3vGhqF9YJw,7320
|
|
26
27
|
tableauserverclient/models/flow_run_item.py,sha256=pwJNz0m7l--Sb6z0o83ebWfAmTVFYSr-0apMxHWt0Vs,3139
|
|
27
28
|
tableauserverclient/models/group_item.py,sha256=1KP_KmIG1veJJGyte82aFvMAiU_tvzYtaP2vLEReLoo,3621
|
|
28
|
-
tableauserverclient/models/interval_item.py,sha256=
|
|
29
|
+
tableauserverclient/models/interval_item.py,sha256=uSYQskXWlPFoQHC2wz58j6G93qhVKepMTYeWW9FbGaY,9053
|
|
29
30
|
tableauserverclient/models/job_item.py,sha256=PMt7G2qu8WAxe-b3f_0BtQ9JcCSvk-g5L5EcOUpYFCY,8619
|
|
30
31
|
tableauserverclient/models/metric_item.py,sha256=Zrzi_Un43p2jzE_4OhetxhjfeSUYzotMfm8Hjd5WOhI,5397
|
|
31
32
|
tableauserverclient/models/pagination_item.py,sha256=Hdr2EIKWkXfrjWOr-Ao_vzQTWB2akUfxxtaupFKLrlQ,1432
|
|
32
33
|
tableauserverclient/models/permissions_item.py,sha256=2WlJOc1Vxkxl-WnAkFaacWsGxUXU9_d85AetSKrKwP0,3731
|
|
33
|
-
tableauserverclient/models/project_item.py,sha256=
|
|
34
|
+
tableauserverclient/models/project_item.py,sha256=DNxAhkMkRt9MLcMie4w8nuTG4sApIkt618onBEdoM_c,6915
|
|
34
35
|
tableauserverclient/models/property_decorators.py,sha256=eXaYxzXYuHdftlcnqSQOzHYM7OAilETWK8Td2Dtmmuc,4726
|
|
35
36
|
tableauserverclient/models/reference_item.py,sha256=HddG1loGNJU7gB7hBccCjnhagnSEbk4_wY2kuPbNhRQ,534
|
|
36
37
|
tableauserverclient/models/revision_item.py,sha256=fLkcScAgBBiVMCyDPk4aboyZNxrykIavzJP2vkn6Rq0,2787
|
|
37
|
-
tableauserverclient/models/schedule_item.py,sha256=
|
|
38
|
+
tableauserverclient/models/schedule_item.py,sha256=wSkOz9OUJol_aznOl66ZMDz9Q0Y-LOOc3me8X-ysV28,11324
|
|
38
39
|
tableauserverclient/models/server_info_item.py,sha256=pR_fqqPuiBwpLn0wr4rQ4vxK7gxGJVTtaswzwjslheE,1880
|
|
39
40
|
tableauserverclient/models/site_item.py,sha256=xvXpsR0eEz2ypS-94KJTMS-gusHV1bLomKIhWX7GXrU,42256
|
|
40
41
|
tableauserverclient/models/subscription_item.py,sha256=XkQiAi_gWPBnxtTaB8TgrFa-IDVDtai9H00Ilay0T64,4744
|
|
@@ -43,7 +44,7 @@ tableauserverclient/models/tableau_auth.py,sha256=TdkOtRnNRi4p0w_WhBsWqUunNVG9nx
|
|
|
43
44
|
tableauserverclient/models/tableau_types.py,sha256=1iq1034K1W9ugLAxON_t8n2UxSouiWV_ZEwoHiNM94c,918
|
|
44
45
|
tableauserverclient/models/tag_item.py,sha256=mVfJGZG8PspPM8hBxX38heEQ_Rnp5sSwfwWdLNA-UV0,619
|
|
45
46
|
tableauserverclient/models/target.py,sha256=dVc1SHwo9ZASR8250MhAPODXX_G5UqA4BnK8hykRE6E,263
|
|
46
|
-
tableauserverclient/models/task_item.py,sha256=
|
|
47
|
+
tableauserverclient/models/task_item.py,sha256=o0xm588Mc6WGGbYaY0eAxdYog5Z_lfWp1vripK7rKPI,3738
|
|
47
48
|
tableauserverclient/models/user_item.py,sha256=vaQD3nvU7bJVD6t0nkWQFse8UDz8HTp8wh_6WmCjKtM,15776
|
|
48
49
|
tableauserverclient/models/view_item.py,sha256=7OLTLpLXDiYAWi7Tc6vSCkh2Krl7-cvKUwA7iMWZspQ,6777
|
|
49
50
|
tableauserverclient/models/webhook_item.py,sha256=LLf2HQ2Y50Rw-c4dSYyv6MXLbXdij9Iyy261gu9zpv4,2650
|
|
@@ -53,8 +54,8 @@ tableauserverclient/server/exceptions.py,sha256=l-O4lcoEeXJC7fLWUanIcZM_Mf8m54uK
|
|
|
53
54
|
tableauserverclient/server/filter.py,sha256=WZ_dIBuADRZf8VTYcRgn1rulSGUOJq8_uUltq05KkRI,1119
|
|
54
55
|
tableauserverclient/server/pager.py,sha256=rd6rKkoTxP0u3RiOZ3HY9oWKyOcf8aowbHPcs-Ul4D0,2725
|
|
55
56
|
tableauserverclient/server/query.py,sha256=qMweo-N5P_V9z2Or4u_QrStaBbGL6HOg4Y4EDg8uh44,5735
|
|
56
|
-
tableauserverclient/server/request_factory.py,sha256=
|
|
57
|
-
tableauserverclient/server/request_options.py,sha256=
|
|
57
|
+
tableauserverclient/server/request_factory.py,sha256=br5JwdVFbo9gU2kvg1DUzMnYN31i4UcdLWXwpMheMoY,59350
|
|
58
|
+
tableauserverclient/server/request_options.py,sha256=JiibNhk9QHI1houbtb4NYD4-y9GLxC98iArZJ1E8GUQ,8553
|
|
58
59
|
tableauserverclient/server/server.py,sha256=LhbQOk-lvIBAOF5tTAghYNPFsKJN4Z95SJbuHLAxdKo,8401
|
|
59
60
|
tableauserverclient/server/sort.py,sha256=ikndqXW2r2FeacJzybC2TVcJGn4ktviWgXwPyK-AX-0,208
|
|
60
61
|
tableauserverclient/server/endpoint/__init__.py,sha256=9KGDtgBWlWsWbBQvQV2cbIU-VmHmMqcgtSM7PonpIzg,1141
|
|
@@ -63,15 +64,15 @@ tableauserverclient/server/endpoint/custom_views_endpoint.py,sha256=SShsI8TnDe3t
|
|
|
63
64
|
tableauserverclient/server/endpoint/data_acceleration_report_endpoint.py,sha256=nfC2gXoCke-YXGTiMWGDRBK_lDlLa9Y6hts7w3Zs8cI,1170
|
|
64
65
|
tableauserverclient/server/endpoint/data_alert_endpoint.py,sha256=c6-ICGRVDRQN7ClAgCeJsk6JK4HyBvtQGQVLOzNc4eo,5358
|
|
65
66
|
tableauserverclient/server/endpoint/databases_endpoint.py,sha256=0V6cHwF5E1K1oQ9JKT-rb7t1EFPNwlSKMC2HyqEqYPw,5366
|
|
66
|
-
tableauserverclient/server/endpoint/datasources_endpoint.py,sha256=
|
|
67
|
+
tableauserverclient/server/endpoint/datasources_endpoint.py,sha256=XjoPiejyCGIze6gMO_ALZGWS44vFJ8LxroYr7Xq1OXE,20407
|
|
67
68
|
tableauserverclient/server/endpoint/default_permissions_endpoint.py,sha256=HQSYPIJwVCimuWo4YGYvsc41_-rweLHmJ-tQrWdH-Y8,4386
|
|
68
69
|
tableauserverclient/server/endpoint/dqw_endpoint.py,sha256=TvzjonvIiGtp4EisoBKms1lHOkVTjagQACw6unyY9_8,2418
|
|
69
|
-
tableauserverclient/server/endpoint/endpoint.py,sha256=
|
|
70
|
+
tableauserverclient/server/endpoint/endpoint.py,sha256=8-XteqZmfuHDyqR_mGSXKZm9esWbeybaZByHEZoBEac,12743
|
|
70
71
|
tableauserverclient/server/endpoint/exceptions.py,sha256=fh4fFYasqiKd3EPTxriVsmLkRL24wz9UvX94BMRfyLY,2534
|
|
71
72
|
tableauserverclient/server/endpoint/favorites_endpoint.py,sha256=D0MCco9kxaeCShfWOnVt3UpttJRgczJmJKXb3w0jpw4,6701
|
|
72
73
|
tableauserverclient/server/endpoint/fileuploads_endpoint.py,sha256=fh2xH6a8mMcBI14QzodWPBMabK1_9SMDbzZbawz-ZJc,2553
|
|
73
74
|
tableauserverclient/server/endpoint/flow_runs_endpoint.py,sha256=1vwkOrb574natWl-njFOij8xaV32e9RU3-rFbu4bML0,3338
|
|
74
|
-
tableauserverclient/server/endpoint/flows_endpoint.py,sha256=
|
|
75
|
+
tableauserverclient/server/endpoint/flows_endpoint.py,sha256=DIHQUb5LHSerYXsEmaRoiNcdeQ2tU_RaBdr23NnLG2Y,13006
|
|
75
76
|
tableauserverclient/server/endpoint/groups_endpoint.py,sha256=_aSTVy7m3NXSEqgC0cPfJt6KOaPoWprxzz0MD8CsiiY,6640
|
|
76
77
|
tableauserverclient/server/endpoint/jobs_endpoint.py,sha256=P5Hdv1hsNVxd5KdrcNCfR9ZtJ7v3UgrfATCBItVdAxY,3239
|
|
77
78
|
tableauserverclient/server/endpoint/metadata_endpoint.py,sha256=AKnmRNj9U85p__igiuY_vhhipsxNXpQ_9zzNUtTuU9o,5203
|
|
@@ -84,14 +85,14 @@ tableauserverclient/server/endpoint/server_info_endpoint.py,sha256=4TtCc7oIp6iEu
|
|
|
84
85
|
tableauserverclient/server/endpoint/sites_endpoint.py,sha256=lMeVXxm1k32HZK84u7aTys9hUQekb4PE1hlhKFxaZ8c,6694
|
|
85
86
|
tableauserverclient/server/endpoint/subscriptions_endpoint.py,sha256=mMo9M6DF6tAZIab6UY8f_duETezELl9VXEYs5ocAcGY,3280
|
|
86
87
|
tableauserverclient/server/endpoint/tables_endpoint.py,sha256=OJQIarL-IC9mjeu1jfNh9B3UEkB41O51Q53TQNeSJ78,5376
|
|
87
|
-
tableauserverclient/server/endpoint/tasks_endpoint.py,sha256=
|
|
88
|
+
tableauserverclient/server/endpoint/tasks_endpoint.py,sha256=HjtSGXBGS8Eriaf5bSWySiqLmKQR_rU4Ao3wGz7Xpd8,4060
|
|
88
89
|
tableauserverclient/server/endpoint/users_endpoint.py,sha256=QRoApMyP53nF-AqvC7oLHNcT4VJE8s5ykjUnI5ojmPY,7274
|
|
89
90
|
tableauserverclient/server/endpoint/views_endpoint.py,sha256=GIYPftL5B0YUOb_SisFtfLP0LPIAbbUv24XCi15UH_M,7118
|
|
90
91
|
tableauserverclient/server/endpoint/webhooks_endpoint.py,sha256=-HsbAuKECNcx5EZkqp097PfGHLCNwPRnxbrdzreTpnM,2835
|
|
91
|
-
tableauserverclient/server/endpoint/workbooks_endpoint.py,sha256=
|
|
92
|
-
tableauserverclient-0.
|
|
93
|
-
tableauserverclient-0.
|
|
94
|
-
tableauserverclient-0.
|
|
95
|
-
tableauserverclient-0.
|
|
96
|
-
tableauserverclient-0.
|
|
97
|
-
tableauserverclient-0.
|
|
92
|
+
tableauserverclient/server/endpoint/workbooks_endpoint.py,sha256=_Tfaioe4QBA7VyswtxRu8uL-CzIK0wtslkwj41Wdx_4,21879
|
|
93
|
+
tableauserverclient-0.30.dist-info/LICENSE,sha256=MMkY7MguOb4L-WCmmqrVcwg2iwSGaz-RfAMFvXRXwsQ,1074
|
|
94
|
+
tableauserverclient-0.30.dist-info/LICENSE.versioneer,sha256=OYaGozOXk7bUNSm1z577iDYpti8a40XIqqR4lyQ47D8,334
|
|
95
|
+
tableauserverclient-0.30.dist-info/METADATA,sha256=K7aLh-r1p6dPxb6juSz5gB-fTepGAJOaVzeDABBuk9M,4073
|
|
96
|
+
tableauserverclient-0.30.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
97
|
+
tableauserverclient-0.30.dist-info/top_level.txt,sha256=kBnL39G2RlGqxJSsShDBWG4WZ3NFcWJkv1EGiOfZh4Q,20
|
|
98
|
+
tableauserverclient-0.30.dist-info/RECORD,,
|
|
File without changes
|
{tableauserverclient-0.28.dist-info → tableauserverclient-0.30.dist-info}/LICENSE.versioneer
RENAMED
|
File without changes
|
|
File without changes
|