tableauserverclient 0.36__py3-none-any.whl → 0.38__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 (45) hide show
  1. tableauserverclient/__init__.py +6 -0
  2. tableauserverclient/bin/_version.py +3 -3
  3. tableauserverclient/helpers/strings.py +25 -1
  4. tableauserverclient/models/__init__.py +6 -1
  5. tableauserverclient/models/connection_item.py +3 -3
  6. tableauserverclient/models/datasource_item.py +218 -23
  7. tableauserverclient/models/extract_item.py +82 -0
  8. tableauserverclient/models/flow_item.py +2 -2
  9. tableauserverclient/models/group_item.py +11 -0
  10. tableauserverclient/models/interval_item.py +40 -0
  11. tableauserverclient/models/job_item.py +1 -0
  12. tableauserverclient/models/location_item.py +53 -0
  13. tableauserverclient/models/project_item.py +138 -27
  14. tableauserverclient/models/schedule_item.py +57 -0
  15. tableauserverclient/models/site_item.py +28 -0
  16. tableauserverclient/models/table_item.py +7 -3
  17. tableauserverclient/models/tableau_types.py +13 -1
  18. tableauserverclient/models/user_item.py +101 -1
  19. tableauserverclient/models/view_item.py +79 -5
  20. tableauserverclient/models/workbook_item.py +151 -1
  21. tableauserverclient/server/__init__.py +2 -0
  22. tableauserverclient/server/endpoint/databases_endpoint.py +101 -18
  23. tableauserverclient/server/endpoint/datasources_endpoint.py +562 -7
  24. tableauserverclient/server/endpoint/dqw_endpoint.py +16 -6
  25. tableauserverclient/server/endpoint/endpoint.py +39 -0
  26. tableauserverclient/server/endpoint/exceptions.py +4 -0
  27. tableauserverclient/server/endpoint/fileuploads_endpoint.py +1 -1
  28. tableauserverclient/server/endpoint/groupsets_endpoint.py +2 -2
  29. tableauserverclient/server/endpoint/jobs_endpoint.py +1 -1
  30. tableauserverclient/server/endpoint/schedules_endpoint.py +132 -2
  31. tableauserverclient/server/endpoint/sites_endpoint.py +18 -1
  32. tableauserverclient/server/endpoint/tables_endpoint.py +140 -17
  33. tableauserverclient/server/endpoint/users_endpoint.py +22 -5
  34. tableauserverclient/server/endpoint/views_endpoint.py +5 -1
  35. tableauserverclient/server/endpoint/workbooks_endpoint.py +24 -10
  36. tableauserverclient/server/query.py +36 -0
  37. tableauserverclient/server/request_factory.py +16 -5
  38. tableauserverclient/server/request_options.py +162 -2
  39. tableauserverclient/server/server.py +42 -0
  40. {tableauserverclient-0.36.dist-info → tableauserverclient-0.38.dist-info}/METADATA +3 -2
  41. {tableauserverclient-0.36.dist-info → tableauserverclient-0.38.dist-info}/RECORD +45 -43
  42. {tableauserverclient-0.36.dist-info → tableauserverclient-0.38.dist-info}/WHEEL +1 -1
  43. {tableauserverclient-0.36.dist-info → tableauserverclient-0.38.dist-info/licenses}/LICENSE +0 -0
  44. {tableauserverclient-0.36.dist-info → tableauserverclient-0.38.dist-info/licenses}/LICENSE.versioneer +0 -0
  45. {tableauserverclient-0.36.dist-info → tableauserverclient-0.38.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,13 @@ from .property_decorators import property_is_valid_time, property_not_nullable
2
2
 
3
3
 
4
4
  class IntervalItem:
5
+ """
6
+ This class sets the frequency and start time of the scheduled item. This
7
+ class contains the classes for the hourly, daily, weekly, and monthly
8
+ intervals. This class mirrors the options you can set using the REST API and
9
+ the Tableau Server interface.
10
+ """
11
+
5
12
  class Frequency:
6
13
  Hourly = "Hourly"
7
14
  Daily = "Daily"
@@ -26,6 +33,19 @@ class IntervalItem:
26
33
 
27
34
 
28
35
  class HourlyInterval:
36
+ """
37
+ Runs scheduled item hourly. To set the hourly interval, you create an
38
+ instance of the HourlyInterval class and assign the following values:
39
+ start_time, end_time, and interval_value. To set the start_time and
40
+ end_time, assign the time value using this syntax: start_time=time(hour, minute)
41
+ and end_time=time(hour, minute). The hour is specified in 24 hour time.
42
+ The interval_value specifies how often the to run the task within the
43
+ start and end time. The options are expressed in hours. For example,
44
+ interval_value=.25 is every 15 minutes. The values are .25, .5, 1, 2, 4, 6,
45
+ 8, 12. Hourly schedules that run more frequently than every 60 minutes must
46
+ have start and end times that are on the hour.
47
+ """
48
+
29
49
  def __init__(self, start_time, end_time, interval_value):
30
50
  self.start_time = start_time
31
51
  self.end_time = end_time
@@ -109,6 +129,12 @@ class HourlyInterval:
109
129
 
110
130
 
111
131
  class DailyInterval:
132
+ """
133
+ Runs the scheduled item daily. To set the daily interval, you create an
134
+ instance of the DailyInterval and assign the start_time. The start time uses
135
+ the syntax start_time=time(hour, minute).
136
+ """
137
+
112
138
  def __init__(self, start_time, *interval_values):
113
139
  self.start_time = start_time
114
140
  self.interval = interval_values
@@ -177,6 +203,15 @@ class DailyInterval:
177
203
 
178
204
 
179
205
  class WeeklyInterval:
206
+ """
207
+ Runs the scheduled item once a week. To set the weekly interval, you create
208
+ an instance of the WeeklyInterval and assign the start time and multiple
209
+ instances for the interval_value (days of week and start time). The start
210
+ time uses the syntax time(hour, minute). The interval_value is the day of
211
+ the week, expressed as a IntervalItem. For example
212
+ TSC.IntervalItem.Day.Monday for Monday.
213
+ """
214
+
180
215
  def __init__(self, start_time, *interval_values):
181
216
  self.start_time = start_time
182
217
  self.interval = interval_values
@@ -214,6 +249,11 @@ class WeeklyInterval:
214
249
 
215
250
 
216
251
  class MonthlyInterval:
252
+ """
253
+ Runs the scheduled item once a month. To set the monthly interval, you
254
+ create an instance of the MonthlyInterval and assign the start time and day.
255
+ """
256
+
217
257
  def __init__(self, start_time, interval_value):
218
258
  self.start_time = start_time
219
259
 
@@ -82,6 +82,7 @@ class JobItem:
82
82
  Success: int = 0
83
83
  Failed: int = 1
84
84
  Cancelled: int = 2
85
+ Completed: int = 3
85
86
 
86
87
  def __init__(
87
88
  self,
@@ -0,0 +1,53 @@
1
+ from typing import Optional
2
+ import xml.etree.ElementTree as ET
3
+
4
+
5
+ class LocationItem:
6
+ """
7
+ Details of where an item is located, such as a personal space or project.
8
+
9
+ Attributes
10
+ ----------
11
+ id : str | None
12
+ The ID of the location.
13
+
14
+ type : str | None
15
+ The type of location, such as PersonalSpace or Project.
16
+
17
+ name : str | None
18
+ The name of the location.
19
+ """
20
+
21
+ class Type:
22
+ PersonalSpace = "PersonalSpace"
23
+ Project = "Project"
24
+
25
+ def __init__(self):
26
+ self._id: Optional[str] = None
27
+ self._type: Optional[str] = None
28
+ self._name: Optional[str] = None
29
+
30
+ def __repr__(self):
31
+ return f"{self.__class__.__name__}({self.__dict__!r})"
32
+
33
+ @property
34
+ def id(self) -> Optional[str]:
35
+ return self._id
36
+
37
+ @property
38
+ def type(self) -> Optional[str]:
39
+ return self._type
40
+
41
+ @property
42
+ def name(self) -> Optional[str]:
43
+ return self._name
44
+
45
+ @classmethod
46
+ def from_xml(cls, xml: ET.Element, ns: Optional[dict] = None) -> "LocationItem":
47
+ if ns is None:
48
+ ns = {}
49
+ location = cls()
50
+ location._id = xml.get("id", None)
51
+ location._type = xml.get("type", None)
52
+ location._name = xml.get("name", None)
53
+ return location
@@ -1,11 +1,11 @@
1
- import logging
2
1
  import xml.etree.ElementTree as ET
3
- from typing import Optional
2
+ from typing import Optional, overload
4
3
 
5
4
  from defusedxml.ElementTree import fromstring
6
5
 
7
6
  from tableauserverclient.models.exceptions import UnpopulatedPropertyError
8
- from tableauserverclient.models.property_decorators import property_is_enum, property_not_empty
7
+ from tableauserverclient.models.property_decorators import property_is_enum
8
+ from tableauserverclient.models.user_item import UserItem
9
9
 
10
10
 
11
11
  class ProjectItem:
@@ -39,12 +39,32 @@ class ProjectItem:
39
39
 
40
40
  Attributes
41
41
  ----------
42
+ datasource_count : int
43
+ The number of data sources in the project.
44
+
42
45
  id : str
43
46
  The unique identifier for the project.
44
47
 
48
+ owner: Optional[UserItem]
49
+ The UserItem owner of the project.
50
+
45
51
  owner_id : str
46
52
  The unique identifier for the UserItem owner of the project.
47
53
 
54
+ project_count : int
55
+ The number of projects in the project.
56
+
57
+ top_level_project : bool
58
+ True if the project is a top-level project.
59
+
60
+ view_count : int
61
+ The number of views in the project.
62
+
63
+ workbook_count : int
64
+ The number of workbooks in the project.
65
+
66
+ writeable : bool
67
+ True if the project is writeable.
48
68
  """
49
69
 
50
70
  ERROR_MSG = "Project item must be populated with permissions first."
@@ -75,6 +95,8 @@ class ProjectItem:
75
95
  self.parent_id: Optional[str] = parent_id
76
96
  self._samples: Optional[bool] = samples
77
97
  self._owner_id: Optional[str] = None
98
+ self._top_level_project: Optional[bool] = None
99
+ self._writeable: Optional[bool] = None
78
100
 
79
101
  self._permissions = None
80
102
  self._default_workbook_permissions = None
@@ -87,6 +109,13 @@ class ProjectItem:
87
109
  self._default_database_permissions = None
88
110
  self._default_table_permissions = None
89
111
 
112
+ self._project_count: Optional[int] = None
113
+ self._workbook_count: Optional[int] = None
114
+ self._view_count: Optional[int] = None
115
+ self._datasource_count: Optional[int] = None
116
+
117
+ self._owner: Optional[UserItem] = None
118
+
90
119
  @property
91
120
  def content_permissions(self):
92
121
  return self._content_permissions
@@ -176,25 +205,53 @@ class ProjectItem:
176
205
  def owner_id(self, value: str) -> None:
177
206
  self._owner_id = value
178
207
 
208
+ @property
209
+ def top_level_project(self) -> Optional[bool]:
210
+ return self._top_level_project
211
+
212
+ @property
213
+ def writeable(self) -> Optional[bool]:
214
+ return self._writeable
215
+
216
+ @property
217
+ def project_count(self) -> Optional[int]:
218
+ return self._project_count
219
+
220
+ @property
221
+ def workbook_count(self) -> Optional[int]:
222
+ return self._workbook_count
223
+
224
+ @property
225
+ def view_count(self) -> Optional[int]:
226
+ return self._view_count
227
+
228
+ @property
229
+ def datasource_count(self) -> Optional[int]:
230
+ return self._datasource_count
231
+
232
+ @property
233
+ def owner(self) -> Optional[UserItem]:
234
+ return self._owner
235
+
179
236
  def is_default(self):
180
237
  return self.name.lower() == "default"
181
238
 
182
- def _parse_common_tags(self, project_xml, ns):
183
- if not isinstance(project_xml, ET.Element):
184
- project_xml = fromstring(project_xml).find(".//t:project", namespaces=ns)
185
-
186
- if project_xml is not None:
187
- (
188
- _,
189
- name,
190
- description,
191
- content_permissions,
192
- parent_id,
193
- ) = self._parse_element(project_xml)
194
- self._set_values(None, name, description, content_permissions, parent_id)
195
- return self
196
-
197
- def _set_values(self, project_id, name, description, content_permissions, parent_id, owner_id):
239
+ def _set_values(
240
+ self,
241
+ project_id,
242
+ name,
243
+ description,
244
+ content_permissions,
245
+ parent_id,
246
+ owner_id,
247
+ top_level_project,
248
+ writeable,
249
+ project_count,
250
+ workbook_count,
251
+ view_count,
252
+ datasource_count,
253
+ owner,
254
+ ):
198
255
  if project_id is not None:
199
256
  self._id = project_id
200
257
  if name:
@@ -207,6 +264,20 @@ class ProjectItem:
207
264
  self.parent_id = parent_id
208
265
  if owner_id:
209
266
  self._owner_id = owner_id
267
+ if project_count is not None:
268
+ self._project_count = project_count
269
+ if workbook_count is not None:
270
+ self._workbook_count = workbook_count
271
+ if view_count is not None:
272
+ self._view_count = view_count
273
+ if datasource_count is not None:
274
+ self._datasource_count = datasource_count
275
+ if top_level_project is not None:
276
+ self._top_level_project = top_level_project
277
+ if writeable is not None:
278
+ self._writeable = writeable
279
+ if owner is not None:
280
+ self._owner = owner
210
281
 
211
282
  def _set_permissions(self, permissions):
212
283
  self._permissions = permissions
@@ -220,31 +291,71 @@ class ProjectItem:
220
291
  )
221
292
 
222
293
  @classmethod
223
- def from_response(cls, resp, ns) -> list["ProjectItem"]:
294
+ def from_response(cls, resp: bytes, ns: Optional[dict]) -> list["ProjectItem"]:
224
295
  all_project_items = list()
225
296
  parsed_response = fromstring(resp)
226
297
  all_project_xml = parsed_response.findall(".//t:project", namespaces=ns)
227
298
 
228
299
  for project_xml in all_project_xml:
229
- project_item = cls.from_xml(project_xml)
300
+ project_item = cls.from_xml(project_xml, namespace=ns)
230
301
  all_project_items.append(project_item)
231
302
  return all_project_items
232
303
 
233
304
  @classmethod
234
- def from_xml(cls, project_xml, namespace=None) -> "ProjectItem":
305
+ def from_xml(cls, project_xml: ET.Element, namespace: Optional[dict] = None) -> "ProjectItem":
235
306
  project_item = cls()
236
- project_item._set_values(*cls._parse_element(project_xml))
307
+ project_item._set_values(*cls._parse_element(project_xml, namespace))
237
308
  return project_item
238
309
 
239
310
  @staticmethod
240
- def _parse_element(project_xml):
311
+ def _parse_element(project_xml: ET.Element, namespace: Optional[dict]) -> tuple:
241
312
  id = project_xml.get("id", None)
242
313
  name = project_xml.get("name", None)
243
314
  description = project_xml.get("description", None)
244
315
  content_permissions = project_xml.get("contentPermissions", None)
245
316
  parent_id = project_xml.get("parentProjectId", None)
317
+ top_level_project = str_to_bool(project_xml.get("topLevelProject", None))
318
+ writeable = str_to_bool(project_xml.get("writeable", None))
246
319
  owner_id = None
247
- for owner in project_xml:
248
- owner_id = owner.get("id", None)
320
+ owner = None
321
+ if (owner_elem := project_xml.find(".//t:owner", namespaces=namespace)) is not None:
322
+ owner = UserItem.from_xml(owner_elem, namespace)
323
+ owner_id = owner_elem.get("id", None)
324
+
325
+ project_count = None
326
+ workbook_count = None
327
+ view_count = None
328
+ datasource_count = None
329
+ if (count_elem := project_xml.find(".//t:contentsCounts", namespaces=namespace)) is not None:
330
+ project_count = int(count_elem.get("projectCount", 0))
331
+ workbook_count = int(count_elem.get("workbookCount", 0))
332
+ view_count = int(count_elem.get("viewCount", 0))
333
+ datasource_count = int(count_elem.get("dataSourceCount", 0))
334
+
335
+ return (
336
+ id,
337
+ name,
338
+ description,
339
+ content_permissions,
340
+ parent_id,
341
+ owner_id,
342
+ top_level_project,
343
+ writeable,
344
+ project_count,
345
+ workbook_count,
346
+ view_count,
347
+ datasource_count,
348
+ owner,
349
+ )
350
+
351
+
352
+ @overload
353
+ def str_to_bool(value: str) -> bool: ...
354
+
355
+
356
+ @overload
357
+ def str_to_bool(value: None) -> None: ...
358
+
249
359
 
250
- return id, name, description, content_permissions, parent_id, owner_id
360
+ def str_to_bool(value):
361
+ return value.lower() == "true" if value is not None else None
@@ -20,6 +20,63 @@ Interval = Union[HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval]
20
20
 
21
21
 
22
22
  class ScheduleItem:
23
+ """
24
+ Using the TSC library, you can schedule extract refresh or subscription
25
+ tasks on Tableau Server. You can also get and update information about the
26
+ scheduled tasks, or delete scheduled tasks.
27
+
28
+ If you have the identifier of the job, you can use the TSC library to find
29
+ out the status of the asynchronous job.
30
+
31
+ The schedule properties are defined in the ScheduleItem class. The class
32
+ corresponds to the properties for schedules you can access in Tableau
33
+ Server or by using the Tableau Server REST API. The Schedule methods are
34
+ based upon the endpoints for jobs in the REST API and operate on the JobItem
35
+ class.
36
+
37
+ Parameters
38
+ ----------
39
+ name : str
40
+ The name of the schedule.
41
+
42
+ priority : int
43
+ The priority of the schedule. Lower values represent higher priority,
44
+ with 0 indicating the highest priority.
45
+
46
+ schedule_type : str
47
+ The type of task schedule. See ScheduleItem.Type for the possible values.
48
+
49
+ execution_order : str
50
+ Specifies how the scheduled tasks should run. The choices are Parallel
51
+ which uses all avaiable background processes for a scheduled task, or
52
+ Serial, which limits the schedule to one background process.
53
+
54
+ interval_item : Interval
55
+ Specifies the frequency that the scheduled task should run. The
56
+ interval_item is an instance of the IntervalItem class. The
57
+ interval_item has properties for frequency (hourly, daily, weekly,
58
+ monthly), and what time and date the scheduled item runs. You set this
59
+ value by declaring an IntervalItem object that is one of the following:
60
+ HourlyInterval, DailyInterval, WeeklyInterval, or MonthlyInterval.
61
+
62
+ Attributes
63
+ ----------
64
+ created_at : datetime
65
+ The date and time the schedule was created.
66
+
67
+ end_schedule_at : datetime
68
+ The date and time the schedule ends.
69
+
70
+ id : str
71
+ The unique identifier for the schedule.
72
+
73
+ next_run_at : datetime
74
+ The date and time the schedule is next run.
75
+
76
+ state : str
77
+ The state of the schedule. See ScheduleItem.State for the possible values.
78
+ """
79
+
23
80
  class Type:
24
81
  Extract = "Extract"
25
82
  Flow = "Flow"
@@ -1188,6 +1188,34 @@ class SiteItem:
1188
1188
  )
1189
1189
 
1190
1190
 
1191
+ class SiteAuthConfiguration:
1192
+ """
1193
+ Authentication configuration for a site.
1194
+ """
1195
+
1196
+ def __init__(self):
1197
+ self.auth_setting: Optional[str] = None
1198
+ self.enabled: Optional[bool] = None
1199
+ self.idp_configuration_id: Optional[str] = None
1200
+ self.idp_configuration_name: Optional[str] = None
1201
+ self.known_provider_alias: Optional[str] = None
1202
+
1203
+ @classmethod
1204
+ def from_response(cls, resp: bytes, ns: dict) -> list["SiteAuthConfiguration"]:
1205
+ all_auth_configs = list()
1206
+ parsed_response = fromstring(resp)
1207
+ all_auth_xml = parsed_response.findall(".//t:siteAuthConfiguration", namespaces=ns)
1208
+ for auth_xml in all_auth_xml:
1209
+ auth_config = cls()
1210
+ auth_config.auth_setting = auth_xml.get("authSetting", None)
1211
+ auth_config.enabled = string_to_bool(auth_xml.get("enabled", ""))
1212
+ auth_config.idp_configuration_id = auth_xml.get("idpConfigurationId", None)
1213
+ auth_config.idp_configuration_name = auth_xml.get("idpConfigurationName", None)
1214
+ auth_config.known_provider_alias = auth_xml.get("knownProviderAlias", None)
1215
+ all_auth_configs.append(auth_config)
1216
+ return all_auth_configs
1217
+
1218
+
1191
1219
  # Used to convert string represented boolean to a boolean type
1192
1220
  def string_to_bool(s: str) -> bool:
1193
1221
  return s.lower() == "true"
@@ -1,8 +1,12 @@
1
+ from typing import Callable, Optional, TYPE_CHECKING
1
2
  from defusedxml.ElementTree import fromstring
2
3
 
3
4
  from .exceptions import UnpopulatedPropertyError
4
5
  from .property_decorators import property_not_empty, property_is_boolean
5
6
 
7
+ if TYPE_CHECKING:
8
+ from tableauserverclient.models import DQWItem
9
+
6
10
 
7
11
  class TableItem:
8
12
  def __init__(self, name, description=None):
@@ -40,7 +44,7 @@ class TableItem:
40
44
  return self._data_quality_warnings()
41
45
 
42
46
  @property
43
- def id(self):
47
+ def id(self) -> Optional[str]:
44
48
  return self._id
45
49
 
46
50
  @property
@@ -100,8 +104,8 @@ class TableItem:
100
104
  def _set_columns(self, columns):
101
105
  self._columns = columns
102
106
 
103
- def _set_data_quality_warnings(self, dqws):
104
- self._data_quality_warnings = dqws
107
+ def _set_data_quality_warnings(self, dqw: Callable[[], list["DQWItem"]]) -> None:
108
+ self._data_quality_warnings = dqw
105
109
 
106
110
  def _set_values(self, table_values):
107
111
  if "id" in table_values:
@@ -1,8 +1,10 @@
1
1
  from typing import Union
2
2
 
3
+ from tableauserverclient.models.database_item import DatabaseItem
3
4
  from tableauserverclient.models.datasource_item import DatasourceItem
4
5
  from tableauserverclient.models.flow_item import FlowItem
5
6
  from tableauserverclient.models.project_item import ProjectItem
7
+ from tableauserverclient.models.table_item import TableItem
6
8
  from tableauserverclient.models.view_item import ViewItem
7
9
  from tableauserverclient.models.workbook_item import WorkbookItem
8
10
  from tableauserverclient.models.metric_item import MetricItem
@@ -25,7 +27,17 @@ class Resource:
25
27
 
26
28
  # resource types that have permissions, can be renamed, etc
27
29
  # todo: refactoring: should actually define TableauItem as an interface and let all these implement it
28
- TableauItem = Union[DatasourceItem, FlowItem, MetricItem, ProjectItem, ViewItem, WorkbookItem, VirtualConnectionItem]
30
+ TableauItem = Union[
31
+ DatasourceItem,
32
+ FlowItem,
33
+ MetricItem,
34
+ ProjectItem,
35
+ ViewItem,
36
+ WorkbookItem,
37
+ VirtualConnectionItem,
38
+ DatabaseItem,
39
+ TableItem,
40
+ ]
29
41
 
30
42
 
31
43
  def plural_type(content_type: Union[Resource, str]) -> str: