tableauserverclient 0.29__py3-none-any.whl → 0.30.post0.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. tableauserverclient/__init__.py +1 -0
  2. tableauserverclient/_version.py +3 -3
  3. tableauserverclient/models/__init__.py +1 -0
  4. tableauserverclient/models/data_freshness_policy_item.py +210 -0
  5. tableauserverclient/models/interval_item.py +2 -2
  6. tableauserverclient/models/property_decorators.py +6 -11
  7. tableauserverclient/models/schedule_item.py +1 -0
  8. tableauserverclient/models/task_item.py +1 -0
  9. tableauserverclient/models/view_item.py +34 -0
  10. tableauserverclient/models/workbook_item.py +34 -4
  11. tableauserverclient/server/endpoint/__init__.py +1 -0
  12. tableauserverclient/server/endpoint/datasources_endpoint.py +5 -3
  13. tableauserverclient/server/endpoint/endpoint.py +3 -40
  14. tableauserverclient/server/endpoint/flow_task_endpoint.py +29 -0
  15. tableauserverclient/server/endpoint/flows_endpoint.py +5 -3
  16. tableauserverclient/server/endpoint/workbooks_endpoint.py +14 -4
  17. tableauserverclient/server/pager.py +5 -1
  18. tableauserverclient/server/request_factory.py +92 -4
  19. tableauserverclient/server/request_options.py +47 -3
  20. tableauserverclient/server/server.py +2 -0
  21. {tableauserverclient-0.29.dist-info → tableauserverclient-0.30.post0.dev1.dist-info}/METADATA +2 -1
  22. {tableauserverclient-0.29.dist-info → tableauserverclient-0.30.post0.dev1.dist-info}/RECORD +26 -24
  23. {tableauserverclient-0.29.dist-info → tableauserverclient-0.30.post0.dev1.dist-info}/WHEEL +1 -1
  24. {tableauserverclient-0.29.dist-info → tableauserverclient-0.30.post0.dev1.dist-info}/LICENSE +0 -0
  25. {tableauserverclient-0.29.dist-info → tableauserverclient-0.30.post0.dev1.dist-info}/LICENSE.versioneer +0 -0
  26. {tableauserverclient-0.29.dist-info → tableauserverclient-0.30.post0.dev1.dist-info}/top_level.txt +0 -0
@@ -10,6 +10,7 @@ from .models import (
10
10
  DailyInterval,
11
11
  DataAlertItem,
12
12
  DatabaseItem,
13
+ DataFreshnessPolicyItem,
13
14
  DatasourceItem,
14
15
  FavoriteItem,
15
16
  FlowItem,
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2024-01-14T00:38:52+1100",
11
+ "date": "2024-06-03T12:52:39-0700",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "0e46e15fc80c2d4492d9580f111a768536f6afaa",
15
- "version": "0.29"
14
+ "full-revisionid": "4018a0ffc01bfa7c5b0024c6f112ced158d420b9",
15
+ "version": "0.30.post0.dev1"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -5,6 +5,7 @@ from .custom_view_item import CustomViewItem
5
5
  from .data_acceleration_report_item import DataAccelerationReportItem
6
6
  from .data_alert_item import DataAlertItem
7
7
  from .database_item import DatabaseItem
8
+ from .data_freshness_policy_item import DataFreshnessPolicyItem
8
9
  from .datasource_item import DatasourceItem
9
10
  from .dqw_item import DQWItem
10
11
  from .exceptions import UnpopulatedPropertyError
@@ -0,0 +1,210 @@
1
+ import xml.etree.ElementTree as ET
2
+
3
+ from typing import Optional, Union, List
4
+ from tableauserverclient.models.property_decorators import property_is_enum, property_not_nullable
5
+ from .interval_item import IntervalItem
6
+
7
+
8
+ class DataFreshnessPolicyItem:
9
+ class Option:
10
+ AlwaysLive = "AlwaysLive"
11
+ SiteDefault = "SiteDefault"
12
+ FreshEvery = "FreshEvery"
13
+ FreshAt = "FreshAt"
14
+
15
+ class FreshEvery:
16
+ class Frequency:
17
+ Minutes = "Minutes"
18
+ Hours = "Hours"
19
+ Days = "Days"
20
+ Weeks = "Weeks"
21
+
22
+ def __init__(self, frequency: str, value: int):
23
+ self.frequency: str = frequency
24
+ self.value: int = value
25
+
26
+ def __repr__(self):
27
+ return "<FreshEvery frequency={_frequency} value={_value}>".format(**vars(self))
28
+
29
+ @property
30
+ def frequency(self) -> str:
31
+ return self._frequency
32
+
33
+ @frequency.setter
34
+ @property_is_enum(Frequency)
35
+ def frequency(self, value: str):
36
+ self._frequency = value
37
+
38
+ @classmethod
39
+ def from_xml_element(cls, fresh_every_schedule_elem: ET.Element):
40
+ frequency = fresh_every_schedule_elem.get("frequency", None)
41
+ value_str = fresh_every_schedule_elem.get("value", None)
42
+ if (frequency is None) or (value_str is None):
43
+ return None
44
+ value = int(value_str)
45
+ return DataFreshnessPolicyItem.FreshEvery(frequency, value)
46
+
47
+ class FreshAt:
48
+ class Frequency:
49
+ Day = "Day"
50
+ Week = "Week"
51
+ Month = "Month"
52
+
53
+ def __init__(self, frequency: str, time: str, timezone, interval_item: Optional[List[str]] = None):
54
+ self.frequency = frequency
55
+ self.time = time
56
+ self.timezone = timezone
57
+ self.interval_item: Optional[List[str]] = interval_item
58
+
59
+ def __repr__(self):
60
+ return (
61
+ "<FreshAt frequency={_frequency} time={_time}> timezone={_timezone} " "interval_item={_interval_time}"
62
+ ).format(**vars(self))
63
+
64
+ @property
65
+ def interval_item(self) -> Optional[List[str]]:
66
+ return self._interval_item
67
+
68
+ @interval_item.setter
69
+ def interval_item(self, value: List[str]):
70
+ self._interval_item = value
71
+
72
+ @property
73
+ def time(self):
74
+ return self._time
75
+
76
+ @time.setter
77
+ @property_not_nullable
78
+ def time(self, value):
79
+ self._time = value
80
+
81
+ @property
82
+ def timezone(self) -> str:
83
+ return self._timezone
84
+
85
+ @timezone.setter
86
+ def timezone(self, value: str):
87
+ self._timezone = value
88
+
89
+ @property
90
+ def frequency(self) -> str:
91
+ return self._frequency
92
+
93
+ @frequency.setter
94
+ @property_is_enum(Frequency)
95
+ def frequency(self, value: str):
96
+ self._frequency = value
97
+
98
+ @classmethod
99
+ def from_xml_element(cls, fresh_at_schedule_elem: ET.Element, ns):
100
+ frequency = fresh_at_schedule_elem.get("frequency", None)
101
+ time = fresh_at_schedule_elem.get("time", None)
102
+ if (frequency is None) or (time is None):
103
+ return None
104
+ timezone = fresh_at_schedule_elem.get("timezone", None)
105
+ interval = parse_intervals(fresh_at_schedule_elem, frequency, ns)
106
+ return DataFreshnessPolicyItem.FreshAt(frequency, time, timezone, interval)
107
+
108
+ def __init__(self, option: str):
109
+ self.option = option
110
+ self.fresh_every_schedule: Optional[DataFreshnessPolicyItem.FreshEvery] = None
111
+ self.fresh_at_schedule: Optional[DataFreshnessPolicyItem.FreshAt] = None
112
+
113
+ def __repr__(self):
114
+ return "<DataFreshnessPolicy option={_option}>".format(**vars(self))
115
+
116
+ @property
117
+ def option(self) -> str:
118
+ return self._option
119
+
120
+ @option.setter
121
+ @property_is_enum(Option)
122
+ def option(self, value: str):
123
+ self._option = value
124
+
125
+ @property
126
+ def fresh_every_schedule(self) -> Optional[FreshEvery]:
127
+ return self._fresh_every_schedule
128
+
129
+ @fresh_every_schedule.setter
130
+ def fresh_every_schedule(self, value: FreshEvery):
131
+ self._fresh_every_schedule = value
132
+
133
+ @property
134
+ def fresh_at_schedule(self) -> Optional[FreshAt]:
135
+ return self._fresh_at_schedule
136
+
137
+ @fresh_at_schedule.setter
138
+ def fresh_at_schedule(self, value: FreshAt):
139
+ self._fresh_at_schedule = value
140
+
141
+ @classmethod
142
+ def from_xml_element(cls, data_freshness_policy_elem, ns):
143
+ option = data_freshness_policy_elem.get("option", None)
144
+ if option is None:
145
+ return None
146
+ data_freshness_policy = DataFreshnessPolicyItem(option)
147
+
148
+ fresh_at_schedule = None
149
+ fresh_every_schedule = None
150
+ if option == "FreshAt":
151
+ fresh_at_schedule_elem = data_freshness_policy_elem.find(".//t:freshAtSchedule", namespaces=ns)
152
+ fresh_at_schedule = DataFreshnessPolicyItem.FreshAt.from_xml_element(fresh_at_schedule_elem, ns)
153
+ data_freshness_policy.fresh_at_schedule = fresh_at_schedule
154
+ elif option == "FreshEvery":
155
+ fresh_every_schedule_elem = data_freshness_policy_elem.find(".//t:freshEverySchedule", namespaces=ns)
156
+ fresh_every_schedule = DataFreshnessPolicyItem.FreshEvery.from_xml_element(fresh_every_schedule_elem)
157
+ data_freshness_policy.fresh_every_schedule = fresh_every_schedule
158
+
159
+ return data_freshness_policy
160
+
161
+
162
+ def parse_intervals(intervals_elem, frequency, ns):
163
+ interval_elems = intervals_elem.findall(".//t:intervals/t:interval", namespaces=ns)
164
+ interval = []
165
+ for interval_elem in interval_elems:
166
+ interval.extend(interval_elem.attrib.items())
167
+
168
+ # No intervals expected for Day frequency
169
+ if frequency == DataFreshnessPolicyItem.FreshAt.Frequency.Day:
170
+ return None
171
+
172
+ if frequency == DataFreshnessPolicyItem.FreshAt.Frequency.Week:
173
+ interval_values = [(i[1]).title() for i in interval]
174
+ return parse_week_intervals(interval_values)
175
+
176
+ if frequency == DataFreshnessPolicyItem.FreshAt.Frequency.Month:
177
+ interval_values = [(i[1]) for i in interval]
178
+ return parse_month_intervals(interval_values)
179
+
180
+
181
+ def parse_week_intervals(interval_values):
182
+ # Using existing IntervalItem.Day to check valid weekday string
183
+ if not all(hasattr(IntervalItem.Day, day) for day in interval_values):
184
+ raise ValueError("Invalid week day defined " + str(interval_values))
185
+ return interval_values
186
+
187
+
188
+ def parse_month_intervals(interval_values):
189
+ error = "Invalid interval value for a monthly frequency: {}.".format(interval_values)
190
+
191
+ # Month interval can have value either only ['LastDay'] or list of dates e.g. ["1", 20", "30"]
192
+ # First check if the list only have LastDay value. When using LastDay, there shouldn't be
193
+ # any other values, hence checking the first element of the list is enough.
194
+ # If the value is not "LastDay", we assume intervals is on list of dates format.
195
+ # We created this function instead of using existing MonthlyInterval because we allow list of dates interval,
196
+
197
+ intervals = []
198
+ if interval_values[0] == "LastDay":
199
+ intervals.append(interval_values[0])
200
+ else:
201
+ for interval in interval_values:
202
+ try:
203
+ if 1 <= int(interval) <= 31:
204
+ intervals.append(interval)
205
+ else:
206
+ raise ValueError(error)
207
+ except ValueError:
208
+ if interval_values[0] != "LastDay":
209
+ raise ValueError(error)
210
+ return intervals
@@ -69,7 +69,7 @@ class HourlyInterval(object):
69
69
 
70
70
  @interval.setter
71
71
  def interval(self, intervals):
72
- VALID_INTERVALS = {0.25, 0.5, 1, 2, 4, 6, 8, 12}
72
+ VALID_INTERVALS = {0.25, 0.5, 1, 2, 4, 6, 8, 12, 24}
73
73
  for interval in intervals:
74
74
  # if an hourly interval is a string, then it is a weekDay interval
75
75
  if isinstance(interval, str) and not interval.isnumeric() and not hasattr(IntervalItem.Day, interval):
@@ -136,7 +136,7 @@ class DailyInterval(object):
136
136
 
137
137
  @interval.setter
138
138
  def interval(self, intervals):
139
- VALID_INTERVALS = {0.25, 0.5, 1, 2, 4, 6, 8, 12}
139
+ VALID_INTERVALS = {0.25, 0.5, 1, 2, 4, 6, 8, 12, 24}
140
140
 
141
141
  for interval in intervals:
142
142
  # if an hourly interval is a string, then it is a weekDay interval
@@ -1,6 +1,7 @@
1
1
  import datetime
2
2
  import re
3
3
  from functools import wraps
4
+ from typing import Any, Container, Optional, Tuple
4
5
 
5
6
  from tableauserverclient.datetime_helpers import parse_datetime
6
7
 
@@ -65,7 +66,7 @@ def property_is_valid_time(func):
65
66
  return wrapper
66
67
 
67
68
 
68
- def property_is_int(range, allowed=None):
69
+ def property_is_int(range: Tuple[int, int], allowed: Optional[Container[Any]] = None):
69
70
  """Takes a range of ints and a list of exemptions to check against
70
71
  when setting a property on a model. The range is a tuple of (min, max) and the
71
72
  allowed list (empty by default) allows values outside that range.
@@ -89,8 +90,10 @@ def property_is_int(range, allowed=None):
89
90
  raise ValueError(error)
90
91
 
91
92
  min, max = range
93
+ if value in allowed:
94
+ return func(self, value)
92
95
 
93
- if (value < min or value > max) and (value not in allowed):
96
+ if value < min or value > max:
94
97
  raise ValueError(error)
95
98
 
96
99
  return func(self, value)
@@ -144,15 +147,7 @@ def property_is_data_acceleration_config(func):
144
147
  def wrapper(self, value):
145
148
  if not isinstance(value, dict):
146
149
  raise ValueError("{} is not type 'dict', cannot update {})".format(value.__class__.__name__, func.__name__))
147
- if len(value) != 4 or not all(
148
- attr in value.keys()
149
- for attr in (
150
- "acceleration_enabled",
151
- "accelerate_now",
152
- "last_updated_at",
153
- "acceleration_status",
154
- )
155
- ):
150
+ if len(value) < 2 or not all(attr in value.keys() for attr in ("acceleration_enabled", "accelerate_now")):
156
151
  error = "{} should have 2 keys ".format(func.__name__)
157
152
  error += "'acceleration_enabled' and 'accelerate_now'"
158
153
  error += "instead you have {}".format(value.keys())
@@ -26,6 +26,7 @@ class ScheduleItem(object):
26
26
  Subscription = "Subscription"
27
27
  DataAcceleration = "DataAcceleration"
28
28
  ActiveDirectorySync = "ActiveDirectorySync"
29
+ System = "System"
29
30
 
30
31
  class ExecutionOrder:
31
32
  Parallel = "Parallel"
@@ -18,6 +18,7 @@ class TaskItem(object):
18
18
  _TASK_TYPE_MAPPING = {
19
19
  "RefreshExtractTask": Type.ExtractRefresh,
20
20
  "MaterializeViewsTask": Type.DataAcceleration,
21
+ "RunFlowTask": Type.RunFlow,
21
22
  }
22
23
 
23
24
  def __init__(
@@ -31,6 +31,10 @@ class ViewItem(object):
31
31
  self._workbook_id: Optional[str] = None
32
32
  self._permissions: Optional[Callable[[], List[PermissionsRule]]] = None
33
33
  self.tags: Set[str] = set()
34
+ self._data_acceleration_config = {
35
+ "acceleration_enabled": None,
36
+ "acceleration_status": None,
37
+ }
34
38
 
35
39
  def __str__(self):
36
40
  return "<ViewItem {0} '{1}' contentUrl='{2}' project={3}>".format(
@@ -133,6 +137,14 @@ class ViewItem(object):
133
137
  def workbook_id(self) -> Optional[str]:
134
138
  return self._workbook_id
135
139
 
140
+ @property
141
+ def data_acceleration_config(self):
142
+ return self._data_acceleration_config
143
+
144
+ @data_acceleration_config.setter
145
+ def data_acceleration_config(self, value):
146
+ self._data_acceleration_config = value
147
+
136
148
  @property
137
149
  def permissions(self) -> List[PermissionsRule]:
138
150
  if self._permissions is None:
@@ -164,6 +176,7 @@ class ViewItem(object):
164
176
  owner_elem = view_xml.find(".//t:owner", namespaces=ns)
165
177
  project_elem = view_xml.find(".//t:project", namespaces=ns)
166
178
  tags_elem = view_xml.find(".//t:tags", namespaces=ns)
179
+ data_acceleration_config_elem = view_xml.find(".//t:dataAccelerationConfig", namespaces=ns)
167
180
  view_item._created_at = parse_datetime(view_xml.get("createdAt", None))
168
181
  view_item._updated_at = parse_datetime(view_xml.get("updatedAt", None))
169
182
  view_item._id = view_xml.get("id", None)
@@ -186,4 +199,25 @@ class ViewItem(object):
186
199
  tags = TagItem.from_xml_element(tags_elem, ns)
187
200
  view_item.tags = tags
188
201
  view_item._initial_tags = copy.copy(tags)
202
+ if data_acceleration_config_elem is not None:
203
+ data_acceleration_config = parse_data_acceleration_config(data_acceleration_config_elem)
204
+ view_item.data_acceleration_config = data_acceleration_config
189
205
  return view_item
206
+
207
+
208
+ def parse_data_acceleration_config(data_acceleration_elem):
209
+ data_acceleration_config = dict()
210
+
211
+ acceleration_enabled = data_acceleration_elem.get("accelerationEnabled", None)
212
+ if acceleration_enabled is not None:
213
+ acceleration_enabled = string_to_bool(acceleration_enabled)
214
+
215
+ acceleration_status = data_acceleration_elem.get("accelerationStatus", None)
216
+
217
+ data_acceleration_config["acceleration_enabled"] = acceleration_enabled
218
+ data_acceleration_config["acceleration_status"] = acceleration_status
219
+ return data_acceleration_config
220
+
221
+
222
+ def string_to_bool(s: str) -> bool:
223
+ return s.lower() == "true"
@@ -17,6 +17,7 @@ from .property_decorators import (
17
17
  from .revision_item import RevisionItem
18
18
  from .tag_item import TagItem
19
19
  from .view_item import ViewItem
20
+ from .data_freshness_policy_item import DataFreshnessPolicyItem
20
21
 
21
22
 
22
23
  class WorkbookItem(object):
@@ -34,7 +35,7 @@ class WorkbookItem(object):
34
35
  self._revisions = None
35
36
  self._size = None
36
37
  self._updated_at = None
37
- self._views = None
38
+ self._views: Optional[Callable[[], List[ViewItem]]] = None
38
39
  self.name = name
39
40
  self._description = None
40
41
  self.owner_id: Optional[str] = None
@@ -49,6 +50,7 @@ class WorkbookItem(object):
49
50
  "last_updated_at": None,
50
51
  "acceleration_status": None,
51
52
  }
53
+ self.data_freshness_policy = None
52
54
  self._permissions = None
53
55
 
54
56
  return None
@@ -91,6 +93,10 @@ class WorkbookItem(object):
91
93
  def description(self) -> Optional[str]:
92
94
  return self._description
93
95
 
96
+ @description.setter
97
+ def description(self, value: str):
98
+ self._description = value
99
+
94
100
  @property
95
101
  def id(self) -> Optional[str]:
96
102
  return self._id
@@ -162,6 +168,10 @@ class WorkbookItem(object):
162
168
  # We had views included in a WorkbookItem response
163
169
  return self._views
164
170
 
171
+ @views.setter
172
+ def views(self, value):
173
+ self._views = value
174
+
165
175
  @property
166
176
  def data_acceleration_config(self):
167
177
  return self._data_acceleration_config
@@ -171,6 +181,15 @@ class WorkbookItem(object):
171
181
  def data_acceleration_config(self, value):
172
182
  self._data_acceleration_config = value
173
183
 
184
+ @property
185
+ def data_freshness_policy(self):
186
+ return self._data_freshness_policy
187
+
188
+ @data_freshness_policy.setter
189
+ # @property_is_data_freshness_policy
190
+ def data_freshness_policy(self, value):
191
+ self._data_freshness_policy = value
192
+
174
193
  @property
175
194
  def revisions(self) -> List[RevisionItem]:
176
195
  if self._revisions is None:
@@ -217,8 +236,9 @@ class WorkbookItem(object):
217
236
  project_name,
218
237
  owner_id,
219
238
  _,
220
- _,
239
+ views,
221
240
  data_acceleration_config,
241
+ data_freshness_policy,
222
242
  ) = self._parse_element(workbook_xml, ns)
223
243
 
224
244
  self._set_values(
@@ -235,8 +255,9 @@ class WorkbookItem(object):
235
255
  project_name,
236
256
  owner_id,
237
257
  None,
238
- None,
258
+ views,
239
259
  data_acceleration_config,
260
+ data_freshness_policy,
240
261
  )
241
262
 
242
263
  return self
@@ -258,6 +279,7 @@ class WorkbookItem(object):
258
279
  tags,
259
280
  views,
260
281
  data_acceleration_config,
282
+ data_freshness_policy,
261
283
  ):
262
284
  if id is not None:
263
285
  self._id = id
@@ -286,10 +308,12 @@ class WorkbookItem(object):
286
308
  if tags:
287
309
  self.tags = tags
288
310
  self._initial_tags = copy.copy(tags)
289
- if views:
311
+ if views is not None:
290
312
  self._views = views
291
313
  if data_acceleration_config is not None:
292
314
  self.data_acceleration_config = data_acceleration_config
315
+ if data_freshness_policy is not None:
316
+ self.data_freshness_policy = data_freshness_policy
293
317
 
294
318
  @classmethod
295
319
  def from_response(cls, resp: str, ns: Dict[str, str]) -> List["WorkbookItem"]:
@@ -356,6 +380,11 @@ class WorkbookItem(object):
356
380
  if data_acceleration_elem is not None:
357
381
  data_acceleration_config = parse_data_acceleration_config(data_acceleration_elem)
358
382
 
383
+ data_freshness_policy = None
384
+ data_freshness_policy_elem = workbook_xml.find(".//t:dataFreshnessPolicy", namespaces=ns)
385
+ if data_freshness_policy_elem is not None:
386
+ data_freshness_policy = DataFreshnessPolicyItem.from_xml_element(data_freshness_policy_elem, ns)
387
+
359
388
  return (
360
389
  id,
361
390
  name,
@@ -372,6 +401,7 @@ class WorkbookItem(object):
372
401
  tags,
373
402
  views,
374
403
  data_acceleration_config,
404
+ data_freshness_policy,
375
405
  )
376
406
 
377
407
 
@@ -10,6 +10,7 @@ from .favorites_endpoint import Favorites
10
10
  from .fileuploads_endpoint import Fileuploads
11
11
  from .flow_runs_endpoint import FlowRuns
12
12
  from .flows_endpoint import Flows
13
+ from .flow_task_endpoint import FlowTasks
13
14
  from .groups_endpoint import Groups
14
15
  from .jobs_endpoint import Jobs
15
16
  from .metadata_endpoint import Metadata
@@ -1,4 +1,4 @@
1
- import cgi
1
+ from email.message import Message
2
2
  import copy
3
3
  import json
4
4
  import io
@@ -437,14 +437,16 @@ class Datasources(QuerysetEndpoint):
437
437
  url += "?includeExtract=False"
438
438
 
439
439
  with closing(self.get_request(url, parameters={"stream": True})) as server_response:
440
- _, params = cgi.parse_header(server_response.headers["Content-Disposition"])
440
+ m = Message()
441
+ m["Content-Disposition"] = server_response.headers["Content-Disposition"]
442
+ params = m.get_filename(failobj="")
441
443
  if isinstance(filepath, io_types_w):
442
444
  for chunk in server_response.iter_content(1024): # 1KB
443
445
  filepath.write(chunk)
444
446
  return_path = filepath
445
447
  else:
446
448
  params = fix_filename(params)
447
- filename = to_filename(os.path.basename(params["filename"]))
449
+ filename = to_filename(os.path.basename(params))
448
450
  download_path = make_download_path(filepath, filename)
449
451
  with open(download_path, "wb") as f:
450
452
  for chunk in server_response.iter_content(1024): # 1KB
@@ -1,5 +1,3 @@
1
- from threading import Thread
2
- from time import sleep
3
1
  from tableauserverclient import datetime_helpers as datetime
4
2
 
5
3
  from packaging.version import Version
@@ -76,55 +74,20 @@ class Endpoint(object):
76
74
  return parameters
77
75
 
78
76
  def _blocking_request(self, method, url, parameters={}) -> Optional[Union["Response", Exception]]:
79
- self.async_response = None
80
77
  response = None
81
78
  logger.debug("[{}] Begin blocking request to {}".format(datetime.timestamp(), url))
82
79
  try:
83
80
  response = method(url, **parameters)
84
- self.async_response = response
85
81
  logger.debug("[{}] Call finished".format(datetime.timestamp()))
86
82
  except Exception as e:
87
83
  logger.debug("Error making request to server: {}".format(e))
88
- self.async_response = e
89
- finally:
90
- if response and not self.async_response:
91
- logger.debug("Request response not saved")
92
- return None
93
- logger.debug("[{}] Request complete".format(datetime.timestamp()))
94
- return self.async_response
84
+ raise e
85
+ return response
95
86
 
96
87
  def send_request_while_show_progress_threaded(
97
88
  self, method, url, parameters={}, request_timeout=None
98
89
  ) -> Optional[Union["Response", Exception]]:
99
- try:
100
- request_thread = Thread(target=self._blocking_request, args=(method, url, parameters))
101
- request_thread.start()
102
- except Exception as e:
103
- logger.debug("Error starting server request on separate thread: {}".format(e))
104
- return None
105
- seconds = 0.05
106
- minutes = 0
107
- last_log_minute = 0
108
- sleep(seconds)
109
- if self.async_response is not None:
110
- # a quick return for any immediate responses
111
- return self.async_response
112
- timed_out: bool = request_timeout is not None and seconds > request_timeout
113
- while (self.async_response is None) and not timed_out:
114
- sleep(DELAY_SLEEP_SECONDS)
115
- seconds = seconds + DELAY_SLEEP_SECONDS
116
- minutes = int(seconds / 60)
117
- last_log_minute = self.log_wait_time(minutes, last_log_minute, url)
118
- return self.async_response
119
-
120
- def log_wait_time(self, minutes, last_log_minute, url) -> int:
121
- logger.debug("{} Waiting....".format(datetime.timestamp()))
122
- if minutes > last_log_minute: # detailed log message ~every minute
123
- logger.info("[{}] Waiting ({} minutes so far) for request to {}".format(datetime.timestamp(), minutes, url))
124
- last_log_minute = minutes
125
- else:
126
- logger.debug("[{}] Waiting for request to {}".format(datetime.timestamp(), url))
127
- return last_log_minute
90
+ return self._blocking_request(method, url, parameters)
128
91
 
129
92
  def _make_request(
130
93
  self,
@@ -0,0 +1,29 @@
1
+ import logging
2
+ from typing import List, Optional, Tuple, TYPE_CHECKING
3
+
4
+ from tableauserverclient.server.endpoint.endpoint import Endpoint, api
5
+ from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError
6
+ from tableauserverclient.models import TaskItem, PaginationItem
7
+ from tableauserverclient.server import RequestFactory
8
+
9
+ from tableauserverclient.helpers.logging import logger
10
+
11
+ if TYPE_CHECKING:
12
+ from tableauserverclient.server.request_options import RequestOptions
13
+
14
+
15
+ class FlowTasks(Endpoint):
16
+ @property
17
+ def baseurl(self) -> str:
18
+ return "{0}/sites/{1}/tasks/flows".format(self.parent_srv.baseurl, self.parent_srv.site_id)
19
+
20
+ @api(version="3.22")
21
+ def create(self, flow_item: TaskItem) -> TaskItem:
22
+ if not flow_item:
23
+ error = "No flow provided"
24
+ raise ValueError(error)
25
+ logger.info("Creating an flow task %s", flow_item)
26
+ url = self.baseurl
27
+ create_req = RequestFactory.FlowTask.create_flow_task_req(flow_item)
28
+ server_response = self.post_request(url, create_req)
29
+ return server_response.content
@@ -1,4 +1,4 @@
1
- import cgi
1
+ from email.message import Message
2
2
  import copy
3
3
  import io
4
4
  import logging
@@ -120,14 +120,16 @@ class Flows(QuerysetEndpoint):
120
120
  url = "{0}/{1}/content".format(self.baseurl, flow_id)
121
121
 
122
122
  with closing(self.get_request(url, parameters={"stream": True})) as server_response:
123
- _, params = cgi.parse_header(server_response.headers["Content-Disposition"])
123
+ m = Message()
124
+ m["Content-Disposition"] = server_response.headers["Content-Disposition"]
125
+ params = m.get_filename(failobj="")
124
126
  if isinstance(filepath, io_types_w):
125
127
  for chunk in server_response.iter_content(1024): # 1KB
126
128
  filepath.write(chunk)
127
129
  return_path = filepath
128
130
  else:
129
131
  params = fix_filename(params)
130
- filename = to_filename(os.path.basename(params["filename"]))
132
+ filename = to_filename(os.path.basename(params))
131
133
  download_path = make_download_path(filepath, filename)
132
134
  with open(download_path, "wb") as f:
133
135
  for chunk in server_response.iter_content(1024): # 1KB
@@ -1,4 +1,4 @@
1
- import cgi
1
+ from email.message import Message
2
2
  import copy
3
3
  import io
4
4
  import logging
@@ -137,7 +137,12 @@ class Workbooks(QuerysetEndpoint):
137
137
 
138
138
  # Update workbook
139
139
  @api(version="2.0")
140
- def update(self, workbook_item: WorkbookItem) -> WorkbookItem:
140
+ @parameter_added_in(include_view_acceleration_status="3.22")
141
+ def update(
142
+ self,
143
+ workbook_item: WorkbookItem,
144
+ include_view_acceleration_status: bool = False,
145
+ ) -> WorkbookItem:
141
146
  if not workbook_item.id:
142
147
  error = "Workbook item missing ID. Workbook must be retrieved from server first."
143
148
  raise MissingRequiredFieldError(error)
@@ -146,6 +151,9 @@ class Workbooks(QuerysetEndpoint):
146
151
 
147
152
  # Update the workbook itself
148
153
  url = "{0}/{1}".format(self.baseurl, workbook_item.id)
154
+ if include_view_acceleration_status:
155
+ url += "?includeViewAccelerationStatus=True"
156
+
149
157
  update_req = RequestFactory.Workbook.update_req(workbook_item)
150
158
  server_response = self.put_request(url, update_req)
151
159
  logger.info("Updated workbook item (ID: {0})".format(workbook_item.id))
@@ -483,14 +491,16 @@ class Workbooks(QuerysetEndpoint):
483
491
  url += "?includeExtract=False"
484
492
 
485
493
  with closing(self.get_request(url, parameters={"stream": True})) as server_response:
486
- _, params = cgi.parse_header(server_response.headers["Content-Disposition"])
494
+ m = Message()
495
+ m["Content-Disposition"] = server_response.headers["Content-Disposition"]
496
+ params = m.get_filename(failobj="")
487
497
  if isinstance(filepath, io_types_w):
488
498
  for chunk in server_response.iter_content(1024): # 1KB
489
499
  filepath.write(chunk)
490
500
  return_path = filepath
491
501
  else:
492
502
  params = fix_filename(params)
493
- filename = to_filename(os.path.basename(params["filename"]))
503
+ filename = to_filename(os.path.basename(params))
494
504
  download_path = make_download_path(filepath, filename)
495
505
  with open(download_path, "wb") as f:
496
506
  for chunk in server_response.iter_content(1024): # 1KB
@@ -47,7 +47,11 @@ class Pager(object):
47
47
 
48
48
  # Get the rest on demand as a generator
49
49
  while self._count < last_pagination_item.total_available:
50
- if len(current_item_list) == 0:
50
+ if (
51
+ len(current_item_list) == 0
52
+ and (last_pagination_item.page_number * last_pagination_item.page_size)
53
+ < last_pagination_item.total_available
54
+ ):
51
55
  current_item_list, last_pagination_item = self._load_next_page(last_pagination_item)
52
56
 
53
57
  try:
@@ -57,6 +57,11 @@ def _add_hiddenview_element(views_element, view_name):
57
57
  view_element.attrib["hidden"] = "true"
58
58
 
59
59
 
60
+ def _add_view_element(views_element, view_id):
61
+ view_element = ET.SubElement(views_element, "view")
62
+ view_element.attrib["id"] = view_id
63
+
64
+
60
65
  def _add_credentials_element(parent_element, connection_credentials):
61
66
  credentials_element = ET.SubElement(parent_element, "connectionCredentials")
62
67
  if connection_credentials.password is None or connection_credentials.name is None:
@@ -911,6 +916,9 @@ class WorkbookRequest(object):
911
916
  for connection in connections:
912
917
  _add_connections_element(connections_element, connection)
913
918
 
919
+ if workbook_item.description is not None:
920
+ workbook_element.attrib["description"] = workbook_item.description
921
+
914
922
  if hidden_views is not None:
915
923
  import warnings
916
924
 
@@ -941,16 +949,61 @@ class WorkbookRequest(object):
941
949
  if workbook_item.owner_id:
942
950
  owner_element = ET.SubElement(workbook_element, "owner")
943
951
  owner_element.attrib["id"] = workbook_item.owner_id
944
- if workbook_item.data_acceleration_config["acceleration_enabled"] is not None:
952
+ if workbook_item._views is not None:
953
+ views_element = ET.SubElement(workbook_element, "views")
954
+ for view in workbook_item.views:
955
+ _add_view_element(views_element, view.id)
956
+ if workbook_item.data_acceleration_config:
945
957
  data_acceleration_config = workbook_item.data_acceleration_config
946
958
  data_acceleration_element = ET.SubElement(workbook_element, "dataAccelerationConfig")
947
- data_acceleration_element.attrib["accelerationEnabled"] = str(
948
- data_acceleration_config["acceleration_enabled"]
949
- ).lower()
959
+ if data_acceleration_config["acceleration_enabled"] is not None:
960
+ data_acceleration_element.attrib["accelerationEnabled"] = str(
961
+ data_acceleration_config["acceleration_enabled"]
962
+ ).lower()
950
963
  if data_acceleration_config["accelerate_now"] is not None:
951
964
  data_acceleration_element.attrib["accelerateNow"] = str(
952
965
  data_acceleration_config["accelerate_now"]
953
966
  ).lower()
967
+ if workbook_item.data_freshness_policy is not None:
968
+ data_freshness_policy_config = workbook_item.data_freshness_policy
969
+ data_freshness_policy_element = ET.SubElement(workbook_element, "dataFreshnessPolicy")
970
+ data_freshness_policy_element.attrib["option"] = str(data_freshness_policy_config.option)
971
+ # Fresh Every Schedule
972
+ if data_freshness_policy_config.option == "FreshEvery":
973
+ if data_freshness_policy_config.fresh_every_schedule is not None:
974
+ fresh_every_element = ET.SubElement(data_freshness_policy_element, "freshEverySchedule")
975
+ fresh_every_element.attrib[
976
+ "frequency"
977
+ ] = data_freshness_policy_config.fresh_every_schedule.frequency
978
+ fresh_every_element.attrib["value"] = str(data_freshness_policy_config.fresh_every_schedule.value)
979
+ else:
980
+ raise ValueError(f"data_freshness_policy_config.fresh_every_schedule must be populated.")
981
+ # Fresh At Schedule
982
+ if data_freshness_policy_config.option == "FreshAt":
983
+ if data_freshness_policy_config.fresh_at_schedule is not None:
984
+ fresh_at_element = ET.SubElement(data_freshness_policy_element, "freshAtSchedule")
985
+ frequency = data_freshness_policy_config.fresh_at_schedule.frequency
986
+ fresh_at_element.attrib["frequency"] = frequency
987
+ fresh_at_element.attrib["time"] = str(data_freshness_policy_config.fresh_at_schedule.time)
988
+ fresh_at_element.attrib["timezone"] = str(data_freshness_policy_config.fresh_at_schedule.timezone)
989
+ intervals = data_freshness_policy_config.fresh_at_schedule.interval_item
990
+ # Fresh At Schedule intervals if Frequency is Week or Month
991
+ if frequency != DataFreshnessPolicyItem.FreshAt.Frequency.Day:
992
+ if intervals is not None:
993
+ # if intervals is not None or frequency != DataFreshnessPolicyItem.FreshAt.Frequency.Day:
994
+ intervals_element = ET.SubElement(fresh_at_element, "intervals")
995
+ for interval in intervals:
996
+ expression = IntervalItem.Occurrence.WeekDay
997
+ if frequency == DataFreshnessPolicyItem.FreshAt.Frequency.Month:
998
+ expression = IntervalItem.Occurrence.MonthDay
999
+ single_interval_element = ET.SubElement(intervals_element, "interval")
1000
+ single_interval_element.attrib[expression] = interval
1001
+ else:
1002
+ raise ValueError(
1003
+ f"fresh_at_schedule.interval_item must be populated for " f"Week & Month frequency."
1004
+ )
1005
+ else:
1006
+ raise ValueError(f"data_freshness_policy_config.fresh_at_schedule must be populated.")
954
1007
 
955
1008
  return ET.tostring(xml_request)
956
1009
 
@@ -1061,6 +1114,40 @@ class TaskRequest(object):
1061
1114
  return ET.tostring(xml_request)
1062
1115
 
1063
1116
 
1117
+ class FlowTaskRequest(object):
1118
+ @_tsrequest_wrapped
1119
+ def create_flow_task_req(self, xml_request: ET.Element, flow_item: "TaskItem") -> bytes:
1120
+ flow_element = ET.SubElement(xml_request, "runFlow")
1121
+
1122
+ # Main attributes
1123
+ flow_element.attrib["type"] = flow_item.task_type
1124
+
1125
+ if flow_item.target is not None:
1126
+ target_element = ET.SubElement(flow_element, flow_item.target.type)
1127
+ target_element.attrib["id"] = flow_item.target.id
1128
+
1129
+ if flow_item.schedule_item is None:
1130
+ return ET.tostring(xml_request)
1131
+
1132
+ # Schedule attributes
1133
+ schedule_element = ET.SubElement(xml_request, "schedule")
1134
+
1135
+ interval_item = flow_item.schedule_item.interval_item
1136
+ schedule_element.attrib["frequency"] = interval_item._frequency
1137
+ frequency_element = ET.SubElement(schedule_element, "frequencyDetails")
1138
+ frequency_element.attrib["start"] = str(interval_item.start_time)
1139
+ if hasattr(interval_item, "end_time") and interval_item.end_time is not None:
1140
+ frequency_element.attrib["end"] = str(interval_item.end_time)
1141
+ if hasattr(interval_item, "interval") and interval_item.interval:
1142
+ intervals_element = ET.SubElement(frequency_element, "intervals")
1143
+ for interval in interval_item._interval_type_pairs(): # type: ignore
1144
+ expression, value = interval
1145
+ single_interval_element = ET.SubElement(intervals_element, "interval")
1146
+ single_interval_element.attrib[expression] = value
1147
+
1148
+ return ET.tostring(xml_request)
1149
+
1150
+
1064
1151
  class SubscriptionRequest(object):
1065
1152
  @_tsrequest_wrapped
1066
1153
  def create_req(self, xml_request: ET.Element, subscription_item: "SubscriptionItem") -> bytes:
@@ -1200,6 +1287,7 @@ class RequestFactory(object):
1200
1287
  Favorite = FavoriteRequest()
1201
1288
  Fileupload = FileuploadRequest()
1202
1289
  Flow = FlowRequest()
1290
+ FlowTask = FlowTaskRequest()
1203
1291
  Group = GroupRequest()
1204
1292
  Metric = MetricRequest()
1205
1293
  Permission = PermissionRequest()
@@ -1,3 +1,7 @@
1
+ import sys
2
+
3
+ from typing_extensions import Self
4
+
1
5
  from tableauserverclient.models.property_decorators import property_is_int
2
6
  import logging
3
7
 
@@ -152,17 +156,27 @@ class _FilterOptionsBase(RequestOptionsBase):
152
156
 
153
157
  def __init__(self):
154
158
  self.view_filters = []
159
+ self.view_parameters = []
155
160
 
156
161
  def get_query_params(self):
157
162
  raise NotImplementedError()
158
163
 
159
- def vf(self, name, value):
164
+ def vf(self, name: str, value: str) -> Self:
165
+ """Apply a filter to the view for a filter that is a normal column
166
+ within the view."""
160
167
  self.view_filters.append((name, value))
161
168
  return self
162
169
 
163
- def _append_view_filters(self, params):
170
+ def parameter(self, name: str, value: str) -> Self:
171
+ """Apply a filter based on a parameter within the workbook."""
172
+ self.view_parameters.append((name, value))
173
+ return self
174
+
175
+ def _append_view_filters(self, params) -> None:
164
176
  for name, value in self.view_filters:
165
177
  params["vf_" + name] = value
178
+ for name, value in self.view_parameters:
179
+ params[name] = value
166
180
 
167
181
 
168
182
  class CSVRequestOptions(_FilterOptionsBase):
@@ -261,11 +275,13 @@ class PDFRequestOptions(_FilterOptionsBase):
261
275
  Portrait = "portrait"
262
276
  Landscape = "landscape"
263
277
 
264
- def __init__(self, page_type=None, orientation=None, maxage=-1):
278
+ def __init__(self, page_type=None, orientation=None, maxage=-1, viz_height=None, viz_width=None):
265
279
  super(PDFRequestOptions, self).__init__()
266
280
  self.page_type = page_type
267
281
  self.orientation = orientation
268
282
  self.max_age = maxage
283
+ self.viz_height = viz_height
284
+ self.viz_width = viz_width
269
285
 
270
286
  @property
271
287
  def max_age(self):
@@ -276,6 +292,24 @@ class PDFRequestOptions(_FilterOptionsBase):
276
292
  def max_age(self, value):
277
293
  self._max_age = value
278
294
 
295
+ @property
296
+ def viz_height(self):
297
+ return self._viz_height
298
+
299
+ @viz_height.setter
300
+ @property_is_int(range=(0, sys.maxsize), allowed=(None,))
301
+ def viz_height(self, value):
302
+ self._viz_height = value
303
+
304
+ @property
305
+ def viz_width(self):
306
+ return self._viz_width
307
+
308
+ @viz_width.setter
309
+ @property_is_int(range=(0, sys.maxsize), allowed=(None,))
310
+ def viz_width(self, value):
311
+ self._viz_width = value
312
+
279
313
  def get_query_params(self):
280
314
  params = {}
281
315
  if self.page_type:
@@ -287,6 +321,16 @@ class PDFRequestOptions(_FilterOptionsBase):
287
321
  if self.max_age != -1:
288
322
  params["maxAge"] = self.max_age
289
323
 
324
+ # XOR. Either both are None or both are not None.
325
+ if (self.viz_height is None) ^ (self.viz_width is None):
326
+ raise ValueError("viz_height and viz_width must be specified together")
327
+
328
+ if self.viz_height is not None:
329
+ params["vizHeight"] = self.viz_height
330
+
331
+ if self.viz_width is not None:
332
+ params["vizWidth"] = self.viz_width
333
+
290
334
  self._append_view_filters(params)
291
335
 
292
336
  return params
@@ -25,6 +25,7 @@ from .endpoint import (
25
25
  Databases,
26
26
  Tables,
27
27
  Flows,
28
+ FlowTasks,
28
29
  Webhooks,
29
30
  DataAccelerationReport,
30
31
  Favorites,
@@ -82,6 +83,7 @@ class Server(object):
82
83
  self.datasources = Datasources(self)
83
84
  self.favorites = Favorites(self)
84
85
  self.flows = Flows(self)
86
+ self.flow_tasks = FlowTasks(self)
85
87
  self.projects = Projects(self)
86
88
  self.schedules = Schedules(self)
87
89
  self.server_info = ServerInfo(self)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tableauserverclient
3
- Version: 0.29
3
+ Version: 0.30.post0.dev1
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)
@@ -41,6 +41,7 @@ Requires-Dist: defusedxml >=0.7.1
41
41
  Requires-Dist: packaging >=23.1
42
42
  Requires-Dist: requests >=2.31
43
43
  Requires-Dist: urllib3 ==2.0.7
44
+ Requires-Dist: typing-extensions >=4.0.1
44
45
  Provides-Extra: test
45
46
  Requires-Dist: argparse ; extra == 'test'
46
47
  Requires-Dist: black ==23.7 ; extra == 'test'
@@ -1,5 +1,5 @@
1
- tableauserverclient/__init__.py,sha256=hDjLTdwXPmQj4BQBRdUcnP7XdhT7apJ_c7JAPyyYZ7Y,1096
2
- tableauserverclient/_version.py,sha256=KhL5CD7tzLBzduk7x_vggt-OGe_KkxkcgCnLbAQjNho,496
1
+ tableauserverclient/__init__.py,sha256=3pYu_AmAPRRuMREy0Uq5Dk9MTFX9tIKa4Vc-j7q8iLI,1125
2
+ tableauserverclient/_version.py,sha256=jhLMneVfFrXy4GLLWUM8iJQL5D7Ngdn0QaWQhKzlIKA,507
3
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
@@ -10,13 +10,14 @@ tableauserverclient/helpers/__init__.py,sha256=llpqF9zV5dsP5hQt8dyop33Op5OzGmxW0
10
10
  tableauserverclient/helpers/headers.py,sha256=Pg-ZWSgV2Yp813BV1Tzo8F2Hn4P01zscla2RRM4yqNk,411
11
11
  tableauserverclient/helpers/logging.py,sha256=5q_oR3wERHVXFXY6XDnLYML5-HdAPJTmqhH9IZg4VfY,113
12
12
  tableauserverclient/helpers/strings.py,sha256=HRTP31HIdD3gFxDDHuBsmOl_8cLdh8fa3xLALgYsUAQ,1563
13
- tableauserverclient/models/__init__.py,sha256=NfVfRDeU8b6zTQ7wzkorhvuGIPabym8lXvNiyEX_AVg,1620
13
+ tableauserverclient/models/__init__.py,sha256=odiSv68Ct4G93dSKuhaq3dK0PuL2lF3JGrYedaaLJ1s,1684
14
14
  tableauserverclient/models/column_item.py,sha256=gV2r7xOVmcn9UnMXFOe9_ORZ85oha1KtIH-crKUhgN4,1972
15
15
  tableauserverclient/models/connection_credentials.py,sha256=8Wa8B_-cB3INFUup-Pyd5znsUD5opgYYH5TfipCzKTU,1559
16
16
  tableauserverclient/models/connection_item.py,sha256=BiLrjWY42EtkAyh2_3juo31rYW9YZN1e3uOXMXIaslY,4743
17
17
  tableauserverclient/models/custom_view_item.py,sha256=Gr2lYgBU1zUfCoaXHhqTzWDspmL33UqOxlt48Y8yaGg,5782
18
18
  tableauserverclient/models/data_acceleration_report_item.py,sha256=5Z18-fppR_lnAyPKHwxi4gOP6kqcYX3Q3RdwiD4Zy94,3402
19
19
  tableauserverclient/models/data_alert_item.py,sha256=sJpncsKY1BY-tFJKDpX7xaHy6aGvELWrorsILJxr3lI,6458
20
+ tableauserverclient/models/data_freshness_policy_item.py,sha256=p5iyGHl-juDM3ARl9tWr3i_LnpzalxUTRoDBP6FdTMo,7639
20
21
  tableauserverclient/models/database_item.py,sha256=SAwoxsgeowwFN20zGt3-226kJabk0M2tqbVLQthvmRs,8043
21
22
  tableauserverclient/models/datasource_item.py,sha256=ZZZvYfzg6gD7DlZWzfEJ3VBQBMYADJ9GeoWV02F7Z8E,11944
22
23
  tableauserverclient/models/dqw_item.py,sha256=MFpxSP6vSzpjh5GLNrom--Sbi8iLwuGLuAda8KHiIa8,3718
@@ -26,16 +27,16 @@ tableauserverclient/models/fileupload_item.py,sha256=tx3LfgRxVqeHGpCN6kTDPkdBY5i
26
27
  tableauserverclient/models/flow_item.py,sha256=rPIVDe8_tHied8TDRiGpg2gyTyQWlNiGb3vGhqF9YJw,7320
27
28
  tableauserverclient/models/flow_run_item.py,sha256=pwJNz0m7l--Sb6z0o83ebWfAmTVFYSr-0apMxHWt0Vs,3139
28
29
  tableauserverclient/models/group_item.py,sha256=1KP_KmIG1veJJGyte82aFvMAiU_tvzYtaP2vLEReLoo,3621
29
- tableauserverclient/models/interval_item.py,sha256=yDIytwU53_OhN0nytGM_GObEpmqWVeLqh3iyAbk3ZE0,9049
30
+ tableauserverclient/models/interval_item.py,sha256=NZbGNuPmFI6NhEZWZVAlWfIFbsnIYbcvxvvv9yvgfV8,9057
30
31
  tableauserverclient/models/job_item.py,sha256=PMt7G2qu8WAxe-b3f_0BtQ9JcCSvk-g5L5EcOUpYFCY,8619
31
32
  tableauserverclient/models/metric_item.py,sha256=Zrzi_Un43p2jzE_4OhetxhjfeSUYzotMfm8Hjd5WOhI,5397
32
33
  tableauserverclient/models/pagination_item.py,sha256=Hdr2EIKWkXfrjWOr-Ao_vzQTWB2akUfxxtaupFKLrlQ,1432
33
34
  tableauserverclient/models/permissions_item.py,sha256=2WlJOc1Vxkxl-WnAkFaacWsGxUXU9_d85AetSKrKwP0,3731
34
35
  tableauserverclient/models/project_item.py,sha256=DNxAhkMkRt9MLcMie4w8nuTG4sApIkt618onBEdoM_c,6915
35
- tableauserverclient/models/property_decorators.py,sha256=eXaYxzXYuHdftlcnqSQOzHYM7OAilETWK8Td2Dtmmuc,4726
36
+ tableauserverclient/models/property_decorators.py,sha256=W5EtN7bqDHrpLQF63i1cGOhPUJi0RzMJ7qmIlJVrGuE,4711
36
37
  tableauserverclient/models/reference_item.py,sha256=HddG1loGNJU7gB7hBccCjnhagnSEbk4_wY2kuPbNhRQ,534
37
38
  tableauserverclient/models/revision_item.py,sha256=fLkcScAgBBiVMCyDPk4aboyZNxrykIavzJP2vkn6Rq0,2787
38
- tableauserverclient/models/schedule_item.py,sha256=wSkOz9OUJol_aznOl66ZMDz9Q0Y-LOOc3me8X-ysV28,11324
39
+ tableauserverclient/models/schedule_item.py,sha256=Tr4uANbrE5X6gQWiN7VR3MzTYTmKQktgKTOuk3hoKeU,11350
39
40
  tableauserverclient/models/server_info_item.py,sha256=pR_fqqPuiBwpLn0wr4rQ4vxK7gxGJVTtaswzwjslheE,1880
40
41
  tableauserverclient/models/site_item.py,sha256=xvXpsR0eEz2ypS-94KJTMS-gusHV1bLomKIhWX7GXrU,42256
41
42
  tableauserverclient/models/subscription_item.py,sha256=XkQiAi_gWPBnxtTaB8TgrFa-IDVDtai9H00Ilay0T64,4744
@@ -44,35 +45,36 @@ tableauserverclient/models/tableau_auth.py,sha256=TdkOtRnNRi4p0w_WhBsWqUunNVG9nx
44
45
  tableauserverclient/models/tableau_types.py,sha256=1iq1034K1W9ugLAxON_t8n2UxSouiWV_ZEwoHiNM94c,918
45
46
  tableauserverclient/models/tag_item.py,sha256=mVfJGZG8PspPM8hBxX38heEQ_Rnp5sSwfwWdLNA-UV0,619
46
47
  tableauserverclient/models/target.py,sha256=dVc1SHwo9ZASR8250MhAPODXX_G5UqA4BnK8hykRE6E,263
47
- tableauserverclient/models/task_item.py,sha256=o0xm588Mc6WGGbYaY0eAxdYog5Z_lfWp1vripK7rKPI,3738
48
+ tableauserverclient/models/task_item.py,sha256=EHVJo6NyORC_NZ0NFZTt8ylRBCeO6LFxI1Ubf15L2zo,3775
48
49
  tableauserverclient/models/user_item.py,sha256=vaQD3nvU7bJVD6t0nkWQFse8UDz8HTp8wh_6WmCjKtM,15776
49
- tableauserverclient/models/view_item.py,sha256=7OLTLpLXDiYAWi7Tc6vSCkh2Krl7-cvKUwA7iMWZspQ,6777
50
+ tableauserverclient/models/view_item.py,sha256=OcZqrCJt0cjz1bSUUdLIseOYeDfo4ji4UWkv11el6Bc,8106
50
51
  tableauserverclient/models/webhook_item.py,sha256=LLf2HQ2Y50Rw-c4dSYyv6MXLbXdij9Iyy261gu9zpv4,2650
51
- tableauserverclient/models/workbook_item.py,sha256=x-qRFLAkVKQzd-PFYTkfuzYBkMcW3EOvc6Zx-iVWa3U,12974
52
+ tableauserverclient/models/workbook_item.py,sha256=w6RVKOPFJ9NEmz7OG0SuKw_8FrTE4rWLKw4JkHl1lag,14118
52
53
  tableauserverclient/server/__init__.py,sha256=ZyvucBvBQMPyTVW7oDHl4W9NWuaSVSMfN9cTcSQRmKw,406
53
54
  tableauserverclient/server/exceptions.py,sha256=l-O4lcoEeXJC7fLWUanIcZM_Mf8m54uK3NQuKNj3RCg,183
54
55
  tableauserverclient/server/filter.py,sha256=WZ_dIBuADRZf8VTYcRgn1rulSGUOJq8_uUltq05KkRI,1119
55
- tableauserverclient/server/pager.py,sha256=rd6rKkoTxP0u3RiOZ3HY9oWKyOcf8aowbHPcs-Ul4D0,2725
56
+ tableauserverclient/server/pager.py,sha256=JLud4Q6tyzfcoIQ5ykSL_veTuvQag-NDNfrPvoC3Bd0,2900
56
57
  tableauserverclient/server/query.py,sha256=qMweo-N5P_V9z2Or4u_QrStaBbGL6HOg4Y4EDg8uh44,5735
57
- tableauserverclient/server/request_factory.py,sha256=br5JwdVFbo9gU2kvg1DUzMnYN31i4UcdLWXwpMheMoY,59350
58
- tableauserverclient/server/request_options.py,sha256=JiibNhk9QHI1houbtb4NYD4-y9GLxC98iArZJ1E8GUQ,8553
59
- tableauserverclient/server/server.py,sha256=LhbQOk-lvIBAOF5tTAghYNPFsKJN4Z95SJbuHLAxdKo,8401
58
+ tableauserverclient/server/request_factory.py,sha256=DnZt4kYUtOmWsZxjitvxVnslTaufOapuD5iLsxWUuyc,64606
59
+ tableauserverclient/server/request_options.py,sha256=_eWeWZH9dIv0nPU2hCVjL99kIV56H2q8cm22a088iiM,9982
60
+ tableauserverclient/server/server.py,sha256=8XOze5vSEVY9Nl0WOeyYljbg2i4Yp-_znlKepCvm3Es,8458
60
61
  tableauserverclient/server/sort.py,sha256=ikndqXW2r2FeacJzybC2TVcJGn4ktviWgXwPyK-AX-0,208
61
- tableauserverclient/server/endpoint/__init__.py,sha256=9KGDtgBWlWsWbBQvQV2cbIU-VmHmMqcgtSM7PonpIzg,1141
62
+ tableauserverclient/server/endpoint/__init__.py,sha256=GcWWEKuMPdqHY2OtYYpc8LF3qkMq3m65k9DhTSqenu8,1183
62
63
  tableauserverclient/server/endpoint/auth_endpoint.py,sha256=EOGGlGHWUZRN0SFBlYnEWUSwCH1lrB0Zfv4VWdd7HQs,5139
63
64
  tableauserverclient/server/endpoint/custom_views_endpoint.py,sha256=SShsI8TnDe3tPXCrCsjs3Oy5Ym4j-QrcDXUfZ7rF9Y4,4542
64
65
  tableauserverclient/server/endpoint/data_acceleration_report_endpoint.py,sha256=nfC2gXoCke-YXGTiMWGDRBK_lDlLa9Y6hts7w3Zs8cI,1170
65
66
  tableauserverclient/server/endpoint/data_alert_endpoint.py,sha256=c6-ICGRVDRQN7ClAgCeJsk6JK4HyBvtQGQVLOzNc4eo,5358
66
67
  tableauserverclient/server/endpoint/databases_endpoint.py,sha256=0V6cHwF5E1K1oQ9JKT-rb7t1EFPNwlSKMC2HyqEqYPw,5366
67
- tableauserverclient/server/endpoint/datasources_endpoint.py,sha256=XjoPiejyCGIze6gMO_ALZGWS44vFJ8LxroYr7Xq1OXE,20407
68
+ tableauserverclient/server/endpoint/datasources_endpoint.py,sha256=6EvZc-mrOZo_qlFORBPVRzWF4srDEBdmFgw-0osxbH8,20489
68
69
  tableauserverclient/server/endpoint/default_permissions_endpoint.py,sha256=HQSYPIJwVCimuWo4YGYvsc41_-rweLHmJ-tQrWdH-Y8,4386
69
70
  tableauserverclient/server/endpoint/dqw_endpoint.py,sha256=TvzjonvIiGtp4EisoBKms1lHOkVTjagQACw6unyY9_8,2418
70
- tableauserverclient/server/endpoint/endpoint.py,sha256=jjZbvukrkomHQeAJVFbLBJgq7is0EA6a76Xzr4kCxo8,14498
71
+ tableauserverclient/server/endpoint/endpoint.py,sha256=8-XteqZmfuHDyqR_mGSXKZm9esWbeybaZByHEZoBEac,12743
71
72
  tableauserverclient/server/endpoint/exceptions.py,sha256=fh4fFYasqiKd3EPTxriVsmLkRL24wz9UvX94BMRfyLY,2534
72
73
  tableauserverclient/server/endpoint/favorites_endpoint.py,sha256=D0MCco9kxaeCShfWOnVt3UpttJRgczJmJKXb3w0jpw4,6701
73
74
  tableauserverclient/server/endpoint/fileuploads_endpoint.py,sha256=fh2xH6a8mMcBI14QzodWPBMabK1_9SMDbzZbawz-ZJc,2553
74
75
  tableauserverclient/server/endpoint/flow_runs_endpoint.py,sha256=1vwkOrb574natWl-njFOij8xaV32e9RU3-rFbu4bML0,3338
75
- tableauserverclient/server/endpoint/flows_endpoint.py,sha256=DIHQUb5LHSerYXsEmaRoiNcdeQ2tU_RaBdr23NnLG2Y,13006
76
+ tableauserverclient/server/endpoint/flow_task_endpoint.py,sha256=j3snbRiYAPkI3FwhGG-CxKogHXP3SY59ep74YPrutpw,1113
77
+ tableauserverclient/server/endpoint/flows_endpoint.py,sha256=cVboeR4IZa0Ck8OvYHkzpNzUJTvYgu7TzmF4Fd6bP20,13088
76
78
  tableauserverclient/server/endpoint/groups_endpoint.py,sha256=_aSTVy7m3NXSEqgC0cPfJt6KOaPoWprxzz0MD8CsiiY,6640
77
79
  tableauserverclient/server/endpoint/jobs_endpoint.py,sha256=P5Hdv1hsNVxd5KdrcNCfR9ZtJ7v3UgrfATCBItVdAxY,3239
78
80
  tableauserverclient/server/endpoint/metadata_endpoint.py,sha256=AKnmRNj9U85p__igiuY_vhhipsxNXpQ_9zzNUtTuU9o,5203
@@ -89,10 +91,10 @@ tableauserverclient/server/endpoint/tasks_endpoint.py,sha256=HjtSGXBGS8Eriaf5bSW
89
91
  tableauserverclient/server/endpoint/users_endpoint.py,sha256=QRoApMyP53nF-AqvC7oLHNcT4VJE8s5ykjUnI5ojmPY,7274
90
92
  tableauserverclient/server/endpoint/views_endpoint.py,sha256=GIYPftL5B0YUOb_SisFtfLP0LPIAbbUv24XCi15UH_M,7118
91
93
  tableauserverclient/server/endpoint/webhooks_endpoint.py,sha256=-HsbAuKECNcx5EZkqp097PfGHLCNwPRnxbrdzreTpnM,2835
92
- tableauserverclient/server/endpoint/workbooks_endpoint.py,sha256=_Tfaioe4QBA7VyswtxRu8uL-CzIK0wtslkwj41Wdx_4,21879
93
- tableauserverclient-0.29.dist-info/LICENSE,sha256=MMkY7MguOb4L-WCmmqrVcwg2iwSGaz-RfAMFvXRXwsQ,1074
94
- tableauserverclient-0.29.dist-info/LICENSE.versioneer,sha256=OYaGozOXk7bUNSm1z577iDYpti8a40XIqqR4lyQ47D8,334
95
- tableauserverclient-0.29.dist-info/METADATA,sha256=rLRiTqsKYowp9yVGVtZGf7_yAzrfgeLb6vULz93_V8M,4073
96
- tableauserverclient-0.29.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
97
- tableauserverclient-0.29.dist-info/top_level.txt,sha256=kBnL39G2RlGqxJSsShDBWG4WZ3NFcWJkv1EGiOfZh4Q,20
98
- tableauserverclient-0.29.dist-info/RECORD,,
94
+ tableauserverclient/server/endpoint/workbooks_endpoint.py,sha256=xWG8DJQFHLsp0DoUgE-sNdjTAy-aVFBfYASRizhdV88,22208
95
+ tableauserverclient-0.30.post0.dev1.dist-info/LICENSE,sha256=MMkY7MguOb4L-WCmmqrVcwg2iwSGaz-RfAMFvXRXwsQ,1074
96
+ tableauserverclient-0.30.post0.dev1.dist-info/LICENSE.versioneer,sha256=OYaGozOXk7bUNSm1z577iDYpti8a40XIqqR4lyQ47D8,334
97
+ tableauserverclient-0.30.post0.dev1.dist-info/METADATA,sha256=3MUzWOn6XvZdbPd9-0Z-zKUVKvgIXdrl-QxI6HQ8RyA,4125
98
+ tableauserverclient-0.30.post0.dev1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
99
+ tableauserverclient-0.30.post0.dev1.dist-info/top_level.txt,sha256=kBnL39G2RlGqxJSsShDBWG4WZ3NFcWJkv1EGiOfZh4Q,20
100
+ tableauserverclient-0.30.post0.dev1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5