tableauserverclient 0.37__py3-none-any.whl → 0.39__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 (118) hide show
  1. tableauserverclient/bin/_version.py → _version.py +3 -3
  2. bin/__init__.py +3 -0
  3. bin/_version.py +21 -0
  4. {tableauserverclient/helpers → helpers}/strings.py +25 -1
  5. {tableauserverclient/models → models}/__init__.py +15 -1
  6. models/collection_item.py +52 -0
  7. {tableauserverclient/models → models}/connection_item.py +16 -2
  8. {tableauserverclient/models → models}/custom_view_item.py +8 -0
  9. {tableauserverclient/models → models}/data_freshness_policy_item.py +3 -3
  10. {tableauserverclient/models → models}/datasource_item.py +113 -3
  11. models/extensions_item.py +186 -0
  12. models/extract_item.py +82 -0
  13. {tableauserverclient/models → models}/favorites_item.py +21 -8
  14. {tableauserverclient/models → models}/flow_item.py +3 -3
  15. {tableauserverclient/models → models}/group_item.py +18 -1
  16. {tableauserverclient/models → models}/groupset_item.py +14 -0
  17. {tableauserverclient/models → models}/interval_item.py +42 -1
  18. models/location_item.py +53 -0
  19. models/oidc_item.py +82 -0
  20. {tableauserverclient/models → models}/permissions_item.py +2 -0
  21. {tableauserverclient/models → models}/project_item.py +141 -29
  22. {tableauserverclient/models → models}/property_decorators.py +2 -2
  23. {tableauserverclient/models → models}/reference_item.py +12 -6
  24. {tableauserverclient/models → models}/schedule_item.py +67 -1
  25. {tableauserverclient/models → models}/site_item.py +54 -0
  26. {tableauserverclient/models → models}/table_item.py +7 -3
  27. {tableauserverclient/models → models}/tableau_auth.py +13 -6
  28. {tableauserverclient/models → models}/tableau_types.py +13 -1
  29. {tableauserverclient/models → models}/user_item.py +111 -4
  30. {tableauserverclient/models → models}/view_item.py +79 -5
  31. {tableauserverclient/models → models}/workbook_item.py +153 -3
  32. {tableauserverclient/server → server}/endpoint/__init__.py +4 -0
  33. {tableauserverclient/server → server}/endpoint/databases_endpoint.py +101 -18
  34. {tableauserverclient/server → server}/endpoint/datasources_endpoint.py +155 -25
  35. {tableauserverclient/server → server}/endpoint/dqw_endpoint.py +16 -6
  36. {tableauserverclient/server → server}/endpoint/endpoint.py +39 -0
  37. server/endpoint/extensions_endpoint.py +79 -0
  38. {tableauserverclient/server → server}/endpoint/flow_task_endpoint.py +1 -1
  39. {tableauserverclient/server → server}/endpoint/flows_endpoint.py +5 -4
  40. server/endpoint/oidc_endpoint.py +157 -0
  41. {tableauserverclient/server → server}/endpoint/projects_endpoint.py +12 -0
  42. server/endpoint/schedules_endpoint.py +328 -0
  43. {tableauserverclient/server → server}/endpoint/sites_endpoint.py +18 -1
  44. {tableauserverclient/server → server}/endpoint/tables_endpoint.py +140 -17
  45. {tableauserverclient/server → server}/endpoint/users_endpoint.py +296 -10
  46. {tableauserverclient/server → server}/endpoint/views_endpoint.py +23 -0
  47. {tableauserverclient/server → server}/endpoint/workbooks_endpoint.py +124 -9
  48. {tableauserverclient/server → server}/query.py +36 -0
  49. {tableauserverclient/server → server}/request_factory.py +286 -2
  50. {tableauserverclient/server → server}/request_options.py +139 -3
  51. {tableauserverclient/server → server}/server.py +46 -0
  52. {tableauserverclient-0.37.dist-info → tableauserverclient-0.39.dist-info}/METADATA +5 -26
  53. tableauserverclient-0.39.dist-info/RECORD +107 -0
  54. {tableauserverclient-0.37.dist-info → tableauserverclient-0.39.dist-info}/WHEEL +1 -1
  55. tableauserverclient-0.39.dist-info/top_level.txt +4 -0
  56. tableauserverclient/__init__.py +0 -141
  57. tableauserverclient/config.py +0 -27
  58. tableauserverclient/datetime_helpers.py +0 -45
  59. tableauserverclient/exponential_backoff.py +0 -30
  60. tableauserverclient/filesys_helpers.py +0 -63
  61. tableauserverclient/namespace.py +0 -37
  62. tableauserverclient/py.typed +0 -0
  63. tableauserverclient/server/endpoint/schedules_endpoint.py +0 -151
  64. tableauserverclient-0.37.dist-info/RECORD +0 -106
  65. tableauserverclient-0.37.dist-info/licenses/LICENSE.versioneer +0 -7
  66. tableauserverclient-0.37.dist-info/top_level.txt +0 -1
  67. {tableauserverclient/helpers → helpers}/__init__.py +0 -0
  68. {tableauserverclient/helpers → helpers}/headers.py +0 -0
  69. {tableauserverclient/helpers → helpers}/logging.py +0 -0
  70. {tableauserverclient/models → models}/column_item.py +0 -0
  71. {tableauserverclient/models → models}/connection_credentials.py +0 -0
  72. {tableauserverclient/models → models}/data_acceleration_report_item.py +0 -0
  73. {tableauserverclient/models → models}/data_alert_item.py +0 -0
  74. {tableauserverclient/models → models}/database_item.py +0 -0
  75. {tableauserverclient/models → models}/dqw_item.py +0 -0
  76. {tableauserverclient/models → models}/exceptions.py +0 -0
  77. {tableauserverclient/models → models}/fileupload_item.py +0 -0
  78. {tableauserverclient/models → models}/flow_run_item.py +0 -0
  79. {tableauserverclient/models → models}/job_item.py +0 -0
  80. {tableauserverclient/models → models}/linked_tasks_item.py +0 -0
  81. {tableauserverclient/models → models}/metric_item.py +0 -0
  82. {tableauserverclient/models → models}/pagination_item.py +0 -0
  83. {tableauserverclient/models → models}/revision_item.py +0 -0
  84. {tableauserverclient/models → models}/server_info_item.py +0 -0
  85. {tableauserverclient/models → models}/subscription_item.py +0 -0
  86. {tableauserverclient/models → models}/tag_item.py +0 -0
  87. {tableauserverclient/models → models}/target.py +0 -0
  88. {tableauserverclient/models → models}/task_item.py +0 -0
  89. {tableauserverclient/models → models}/virtual_connection_item.py +0 -0
  90. {tableauserverclient/models → models}/webhook_item.py +0 -0
  91. {tableauserverclient/server → server}/__init__.py +0 -0
  92. {tableauserverclient/server → server}/endpoint/auth_endpoint.py +0 -0
  93. {tableauserverclient/server → server}/endpoint/custom_views_endpoint.py +0 -0
  94. {tableauserverclient/server → server}/endpoint/data_acceleration_report_endpoint.py +0 -0
  95. {tableauserverclient/server → server}/endpoint/data_alert_endpoint.py +0 -0
  96. {tableauserverclient/server → server}/endpoint/default_permissions_endpoint.py +0 -0
  97. {tableauserverclient/server → server}/endpoint/exceptions.py +0 -0
  98. {tableauserverclient/server → server}/endpoint/favorites_endpoint.py +0 -0
  99. {tableauserverclient/server → server}/endpoint/fileuploads_endpoint.py +0 -0
  100. {tableauserverclient/server → server}/endpoint/flow_runs_endpoint.py +0 -0
  101. {tableauserverclient/server → server}/endpoint/groups_endpoint.py +0 -0
  102. {tableauserverclient/server → server}/endpoint/groupsets_endpoint.py +0 -0
  103. {tableauserverclient/server → server}/endpoint/jobs_endpoint.py +0 -0
  104. {tableauserverclient/server → server}/endpoint/linked_tasks_endpoint.py +0 -0
  105. {tableauserverclient/server → server}/endpoint/metadata_endpoint.py +0 -0
  106. {tableauserverclient/server → server}/endpoint/metrics_endpoint.py +0 -0
  107. {tableauserverclient/server → server}/endpoint/permissions_endpoint.py +0 -0
  108. {tableauserverclient/server → server}/endpoint/resource_tagger.py +0 -0
  109. {tableauserverclient/server → server}/endpoint/server_info_endpoint.py +0 -0
  110. {tableauserverclient/server → server}/endpoint/subscriptions_endpoint.py +0 -0
  111. {tableauserverclient/server → server}/endpoint/tasks_endpoint.py +0 -0
  112. {tableauserverclient/server → server}/endpoint/virtual_connections_endpoint.py +0 -0
  113. {tableauserverclient/server → server}/endpoint/webhooks_endpoint.py +0 -0
  114. {tableauserverclient/server → server}/exceptions.py +0 -0
  115. {tableauserverclient/server → server}/filter.py +0 -0
  116. {tableauserverclient/server → server}/pager.py +0 -0
  117. {tableauserverclient/server → server}/sort.py +0 -0
  118. {tableauserverclient-0.37.dist-info → tableauserverclient-0.39.dist-info}/licenses/LICENSE +0 -0
@@ -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."
@@ -66,15 +86,18 @@ class ProjectItem:
66
86
  content_permissions: Optional[str] = None,
67
87
  parent_id: Optional[str] = None,
68
88
  samples: Optional[bool] = None,
89
+ id: Optional[str] = None,
69
90
  ) -> None:
70
91
  self._content_permissions = None
71
- self._id: Optional[str] = None
92
+ self._id: Optional[str] = id
72
93
  self.description: Optional[str] = description
73
94
  self.name: str = name
74
95
  self.content_permissions: Optional[str] = content_permissions
75
96
  self.parent_id: Optional[str] = parent_id
76
97
  self._samples: Optional[bool] = samples
77
98
  self._owner_id: Optional[str] = None
99
+ self._top_level_project: Optional[bool] = None
100
+ self._writeable: Optional[bool] = None
78
101
 
79
102
  self._permissions = None
80
103
  self._default_workbook_permissions = None
@@ -87,6 +110,13 @@ class ProjectItem:
87
110
  self._default_database_permissions = None
88
111
  self._default_table_permissions = None
89
112
 
113
+ self._project_count: Optional[int] = None
114
+ self._workbook_count: Optional[int] = None
115
+ self._view_count: Optional[int] = None
116
+ self._datasource_count: Optional[int] = None
117
+
118
+ self._owner: Optional[UserItem] = None
119
+
90
120
  @property
91
121
  def content_permissions(self):
92
122
  return self._content_permissions
@@ -165,7 +195,7 @@ class ProjectItem:
165
195
  return self._name
166
196
 
167
197
  @name.setter
168
- def name(self, value: str) -> None:
198
+ def name(self, value: Optional[str]) -> None:
169
199
  self._name = value
170
200
 
171
201
  @property
@@ -176,25 +206,53 @@ class ProjectItem:
176
206
  def owner_id(self, value: str) -> None:
177
207
  self._owner_id = value
178
208
 
209
+ @property
210
+ def top_level_project(self) -> Optional[bool]:
211
+ return self._top_level_project
212
+
213
+ @property
214
+ def writeable(self) -> Optional[bool]:
215
+ return self._writeable
216
+
217
+ @property
218
+ def project_count(self) -> Optional[int]:
219
+ return self._project_count
220
+
221
+ @property
222
+ def workbook_count(self) -> Optional[int]:
223
+ return self._workbook_count
224
+
225
+ @property
226
+ def view_count(self) -> Optional[int]:
227
+ return self._view_count
228
+
229
+ @property
230
+ def datasource_count(self) -> Optional[int]:
231
+ return self._datasource_count
232
+
233
+ @property
234
+ def owner(self) -> Optional[UserItem]:
235
+ return self._owner
236
+
179
237
  def is_default(self):
180
238
  return self.name.lower() == "default"
181
239
 
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):
240
+ def _set_values(
241
+ self,
242
+ project_id,
243
+ name,
244
+ description,
245
+ content_permissions,
246
+ parent_id,
247
+ owner_id,
248
+ top_level_project,
249
+ writeable,
250
+ project_count,
251
+ workbook_count,
252
+ view_count,
253
+ datasource_count,
254
+ owner,
255
+ ):
198
256
  if project_id is not None:
199
257
  self._id = project_id
200
258
  if name:
@@ -207,6 +265,20 @@ class ProjectItem:
207
265
  self.parent_id = parent_id
208
266
  if owner_id:
209
267
  self._owner_id = owner_id
268
+ if project_count is not None:
269
+ self._project_count = project_count
270
+ if workbook_count is not None:
271
+ self._workbook_count = workbook_count
272
+ if view_count is not None:
273
+ self._view_count = view_count
274
+ if datasource_count is not None:
275
+ self._datasource_count = datasource_count
276
+ if top_level_project is not None:
277
+ self._top_level_project = top_level_project
278
+ if writeable is not None:
279
+ self._writeable = writeable
280
+ if owner is not None:
281
+ self._owner = owner
210
282
 
211
283
  def _set_permissions(self, permissions):
212
284
  self._permissions = permissions
@@ -220,31 +292,71 @@ class ProjectItem:
220
292
  )
221
293
 
222
294
  @classmethod
223
- def from_response(cls, resp, ns) -> list["ProjectItem"]:
295
+ def from_response(cls, resp: bytes, ns: Optional[dict]) -> list["ProjectItem"]:
224
296
  all_project_items = list()
225
297
  parsed_response = fromstring(resp)
226
298
  all_project_xml = parsed_response.findall(".//t:project", namespaces=ns)
227
299
 
228
300
  for project_xml in all_project_xml:
229
- project_item = cls.from_xml(project_xml)
301
+ project_item = cls.from_xml(project_xml, namespace=ns)
230
302
  all_project_items.append(project_item)
231
303
  return all_project_items
232
304
 
233
305
  @classmethod
234
- def from_xml(cls, project_xml, namespace=None) -> "ProjectItem":
306
+ def from_xml(cls, project_xml: ET.Element, namespace: Optional[dict] = None) -> "ProjectItem":
235
307
  project_item = cls()
236
- project_item._set_values(*cls._parse_element(project_xml))
308
+ project_item._set_values(*cls._parse_element(project_xml, namespace))
237
309
  return project_item
238
310
 
239
311
  @staticmethod
240
- def _parse_element(project_xml):
312
+ def _parse_element(project_xml: ET.Element, namespace: Optional[dict]) -> tuple:
241
313
  id = project_xml.get("id", None)
242
314
  name = project_xml.get("name", None)
243
315
  description = project_xml.get("description", None)
244
316
  content_permissions = project_xml.get("contentPermissions", None)
245
317
  parent_id = project_xml.get("parentProjectId", None)
318
+ top_level_project = str_to_bool(project_xml.get("topLevelProject", None))
319
+ writeable = str_to_bool(project_xml.get("writeable", None))
246
320
  owner_id = None
247
- for owner in project_xml:
248
- owner_id = owner.get("id", None)
321
+ owner = None
322
+ if (owner_elem := project_xml.find(".//t:owner", namespaces=namespace)) is not None:
323
+ owner = UserItem.from_xml(owner_elem, namespace)
324
+ owner_id = owner_elem.get("id", None)
325
+
326
+ project_count = None
327
+ workbook_count = None
328
+ view_count = None
329
+ datasource_count = None
330
+ if (count_elem := project_xml.find(".//t:contentsCounts", namespaces=namespace)) is not None:
331
+ project_count = int(count_elem.get("projectCount", 0))
332
+ workbook_count = int(count_elem.get("workbookCount", 0))
333
+ view_count = int(count_elem.get("viewCount", 0))
334
+ datasource_count = int(count_elem.get("dataSourceCount", 0))
335
+
336
+ return (
337
+ id,
338
+ name,
339
+ description,
340
+ content_permissions,
341
+ parent_id,
342
+ owner_id,
343
+ top_level_project,
344
+ writeable,
345
+ project_count,
346
+ workbook_count,
347
+ view_count,
348
+ datasource_count,
349
+ owner,
350
+ )
351
+
352
+
353
+ @overload
354
+ def str_to_bool(value: str) -> bool: ...
355
+
356
+
357
+ @overload
358
+ def str_to_bool(value: None) -> None: ...
359
+
249
360
 
250
- return id, name, description, content_permissions, parent_id, owner_id
361
+ def str_to_bool(value):
362
+ return value.lower() == "true" if value is not None else None
@@ -1,7 +1,7 @@
1
1
  import datetime
2
2
  import re
3
3
  from functools import wraps
4
- from typing import Any, Optional
4
+ from typing import Any
5
5
  from collections.abc import Container
6
6
 
7
7
  from tableauserverclient.datetime_helpers import parse_datetime
@@ -67,7 +67,7 @@ def property_is_valid_time(func):
67
67
  return wrapper
68
68
 
69
69
 
70
- def property_is_int(range: tuple[int, int], allowed: Optional[Container[Any]] = None):
70
+ def property_is_int(range: tuple[int, int], allowed: Container[Any] | None = None):
71
71
  """Takes a range of ints and a list of exemptions to check against
72
72
  when setting a property on a model. The range is a tuple of (min, max) and the
73
73
  allowed list (empty by default) allows values outside that range.
@@ -1,9 +1,12 @@
1
+ from typing_extensions import Self
2
+
3
+
1
4
  class ResourceReference:
2
- def __init__(self, id_, tag_name):
5
+ def __init__(self, id_: str | None, tag_name: str) -> None:
3
6
  self.id = id_
4
7
  self.tag_name = tag_name
5
8
 
6
- def __str__(self):
9
+ def __str__(self) -> str:
7
10
  return f"<ResourceReference id={self._id} tag={self._tag_name}>"
8
11
 
9
12
  __repr__ = __str__
@@ -13,18 +16,21 @@ class ResourceReference:
13
16
  return False
14
17
  return (self.id == other.id) and (self.tag_name == other.tag_name)
15
18
 
19
+ def __hash__(self: Self) -> int:
20
+ return hash((self.id, self.tag_name))
21
+
16
22
  @property
17
- def id(self):
23
+ def id(self) -> str | None:
18
24
  return self._id
19
25
 
20
26
  @id.setter
21
- def id(self, value):
27
+ def id(self, value: str | None) -> None:
22
28
  self._id = value
23
29
 
24
30
  @property
25
- def tag_name(self):
31
+ def tag_name(self) -> str:
26
32
  return self._tag_name
27
33
 
28
34
  @tag_name.setter
29
- def tag_name(self, value):
35
+ def tag_name(self, value: str) -> None:
30
36
  self._tag_name = value
@@ -1,6 +1,6 @@
1
1
  import xml.etree.ElementTree as ET
2
2
  from datetime import datetime
3
- from typing import Optional, Union
3
+ from typing import Optional, Union, TYPE_CHECKING
4
4
 
5
5
  from defusedxml.ElementTree import fromstring
6
6
 
@@ -16,10 +16,71 @@ from .property_decorators import (
16
16
  property_is_enum,
17
17
  )
18
18
 
19
+ if TYPE_CHECKING:
20
+ from requests import Response
21
+
22
+
19
23
  Interval = Union[HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval]
20
24
 
21
25
 
22
26
  class ScheduleItem:
27
+ """
28
+ Using the TSC library, you can schedule extract refresh or subscription
29
+ tasks on Tableau Server. You can also get and update information about the
30
+ scheduled tasks, or delete scheduled tasks.
31
+
32
+ If you have the identifier of the job, you can use the TSC library to find
33
+ out the status of the asynchronous job.
34
+
35
+ The schedule properties are defined in the ScheduleItem class. The class
36
+ corresponds to the properties for schedules you can access in Tableau
37
+ Server or by using the Tableau Server REST API. The Schedule methods are
38
+ based upon the endpoints for jobs in the REST API and operate on the JobItem
39
+ class.
40
+
41
+ Parameters
42
+ ----------
43
+ name : str
44
+ The name of the schedule.
45
+
46
+ priority : int
47
+ The priority of the schedule. Lower values represent higher priority,
48
+ with 0 indicating the highest priority.
49
+
50
+ schedule_type : str
51
+ The type of task schedule. See ScheduleItem.Type for the possible values.
52
+
53
+ execution_order : str
54
+ Specifies how the scheduled tasks should run. The choices are Parallel
55
+ which uses all avaiable background processes for a scheduled task, or
56
+ Serial, which limits the schedule to one background process.
57
+
58
+ interval_item : Interval
59
+ Specifies the frequency that the scheduled task should run. The
60
+ interval_item is an instance of the IntervalItem class. The
61
+ interval_item has properties for frequency (hourly, daily, weekly,
62
+ monthly), and what time and date the scheduled item runs. You set this
63
+ value by declaring an IntervalItem object that is one of the following:
64
+ HourlyInterval, DailyInterval, WeeklyInterval, or MonthlyInterval.
65
+
66
+ Attributes
67
+ ----------
68
+ created_at : datetime
69
+ The date and time the schedule was created.
70
+
71
+ end_schedule_at : datetime
72
+ The date and time the schedule ends.
73
+
74
+ id : str
75
+ The unique identifier for the schedule.
76
+
77
+ next_run_at : datetime
78
+ The date and time the schedule is next run.
79
+
80
+ state : str
81
+ The state of the schedule. See ScheduleItem.State for the possible values.
82
+ """
83
+
23
84
  class Type:
24
85
  Extract = "Extract"
25
86
  Flow = "Flow"
@@ -350,3 +411,8 @@ class ScheduleItem:
350
411
  for warning_xml in all_warning_xml:
351
412
  warnings.append(warning_xml.get("message", None))
352
413
  return warnings
414
+
415
+
416
+ def parse_batch_schedule_state(response: "Response", ns) -> list[str]:
417
+ xml = fromstring(response.content)
418
+ return [text for tag in xml.findall(".//t:scheduleLuid", namespaces=ns) if (text := tag.text)]
@@ -85,6 +85,9 @@ class SiteItem:
85
85
  state: str
86
86
  Shows the current state of the site (Active or Suspended).
87
87
 
88
+ attribute_capture_enabled: Optional[str]
89
+ Enables user attributes for all Tableau Server embedding workflows.
90
+
88
91
  """
89
92
 
90
93
  _user_quota: Optional[int] = None
@@ -164,6 +167,7 @@ class SiteItem:
164
167
  time_zone=None,
165
168
  auto_suspend_refresh_enabled: bool = True,
166
169
  auto_suspend_refresh_inactivity_window: int = 30,
170
+ attribute_capture_enabled: Optional[bool] = None,
167
171
  ):
168
172
  self._admin_mode = None
169
173
  self._id: Optional[str] = None
@@ -217,6 +221,7 @@ class SiteItem:
217
221
  self.time_zone = time_zone
218
222
  self.auto_suspend_refresh_enabled = auto_suspend_refresh_enabled
219
223
  self.auto_suspend_refresh_inactivity_window = auto_suspend_refresh_inactivity_window
224
+ self.attribute_capture_enabled = attribute_capture_enabled
220
225
 
