tableauserverclient 0.32__py3-none-any.whl → 0.34__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 (96) hide show
  1. tableauserverclient/__init__.py +34 -18
  2. tableauserverclient/_version.py +3 -3
  3. tableauserverclient/config.py +20 -6
  4. tableauserverclient/models/__init__.py +12 -0
  5. tableauserverclient/models/column_item.py +1 -1
  6. tableauserverclient/models/connection_credentials.py +1 -1
  7. tableauserverclient/models/connection_item.py +10 -8
  8. tableauserverclient/models/custom_view_item.py +29 -6
  9. tableauserverclient/models/data_acceleration_report_item.py +2 -2
  10. tableauserverclient/models/data_alert_item.py +5 -5
  11. tableauserverclient/models/data_freshness_policy_item.py +6 -6
  12. tableauserverclient/models/database_item.py +8 -2
  13. tableauserverclient/models/datasource_item.py +10 -10
  14. tableauserverclient/models/dqw_item.py +1 -1
  15. tableauserverclient/models/favorites_item.py +5 -6
  16. tableauserverclient/models/fileupload_item.py +1 -1
  17. tableauserverclient/models/flow_item.py +12 -12
  18. tableauserverclient/models/flow_run_item.py +3 -3
  19. tableauserverclient/models/group_item.py +4 -4
  20. tableauserverclient/models/groupset_item.py +53 -0
  21. tableauserverclient/models/interval_item.py +36 -23
  22. tableauserverclient/models/job_item.py +26 -10
  23. tableauserverclient/models/linked_tasks_item.py +102 -0
  24. tableauserverclient/models/metric_item.py +5 -5
  25. tableauserverclient/models/pagination_item.py +1 -1
  26. tableauserverclient/models/permissions_item.py +19 -14
  27. tableauserverclient/models/project_item.py +35 -19
  28. tableauserverclient/models/property_decorators.py +12 -11
  29. tableauserverclient/models/reference_item.py +2 -2
  30. tableauserverclient/models/revision_item.py +3 -3
  31. tableauserverclient/models/schedule_item.py +2 -2
  32. tableauserverclient/models/server_info_item.py +26 -6
  33. tableauserverclient/models/site_item.py +69 -3
  34. tableauserverclient/models/subscription_item.py +3 -3
  35. tableauserverclient/models/table_item.py +1 -1
  36. tableauserverclient/models/tableau_auth.py +115 -5
  37. tableauserverclient/models/tableau_types.py +11 -9
  38. tableauserverclient/models/tag_item.py +3 -4
  39. tableauserverclient/models/task_item.py +4 -4
  40. tableauserverclient/models/user_item.py +47 -17
  41. tableauserverclient/models/view_item.py +11 -10
  42. tableauserverclient/models/virtual_connection_item.py +78 -0
  43. tableauserverclient/models/webhook_item.py +6 -6
  44. tableauserverclient/models/workbook_item.py +90 -12
  45. tableauserverclient/namespace.py +1 -1
  46. tableauserverclient/server/__init__.py +2 -1
  47. tableauserverclient/server/endpoint/__init__.py +8 -0
  48. tableauserverclient/server/endpoint/auth_endpoint.py +68 -11
  49. tableauserverclient/server/endpoint/custom_views_endpoint.py +124 -19
  50. tableauserverclient/server/endpoint/data_acceleration_report_endpoint.py +2 -2
  51. tableauserverclient/server/endpoint/data_alert_endpoint.py +14 -14
  52. tableauserverclient/server/endpoint/databases_endpoint.py +32 -17
  53. tableauserverclient/server/endpoint/datasources_endpoint.py +150 -59
  54. tableauserverclient/server/endpoint/default_permissions_endpoint.py +19 -18
  55. tableauserverclient/server/endpoint/dqw_endpoint.py +9 -9
  56. tableauserverclient/server/endpoint/endpoint.py +47 -31
  57. tableauserverclient/server/endpoint/exceptions.py +23 -7
  58. tableauserverclient/server/endpoint/favorites_endpoint.py +31 -31
  59. tableauserverclient/server/endpoint/fileuploads_endpoint.py +11 -13
  60. tableauserverclient/server/endpoint/flow_runs_endpoint.py +59 -17
  61. tableauserverclient/server/endpoint/flow_task_endpoint.py +2 -2
  62. tableauserverclient/server/endpoint/flows_endpoint.py +73 -35
  63. tableauserverclient/server/endpoint/groups_endpoint.py +96 -27
  64. tableauserverclient/server/endpoint/groupsets_endpoint.py +127 -0
  65. tableauserverclient/server/endpoint/jobs_endpoint.py +79 -12
  66. tableauserverclient/server/endpoint/linked_tasks_endpoint.py +45 -0
  67. tableauserverclient/server/endpoint/metadata_endpoint.py +2 -2
  68. tableauserverclient/server/endpoint/metrics_endpoint.py +10 -10
  69. tableauserverclient/server/endpoint/permissions_endpoint.py +13 -15
  70. tableauserverclient/server/endpoint/projects_endpoint.py +124 -30
  71. tableauserverclient/server/endpoint/resource_tagger.py +139 -6
  72. tableauserverclient/server/endpoint/schedules_endpoint.py +17 -18
  73. tableauserverclient/server/endpoint/server_info_endpoint.py +40 -5
  74. tableauserverclient/server/endpoint/sites_endpoint.py +282 -17
  75. tableauserverclient/server/endpoint/subscriptions_endpoint.py +10 -10
  76. tableauserverclient/server/endpoint/tables_endpoint.py +33 -19
  77. tableauserverclient/server/endpoint/tasks_endpoint.py +8 -8
  78. tableauserverclient/server/endpoint/users_endpoint.py +405 -19
  79. tableauserverclient/server/endpoint/views_endpoint.py +111 -25
  80. tableauserverclient/server/endpoint/virtual_connections_endpoint.py +174 -0
  81. tableauserverclient/server/endpoint/webhooks_endpoint.py +11 -11
  82. tableauserverclient/server/endpoint/workbooks_endpoint.py +735 -68
  83. tableauserverclient/server/filter.py +2 -2
  84. tableauserverclient/server/pager.py +8 -10
  85. tableauserverclient/server/query.py +70 -20
  86. tableauserverclient/server/request_factory.py +213 -41
  87. tableauserverclient/server/request_options.py +125 -145
  88. tableauserverclient/server/server.py +73 -9
  89. tableauserverclient/server/sort.py +2 -2
  90. {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/METADATA +17 -17
  91. tableauserverclient-0.34.dist-info/RECORD +106 -0
  92. {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/WHEEL +1 -1
  93. tableauserverclient-0.32.dist-info/RECORD +0 -100
  94. {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/LICENSE +0 -0
  95. {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/LICENSE.versioneer +0 -0
  96. {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/top_level.txt +0 -0
@@ -1,22 +1,22 @@
1
1
  import copy
2
2
  import datetime
3
3
  import xml.etree.ElementTree as ET
4
- from typing import List, Optional, Set
4
+ from typing import Optional
5
5
 
6
6
  from defusedxml.ElementTree import fromstring
7
7
 
8
8
  from tableauserverclient.datetime_helpers import parse_datetime
9
- from .connection_item import ConnectionItem
10
- from .dqw_item import DQWItem
11
- from .exceptions import UnpopulatedPropertyError
12
- from .permissions_item import Permission
13
- from .property_decorators import property_not_nullable
14
- from .tag_item import TagItem
9
+ from tableauserverclient.models.connection_item import ConnectionItem
10
+ from tableauserverclient.models.dqw_item import DQWItem
11
+ from tableauserverclient.models.exceptions import UnpopulatedPropertyError
12
+ from tableauserverclient.models.permissions_item import Permission
13
+ from tableauserverclient.models.property_decorators import property_not_nullable
14
+ from tableauserverclient.models.tag_item import TagItem
15
15
 
16
16
 
17
- class FlowItem(object):
17
+ class FlowItem:
18
18
  def __repr__(self):
19
- return "<Flow {0} '{1}' ({2}) Project={3} createdAt={4}".format(
19
+ return "<Flow {} '{}' ({}) Project={} createdAt={}".format(
20
20
  self._id, self.name, self.description, self.project_id, self.created_at
21
21
  )
22
22
 
@@ -24,13 +24,13 @@ class FlowItem(object):
24
24
  self._webpage_url: Optional[str] = None
25
25
  self._created_at: Optional[datetime.datetime] = None
26
26
  self._id: Optional[str] = None
27
- self._initial_tags: Set[str] = set()
27
+ self._initial_tags: set[str] = set()
28
28
  self._project_name: Optional[str] = None
29
29
  self._updated_at: Optional[datetime.datetime] = None
30
30
  self.name: Optional[str] = name
31
31
  self.owner_id: Optional[str] = None
32
32
  self.project_id: str = project_id
33
- self.tags: Set[str] = set()
33
+ self.tags: set[str] = set()
34
34
  self.description: Optional[str] = None
35
35
 
36
36
  self._connections: Optional[ConnectionItem] = None
@@ -170,7 +170,7 @@ class FlowItem(object):
170
170
  self.owner_id = owner_id
171
171
 
172
172
  @classmethod
173
- def from_response(cls, resp, ns) -> List["FlowItem"]:
173
+ def from_response(cls, resp, ns) -> list["FlowItem"]:
174
174
  all_flow_items = list()
175
175
  parsed_response = fromstring(resp)
176
176
  all_flow_xml = parsed_response.findall(".//t:flow", namespaces=ns)
@@ -1,13 +1,13 @@
1
1
  import itertools
2
2
  from datetime import datetime
3
- from typing import Dict, List, Optional, Type
3
+ from typing import Optional
4
4
 
5
5
  from defusedxml.ElementTree import fromstring
6
6
 
7
7
  from tableauserverclient.datetime_helpers import parse_datetime
8
8
 
9
9
 
10
- class FlowRunItem(object):
10
+ class FlowRunItem:
11
11
  def __init__(self) -> None:
12
12
  self._id: str = ""
13
13
  self._flow_id: Optional[str] = None
@@ -71,7 +71,7 @@ class FlowRunItem(object):
71
71
  self._background_job_id = background_job_id
72
72
 
73
73
  @classmethod
74
- def from_response(cls: Type["FlowRunItem"], resp: bytes, ns: Optional[Dict]) -> List["FlowRunItem"]:
74
+ def from_response(cls: type["FlowRunItem"], resp: bytes, ns: Optional[dict]) -> list["FlowRunItem"]:
75
75
  all_flowrun_items = list()
76
76
  parsed_response = fromstring(resp)
77
77
  all_flowrun_xml = itertools.chain(
@@ -1,4 +1,4 @@
1
- from typing import Callable, List, Optional, TYPE_CHECKING
1
+ from typing import Callable, Optional, TYPE_CHECKING
2
2
 
3
3
  from defusedxml.ElementTree import fromstring
4
4
 
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
11
11
  from tableauserverclient.server import Pager
12
12
 
13
13
 
14
- class GroupItem(object):
14
+ class GroupItem:
15
15
  tag_name: str = "group"
16
16
 
17
17
  class LicenseMode:
@@ -27,7 +27,7 @@ class GroupItem(object):
27
27
  self.domain_name: Optional[str] = domain_name
28
28
 
29
29
  def __repr__(self):
30
- return "{}({!r})".format(self.__class__.__name__, self.__dict__)
30
+ return f"{self.__class__.__name__}({self.__dict__!r})"
31
31
 
32
32
  @property
33
33
  def domain_name(self) -> Optional[str]:
@@ -79,7 +79,7 @@ class GroupItem(object):
79
79
  self._users = users
80
80
 
81
81
  @classmethod
82
- def from_response(cls, resp, ns) -> List["GroupItem"]:
82
+ def from_response(cls, resp, ns) -> list["GroupItem"]:
83
83
  all_group_items = list()
84
84
  parsed_response = fromstring(resp)
85
85
  all_group_xml = parsed_response.findall(".//t:group", namespaces=ns)
@@ -0,0 +1,53 @@
1
+ from typing import Optional
2
+ import xml.etree.ElementTree as ET
3
+
4
+ from defusedxml.ElementTree import fromstring
5
+
6
+ from tableauserverclient.models.group_item import GroupItem
7
+ from tableauserverclient.models.reference_item import ResourceReference
8
+
9
+
10
+ class GroupSetItem:
11
+ tag_name: str = "groupSet"
12
+
13
+ def __init__(self, name: Optional[str] = None) -> None:
14
+ self.name = name
15
+ self.id: Optional[str] = None
16
+ self.groups: list["GroupItem"] = []
17
+ self.group_count: int = 0
18
+
19
+ def __str__(self) -> str:
20
+ name = self.name
21
+ id = self.id
22
+ return f"<{self.__class__.__qualname__}({name=}, {id=})>"
23
+
24
+ def __repr__(self) -> str:
25
+ return self.__str__()
26
+
27
+ @classmethod
28
+ def from_response(cls, response: bytes, ns: dict[str, str]) -> list["GroupSetItem"]:
29
+ parsed_response = fromstring(response)
30
+ all_groupset_xml = parsed_response.findall(".//t:groupSet", namespaces=ns)
31
+ return [cls.from_xml(xml, ns) for xml in all_groupset_xml]
32
+
33
+ @classmethod
34
+ def from_xml(cls, groupset_xml: ET.Element, ns: dict[str, str]) -> "GroupSetItem":
35
+ def get_group(group_xml: ET.Element) -> GroupItem:
36
+ group_item = GroupItem()
37
+ group_item._id = group_xml.get("id")
38
+ group_item.name = group_xml.get("name")
39
+ return group_item
40
+
41
+ group_set_item = cls()
42
+ group_set_item.name = groupset_xml.get("name")
43
+ group_set_item.id = groupset_xml.get("id")
44
+ group_set_item.group_count = int(count) if (count := groupset_xml.get("groupCount")) else 0
45
+ group_set_item.groups = [
46
+ get_group(group_xml) for group_xml in groupset_xml.findall(".//t:group", namespaces=ns)
47
+ ]
48
+
49
+ return group_set_item
50
+
51
+ @staticmethod
52
+ def as_reference(id_: str) -> ResourceReference:
53
+ return ResourceReference(id_, GroupSetItem.tag_name)
@@ -1,7 +1,7 @@
1
1
  from .property_decorators import property_is_valid_time, property_not_nullable
2
2
 
3
3
 
4
- class IntervalItem(object):
4
+ class IntervalItem:
5
5
  class Frequency:
6
6
  Hourly = "Hourly"
7
7
  Daily = "Daily"
@@ -25,7 +25,7 @@ class IntervalItem(object):
25
25
  LastDay = "LastDay"
26
26
 
27
27
 
28
- class HourlyInterval(object):
28
+ class HourlyInterval:
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
@@ -73,12 +73,12 @@ class HourlyInterval(object):
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):
76
- error = "Invalid weekDay interval {}".format(interval)
76
+ error = f"Invalid weekDay interval {interval}"
77
77
  raise ValueError(error)
78
78
 
79
79
  # if an hourly interval is a number, it is an hours or minutes interval
80
80
  if isinstance(interval, (int, float)) and float(interval) not in VALID_INTERVALS:
81
- error = "Invalid interval {} not in {}".format(interval, str(VALID_INTERVALS))
81
+ error = f"Invalid interval {interval} not in {str(VALID_INTERVALS)}"
82
82
  raise ValueError(error)
83
83
 
84
84
  self._interval = intervals
@@ -108,7 +108,7 @@ class HourlyInterval(object):
108
108
  return interval_type_pairs
109
109
 
110
110
 
111
- class DailyInterval(object):
111
+ class DailyInterval:
112
112
  def __init__(self, start_time, *interval_values):
113
113
  self.start_time = start_time
114
114
  self.interval = interval_values
@@ -141,12 +141,12 @@ class DailyInterval(object):
141
141
  for interval in intervals:
142
142
  # if an hourly interval is a string, then it is a weekDay interval
143
143
  if isinstance(interval, str) and not interval.isnumeric() and not hasattr(IntervalItem.Day, interval):
144
- error = "Invalid weekDay interval {}".format(interval)
144
+ error = f"Invalid weekDay interval {interval}"
145
145
  raise ValueError(error)
146
146
 
147
147
  # if an hourly interval is a number, it is an hours or minutes interval
148
148
  if isinstance(interval, (int, float)) and float(interval) not in VALID_INTERVALS:
149
- error = "Invalid interval {} not in {}".format(interval, str(VALID_INTERVALS))
149
+ error = f"Invalid interval {interval} not in {str(VALID_INTERVALS)}"
150
150
  raise ValueError(error)
151
151
 
152
152
  self._interval = intervals
@@ -176,7 +176,7 @@ class DailyInterval(object):
176
176
  return interval_type_pairs
177
177
 
178
178
 
179
- class WeeklyInterval(object):
179
+ class WeeklyInterval:
180
180
  def __init__(self, start_time, *interval_values):
181
181
  self.start_time = start_time
182
182
  self.interval = interval_values
@@ -213,7 +213,7 @@ class WeeklyInterval(object):
213
213
  return [(IntervalItem.Occurrence.WeekDay, day) for day in self.interval]
214
214
 
215
215
 
216
- class MonthlyInterval(object):
216
+ class MonthlyInterval:
217
217
  def __init__(self, start_time, interval_value):
218
218
  self.start_time = start_time
219
219
 
@@ -246,21 +246,34 @@ class MonthlyInterval(object):
246
246
 
247
247
  @interval.setter
248
248
  def interval(self, interval_values):
249
- # This is weird because the value could be a str or an int
250
- # The only valid str is 'LastDay' so we check that first. If that's not it
251
- # try to convert it to an int, if that fails because it's an incorrect string
252
- # like 'badstring' we catch and re-raise. Otherwise we convert to int and check
253
- # that it's in range 1-31
249
+ # Valid monthly intervals strings can contain any of the following
250
+ # day numbers (1-31) (integer or string)
251
+ # relative day within the month (First, Second, ... Last)
252
+ # week days (Sunday, Monday, ... LastDay)
253
+ VALID_INTERVALS = [
254
+ "Sunday",
255
+ "Monday",
256
+ "Tuesday",
257
+ "Wednesday",
258
+ "Thursday",
259
+ "Friday",
260
+ "Saturday",
261
+ "LastDay",
262
+ "First",
263
+ "Second",
264
+ "Third",
265
+ "Fourth",
266
+ "Fifth",
267
+ "Last",
268
+ ]
269
+ for value in range(1, 32):
270
+ VALID_INTERVALS.append(str(value))
271
+ VALID_INTERVALS.append(value)
272
+
254
273
  for interval_value in interval_values:
255
- error = "Invalid interval value for a monthly frequency: {}.".format(interval_value)
256
-
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)
274
+ if interval_value not in VALID_INTERVALS:
275
+ error = f"Invalid monthly interval: {interval_value}"
276
+ raise ValueError(error)
264
277
 
265
278
  self._interval = interval_values
266
279
 
@@ -1,13 +1,13 @@
1
1
  import datetime
2
- from typing import List, Optional
2
+ from typing import Optional
3
3
 
4
4
  from defusedxml.ElementTree import fromstring
5
5
 
6
6
  from tableauserverclient.datetime_helpers import parse_datetime
7
- from .flow_run_item import FlowRunItem
7
+ from tableauserverclient.models.flow_run_item import FlowRunItem
8
8
 
9
9
 
10
- class JobItem(object):
10
+ class JobItem:
11
11
  class FinishCode:
12
12
  """
13
13
  Status codes as documented on
@@ -27,12 +27,14 @@ class JobItem(object):
27
27
  started_at: Optional[datetime.datetime] = None,
28
28
  completed_at: Optional[datetime.datetime] = None,
29
29
  finish_code: int = 0,
30
- notes: Optional[List[str]] = None,
30
+ notes: Optional[list[str]] = None,
31
31
  mode: Optional[str] = None,
32
32
  workbook_id: Optional[str] = None,
33
33
  datasource_id: Optional[str] = None,
34
34
  flow_run: Optional[FlowRunItem] = None,
35
35
  updated_at: Optional[datetime.datetime] = None,
36
+ workbook_name: Optional[str] = None,
37
+ datasource_name: Optional[str] = None,
36
38
  ):
37
39
  self._id = id_
38
40
  self._type = job_type
@@ -41,12 +43,14 @@ class JobItem(object):
41
43
  self._started_at = started_at
42
44
  self._completed_at = completed_at
43
45
  self._finish_code = finish_code
44
- self._notes: List[str] = notes or []
46
+ self._notes: list[str] = notes or []
45
47
  self._mode = mode
46
48
  self._workbook_id = workbook_id
47
49
  self._datasource_id = datasource_id
48
50
  self._flow_run = flow_run
49
51
  self._updated_at = updated_at
52
+ self._workbook_name = workbook_name
53
+ self._datasource_name = datasource_name
50
54
 
51
55
  @property
52
56
  def id(self) -> str:
@@ -77,7 +81,7 @@ class JobItem(object):
77
81
  return self._finish_code
78
82
 
79
83
  @property
80
- def notes(self) -> List[str]:
84
+ def notes(self) -> list[str]:
81
85
  return self._notes
82
86
 
83
87
  @property
@@ -117,6 +121,14 @@ class JobItem(object):
117
121
  def updated_at(self) -> Optional[datetime.datetime]:
118
122
  return self._updated_at
119
123
 
124
+ @property
125
+ def workbook_name(self) -> Optional[str]:
126
+ return self._workbook_name
127
+
128
+ @property
129
+ def datasource_name(self) -> Optional[str]:
130
+ return self._datasource_name
131
+
120
132
  def __str__(self):
121
133
  return (
122
134
  "<Job#{_id} {_type} created_at({_created_at}) started_at({_started_at}) updated_at({_updated_at}) completed_at({_completed_at})"
@@ -127,7 +139,7 @@ class JobItem(object):
127
139
  return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
128
140
 
129
141
  @classmethod
130
- def from_response(cls, xml, ns) -> List["JobItem"]:
142
+ def from_response(cls, xml, ns) -> list["JobItem"]:
131
143
  parsed_response = fromstring(xml)
132
144
  all_tasks_xml = parsed_response.findall(".//t:job", namespaces=ns)
133
145
 
@@ -148,8 +160,10 @@ class JobItem(object):
148
160
  mode = element.get("mode", None)
149
161
  workbook = element.find(".//t:workbook[@id]", namespaces=ns)
150
162
  workbook_id = workbook.get("id") if workbook is not None else None
163
+ workbook_name = workbook.get("name") if workbook is not None else None
151
164
  datasource = element.find(".//t:datasource[@id]", namespaces=ns)
152
165
  datasource_id = datasource.get("id") if datasource is not None else None
166
+ datasource_name = datasource.get("name") if datasource is not None else None
153
167
  flow_run = None
154
168
  updated_at = parse_datetime(element.get("updatedAt", None))
155
169
  for flow_job in element.findall(".//t:runFlowJobType", namespaces=ns):
@@ -172,10 +186,12 @@ class JobItem(object):
172
186
  datasource_id,
173
187
  flow_run,
174
188
  updated_at,
189
+ workbook_name,
190
+ datasource_name,
175
191
  )
176
192
 
177
193
 
178
- class BackgroundJobItem(object):
194
+ class BackgroundJobItem:
179
195
  class Status:
180
196
  Pending: str = "Pending"
181
197
  InProgress: str = "InProgress"
@@ -206,7 +222,7 @@ class BackgroundJobItem(object):
206
222
  self._subtitle = subtitle
207
223
 
208
224
  def __str__(self):
209
- return f"<{self.__class__.name} {self._id} {self._type}>"
225
+ return f"<{self.__class__.__qualname__} {self._id} {self._type}>"
210
226
 
211
227
  def __repr__(self):
212
228
  return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
@@ -254,7 +270,7 @@ class BackgroundJobItem(object):
254
270
  return self._priority
255
271
 
256
272
  @classmethod
257
- def from_response(cls, xml, ns) -> List["BackgroundJobItem"]:
273
+ def from_response(cls, xml, ns) -> list["BackgroundJobItem"]:
258
274
  parsed_response = fromstring(xml)
259
275
  all_tasks_xml = parsed_response.findall(".//t:backgroundJob", namespaces=ns)
260
276
  return [cls._parse_element(x, ns) for x in all_tasks_xml]
@@ -0,0 +1,102 @@
1
+ import datetime as dt
2
+ from typing import Optional
3
+
4
+ from defusedxml.ElementTree import fromstring
5
+
6
+ from tableauserverclient.datetime_helpers import parse_datetime
7
+ from tableauserverclient.models.schedule_item import ScheduleItem
8
+
9
+
10
+ class LinkedTaskItem:
11
+ def __init__(self) -> None:
12
+ self.id: Optional[str] = None
13
+ self.num_steps: Optional[int] = None
14
+ self.schedule: Optional[ScheduleItem] = None
15
+
16
+ @classmethod
17
+ def from_response(cls, resp: bytes, namespace) -> list["LinkedTaskItem"]:
18
+ parsed_response = fromstring(resp)
19
+ return [
20
+ cls._parse_element(x, namespace)
21
+ for x in parsed_response.findall(".//t:linkedTasks[@id]", namespaces=namespace)
22
+ ]
23
+
24
+ @classmethod
25
+ def _parse_element(cls, xml, namespace) -> "LinkedTaskItem":
26
+ task = cls()
27
+ task.id = xml.get("id")
28
+ task.num_steps = int(xml.get("numSteps"))
29
+ task.schedule = ScheduleItem.from_element(xml, namespace)[0]
30
+ return task
31
+
32
+
33
+ class LinkedTaskStepItem:
34
+ def __init__(self) -> None:
35
+ self.id: Optional[str] = None
36
+ self.step_number: Optional[int] = None
37
+ self.stop_downstream_on_failure: Optional[bool] = None
38
+ self.task_details: list[LinkedTaskFlowRunItem] = []
39
+
40
+ @classmethod
41
+ def from_task_xml(cls, xml, namespace) -> list["LinkedTaskStepItem"]:
42
+ return [cls._parse_element(x, namespace) for x in xml.findall(".//t:linkedTaskSteps[@id]", namespace)]
43
+
44
+ @classmethod
45
+ def _parse_element(cls, xml, namespace) -> "LinkedTaskStepItem":
46
+ step = cls()
47
+ step.id = xml.get("id")
48
+ step.step_number = int(xml.get("stepNumber"))
49
+ step.stop_downstream_on_failure = string_to_bool(xml.get("stopDownstreamTasksOnFailure"))
50
+ step.task_details = LinkedTaskFlowRunItem._parse_element(xml, namespace)
51
+ return step
52
+
53
+
54
+ class LinkedTaskFlowRunItem:
55
+ def __init__(self) -> None:
56
+ self.flow_run_id: Optional[str] = None
57
+ self.flow_run_priority: Optional[int] = None
58
+ self.flow_run_consecutive_failed_count: Optional[int] = None
59
+ self.flow_run_task_type: Optional[str] = None
60
+ self.flow_id: Optional[str] = None
61
+ self.flow_name: Optional[str] = None
62
+
63
+ @classmethod
64
+ def _parse_element(cls, xml, namespace) -> list["LinkedTaskFlowRunItem"]:
65
+ all_tasks = []
66
+ for flow_run in xml.findall(".//t:flowRun[@id]", namespace):
67
+ task = cls()
68
+ task.flow_run_id = flow_run.get("id")
69
+ task.flow_run_priority = int(flow_run.get("priority"))
70
+ task.flow_run_consecutive_failed_count = int(flow_run.get("consecutiveFailedCount"))
71
+ task.flow_run_task_type = flow_run.get("type")
72
+ flow = flow_run.find(".//t:flow[@id]", namespace)
73
+ task.flow_id = flow.get("id")
74
+ task.flow_name = flow.get("name")
75
+ all_tasks.append(task)
76
+
77
+ return all_tasks
78
+
79
+
80
+ class LinkedTaskJobItem:
81
+ def __init__(self) -> None:
82
+ self.id: Optional[str] = None
83
+ self.linked_task_id: Optional[str] = None
84
+ self.status: Optional[str] = None
85
+ self.created_at: Optional[dt.datetime] = None
86
+
87
+ @classmethod
88
+ def from_response(cls, resp: bytes, namespace) -> "LinkedTaskJobItem":
89
+ parsed_response = fromstring(resp)
90
+ job = cls()
91
+ job_xml = parsed_response.find(".//t:linkedTaskJob[@id]", namespaces=namespace)
92
+ if job_xml is None:
93
+ raise ValueError("No linked task job found in response")
94
+ job.id = job_xml.get("id")
95
+ job.linked_task_id = job_xml.get("linkedTaskId")
96
+ job.status = job_xml.get("status")
97
+ job.created_at = parse_datetime(job_xml.get("createdAt"))
98
+ return job
99
+
100
+
101
+ def string_to_bool(s: str) -> bool:
102
+ return s.lower() == "true"
@@ -1,6 +1,6 @@
1
1
  import xml.etree.ElementTree as ET
2
2
  from datetime import datetime
3
- from typing import List, Optional, Set
3
+ from typing import Optional
4
4
 
5
5
  from tableauserverclient.datetime_helpers import parse_datetime
6
6
  from .property_decorators import property_is_boolean, property_is_datetime
@@ -8,7 +8,7 @@ from .tag_item import TagItem
8
8
  from .permissions_item import Permission
9
9
 
10
10
 
11
- class MetricItem(object):
11
+ class MetricItem:
12
12
  def __init__(self, name: Optional[str] = None):
13
13
  self._id: Optional[str] = None
14
14
  self._name: Optional[str] = name
@@ -21,8 +21,8 @@ class MetricItem(object):
21
21
  self._project_name: Optional[str] = None
22
22
  self._owner_id: Optional[str] = None
23
23
  self._view_id: Optional[str] = None
24
- self._initial_tags: Set[str] = set()
25
- self.tags: Set[str] = set()
24
+ self._initial_tags: set[str] = set()
25
+ self.tags: set[str] = set()
26
26
  self._permissions: Optional[Permission] = None
27
27
 
28
28
  @property
@@ -126,7 +126,7 @@ class MetricItem(object):
126
126
  cls,
127
127
  resp: bytes,
128
128
  ns,
129
- ) -> List["MetricItem"]:
129
+ ) -> list["MetricItem"]:
130
130
  all_metric_items = list()
131
131
  parsed_response = ET.fromstring(resp)
132
132
  all_metric_xml = parsed_response.findall(".//t:metric", namespaces=ns)
@@ -1,7 +1,7 @@
1
1
  from defusedxml.ElementTree import fromstring
2
2
 
3
3
 
4
- class PaginationItem(object):
4
+ class PaginationItem:
5
5
  def __init__(self):
6
6
  self._page_number = None
7
7
  self._page_size = None
@@ -1,12 +1,13 @@
1
1
  import xml.etree.ElementTree as ET
2
- from typing import Dict, List, Optional
2
+ from typing import Optional
3
3
 
4
4
  from defusedxml.ElementTree import fromstring
5
5
 
6
- from .exceptions import UnknownGranteeTypeError, UnpopulatedPropertyError
7
- from .group_item import GroupItem
8
- from .reference_item import ResourceReference
9
- from .user_item import UserItem
6
+ from tableauserverclient.models.exceptions import UnknownGranteeTypeError, UnpopulatedPropertyError
7
+ from tableauserverclient.models.group_item import GroupItem
8
+ from tableauserverclient.models.groupset_item import GroupSetItem
9
+ from tableauserverclient.models.reference_item import ResourceReference
10
+ from tableauserverclient.models.user_item import UserItem
10
11
 
11
12
  from tableauserverclient.helpers.logging import logger
12
13
 
@@ -35,23 +36,25 @@ class Permission:
35
36
  ShareView = "ShareView"
36
37
  ViewComments = "ViewComments"
37
38
  ViewUnderlyingData = "ViewUnderlyingData"
39
+ VizqlDataApiAccess = "VizqlDataApiAccess"
38
40
  WebAuthoring = "WebAuthoring"
39
41
  Write = "Write"
40
42
  RunExplainData = "RunExplainData"
41
43
  CreateRefreshMetrics = "CreateRefreshMetrics"
42
44
  SaveAs = "SaveAs"
45
+ PulseMetricDefine = "PulseMetricDefine"
43
46
 
44
47
  def __repr__(self):
45
48
  return "<Enum Capability: AddComment | ChangeHierarchy | ChangePermission ... (17 more) >"
46
49
 
47
50
 
48
51
  class PermissionsRule:
49
- def __init__(self, grantee: ResourceReference, capabilities: Dict[str, str]) -> None:
52
+ def __init__(self, grantee: ResourceReference, capabilities: dict[str, str]) -> None:
50
53
  self.grantee = grantee
51
54
  self.capabilities = capabilities
52
55
 
53
56
  def __repr__(self):
54
- return "<PermissionsRule grantee={}, capabilities={}>".format(self.grantee, self.capabilities)
57
+ return f"<PermissionsRule grantee={self.grantee}, capabilities={self.capabilities}>"
55
58
 
56
59
  def __eq__(self, other: object) -> bool:
57
60
  if not hasattr(other, "grantee") or not hasattr(other, "capabilities"):
@@ -65,7 +68,7 @@ class PermissionsRule:
65
68
  if self.capabilities == other.capabilities:
66
69
  return self
67
70
 
68
- capabilities = set((*self.capabilities.keys(), *other.capabilities.keys()))
71
+ capabilities = {*self.capabilities.keys(), *other.capabilities.keys()}
69
72
  new_capabilities = {}
70
73
  for capability in capabilities:
71
74
  if (self.capabilities.get(capability), other.capabilities.get(capability)) == (
@@ -85,7 +88,7 @@ class PermissionsRule:
85
88
  if self.capabilities == other.capabilities:
86
89
  return self
87
90
 
88
- capabilities = set((*self.capabilities.keys(), *other.capabilities.keys()))
91
+ capabilities = {*self.capabilities.keys(), *other.capabilities.keys()}
89
92
  new_capabilities = {}
90
93
  for capability in capabilities:
91
94
  if Permission.Mode.Allow in (self.capabilities.get(capability), other.capabilities.get(capability)):
@@ -99,14 +102,14 @@ class PermissionsRule:
99
102
  return PermissionsRule(self.grantee, new_capabilities)
100
103
 
101
104
  @classmethod
102
- def from_response(cls, resp, ns=None) -> List["PermissionsRule"]:
105
+ def from_response(cls, resp, ns=None) -> list["PermissionsRule"]:
103
106
  parsed_response = fromstring(resp)
104
107
 
105
108
  rules = []
106
109
  permissions_rules_list_xml = parsed_response.findall(".//t:granteeCapabilities", namespaces=ns)
107
110
 
108
111
  for grantee_capability_xml in permissions_rules_list_xml:
109
- capability_dict: Dict[str, str] = {}
112
+ capability_dict: dict[str, str] = {}
110
113
 
111
114
  grantee = PermissionsRule._parse_grantee_element(grantee_capability_xml, ns)
112
115
 
@@ -115,7 +118,7 @@ class PermissionsRule:
115
118
  mode = capability_xml.get("mode")
116
119
 
117
120
  if name is None or mode is None:
118
- logger.error("Capability was not valid: {}".format(capability_xml))
121
+ logger.error(f"Capability was not valid: {capability_xml}")
119
122
  raise UnpopulatedPropertyError()
120
123
  else:
121
124
  capability_dict[name] = mode
@@ -126,7 +129,7 @@ class PermissionsRule:
126
129
  return rules
127
130
 
128
131
  @staticmethod
129
- def _parse_grantee_element(grantee_capability_xml: ET.Element, ns: Optional[Dict[str, str]]) -> ResourceReference:
132
+ def _parse_grantee_element(grantee_capability_xml: ET.Element, ns: Optional[dict[str, str]]) -> ResourceReference:
130
133
  """Use Xpath magic and some string splitting to get the right object type from the xml"""
131
134
 
132
135
  # Get the first element in the tree with an 'id' attribute
@@ -142,7 +145,9 @@ class PermissionsRule:
142
145
  grantee = UserItem.as_reference(grantee_id)
143
146
  elif grantee_type == "group":
144
147
  grantee = GroupItem.as_reference(grantee_id)
148
+ elif grantee_type == "groupSet":
149
+ grantee = GroupSetItem.as_reference(grantee_id)
145
150
  else:
146
- raise UnknownGranteeTypeError("No support for grantee type of {}".format(grantee_type))
151
+ raise UnknownGranteeTypeError(f"No support for grantee type of {grantee_type}")
147
152
 
148
153
  return grantee