221
226
  @property
222
227
  def admin_mode(self) -> Optional[str]:
@@ -720,6 +725,7 @@ class SiteItem:
720
725
  time_zone,
721
726
  auto_suspend_refresh_enabled,
722
727
  auto_suspend_refresh_inactivity_window,
728
+ attribute_capture_enabled,
723
729
  ) = self._parse_element(site_xml, ns)
724
730
 
725
731
  self._set_values(
@@ -774,6 +780,7 @@ class SiteItem:
774
780
  time_zone,
775
781
  auto_suspend_refresh_enabled,
776
782
  auto_suspend_refresh_inactivity_window,
783
+ attribute_capture_enabled,
777
784
  )
778
785
  return self
779
786
 
@@ -830,6 +837,7 @@ class SiteItem:
830
837
  time_zone,
831
838
  auto_suspend_refresh_enabled,
832
839
  auto_suspend_refresh_inactivity_window,
840
+ attribute_capture_enabled,
833
841
  ):
834
842
  if id is not None:
835
843
  self._id = id
@@ -937,6 +945,7 @@ class SiteItem:
937
945
  self.auto_suspend_refresh_enabled = auto_suspend_refresh_enabled
938
946
  if auto_suspend_refresh_inactivity_window is not None:
939
947
  self.auto_suspend_refresh_inactivity_window = auto_suspend_refresh_inactivity_window
948
+ self.attribute_capture_enabled = attribute_capture_enabled
940
949
 
941
950
  @classmethod
942
951
  def from_response(cls, resp, ns) -> list["SiteItem"]:
@@ -996,6 +1005,7 @@ class SiteItem:
996
1005
  time_zone,
997
1006
  auto_suspend_refresh_enabled,
998
1007
  auto_suspend_refresh_inactivity_window,
1008
+ attribute_capture_enabled,
999
1009
  ) = cls._parse_element(site_xml, ns)
1000
1010
 
1001
1011
  site_item = cls(name, content_url)
@@ -1051,6 +1061,7 @@ class SiteItem:
1051
1061
  time_zone,
1052
1062
  auto_suspend_refresh_enabled,
1053
1063
  auto_suspend_refresh_inactivity_window,
1064
+ attribute_capture_enabled,
1054
1065
  )
1055
1066
  all_site_items.append(site_item)
1056
1067
  return all_site_items
@@ -1132,6 +1143,9 @@ class SiteItem:
1132
1143
 
1133
1144
  flows_enabled = string_to_bool(site_xml.get("flowsEnabled", ""))
1134
1145
  cataloging_enabled = string_to_bool(site_xml.get("catalogingEnabled", ""))
1146
+ attribute_capture_enabled = (
1147
+ string_to_bool(ace) if (ace := site_xml.get("attributeCaptureEnabled")) is not None else None
1148
+ )
1135
1149
 
1136
1150
  return (
1137
1151
  id,
@@ -1185,8 +1199,48 @@ class SiteItem:
1185
1199
  time_zone,
1186
1200
  auto_suspend_refresh_enabled,
1187
1201
  auto_suspend_refresh_inactivity_window,
1202
+ attribute_capture_enabled,
1203
+ )
1204
+
1205
+
1206
+ class SiteAuthConfiguration:
1207
+ """
1208
+ Authentication configuration for a site.
1209
+ """
1210
+
1211
+ def __init__(self):
1212
+ self.auth_setting: Optional[str] = None
1213
+ self.enabled: Optional[bool] = None
1214
+ self.idp_configuration_id: Optional[str] = None
1215
+ self.idp_configuration_name: Optional[str] = None
1216
+ self.known_provider_alias: Optional[str] = None
1217
+
1218
+ @classmethod
1219
+ def from_response(cls, resp: bytes, ns: dict) -> list["SiteAuthConfiguration"]:
1220
+ all_auth_configs = list()
1221
+ parsed_response = fromstring(resp)
1222
+ all_auth_xml = parsed_response.findall(".//t:siteAuthConfiguration", namespaces=ns)
1223
+ for auth_xml in all_auth_xml:
1224
+ auth_config = cls()
1225
+ auth_config.auth_setting = auth_xml.get("authSetting", None)
1226
+ auth_config.enabled = string_to_bool(auth_xml.get("enabled", ""))
1227
+ auth_config.idp_configuration_id = auth_xml.get("idpConfigurationId", None)
1228
+ auth_config.idp_configuration_name = auth_xml.get("idpConfigurationName", None)
1229
+ auth_config.known_provider_alias = auth_xml.get("knownProviderAlias", None)
1230
+ all_auth_configs.append(auth_config)
1231
+ return all_auth_configs
1232
+
1233
+ def __str__(self):
1234
+ return (
1235
+ f"{self.__class__.__qualname__}(auth_setting={self.auth_setting}, "
1236
+ f"enabled={self.enabled}, "
1237
+ f"idp_configuration_id={self.idp_configuration_id}, "
1238
+ f"idp_configuration_name={self.idp_configuration_name})"
1188
1239
  )
1189
1240
 
1241
+ def __repr__(self):
1242
+ return f"<{str(self)}>"
1243
+
1190
1244
 
1191
1245
  # Used to convert string represented boolean to a boolean type
1192
1246
  def string_to_bool(s: str) -> bool:
@@ -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:
@@ -87,7 +87,7 @@ class TableauAuth(Credentials):
87
87
  uid = f", user_id_to_impersonate=f{self.user_id_to_impersonate}"
88
88
  else:
89
89
  uid = ""
90
- return f"<Credentials username={self.username} password=redacted (site={self.site_id}{uid})>"
90
+ return f"<{self.__class__.__qualname__} username={self.username} password=redacted (site={self.site_id}{uid})>"
91
91
 
92
92
 
93
93
  # A Tableau-generated Personal Access Token
@@ -155,8 +155,8 @@ class PersonalAccessTokenAuth(Credentials):
155
155
  else:
156
156
  uid = ""
157
157
  return (
158
- f"<PersonalAccessToken name={self.token_name} token={self.personal_access_token[:2]}..."
159
- f"(site={self.site_id}{uid} >"
158
+ f"<{self.__class__.__qualname__}(name={self.token_name} token={self.personal_access_token[:2]}..."
159
+ f"site={self.site_id}{uid}) >"
160
160
  )
161
161
 
162
162
 
@@ -198,19 +198,26 @@ class JWTAuth(Credentials):
198
198
 
199
199
  """
200
200
 
201
- def __init__(self, jwt: str, site_id: Optional[str] = None, user_id_to_impersonate: Optional[str] = None) -> None:
201
+ def __init__(
202
+ self,
203
+ jwt: str,
204
+ isUat: bool = False,
205
+ site_id: Optional[str] = None,
206
+ user_id_to_impersonate: Optional[str] = None,
207
+ ) -> None:
202
208
  if jwt is None:
203
209
  raise TabError("Must provide a JWT token when using JWT authentication")
204
210
  super().__init__(site_id, user_id_to_impersonate)
205
211
  self.jwt = jwt
212
+ self.isUat = isUat
206
213
 
207
214
  @property
208
215
  def credentials(self) -> dict[str, str]:
209
- return {"jwt": self.jwt}
216
+ return {"jwt": self.jwt, "isUat": str(self.isUat).lower()}
210
217
 
211
218
  def __repr__(self):
212
219
  if self.user_id_to_impersonate:
213
220
  uid = f", user_id_to_impersonate=f{self.user_id_to_impersonate}"
214
221
  else:
215
222
  uid = ""
216
- return f"<{self.__class__.__qualname__} jwt={self.jwt[:5]}... (site={self.site_id}{uid})>"
223
+ return f"<{self.__class__.__qualname__} jwt={self.jwt[:5]}... isUat={self.isUat} (site={self.site_id}{uid})>"
@@ -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: