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,16 +1,15 @@
1
1
  import xml.etree.ElementTree as ET
2
- from typing import Set
3
2
 
4
3
  from defusedxml.ElementTree import fromstring
5
4
 
6
5
 
7
- class TagItem(object):
6
+ class TagItem:
8
7
  @classmethod
9
- def from_response(cls, resp: bytes, ns) -> Set[str]:
8
+ def from_response(cls, resp: bytes, ns) -> set[str]:
10
9
  return cls.from_xml_element(fromstring(resp), ns)
11
10
 
12
11
  @classmethod
13
- def from_xml_element(cls, parsed_response: ET.Element, ns) -> Set[str]:
12
+ def from_xml_element(cls, parsed_response: ET.Element, ns) -> set[str]:
14
13
  all_tags = set()
15
14
  tag_elem = parsed_response.findall(".//t:tag", namespaces=ns)
16
15
  for tag_xml in tag_elem:
@@ -1,5 +1,5 @@
1
1
  from datetime import datetime
2
- from typing import List, Optional
2
+ from typing import Optional
3
3
 
4
4
  from defusedxml.ElementTree import fromstring
5
5
 
@@ -8,7 +8,7 @@ from tableauserverclient.models.schedule_item import ScheduleItem
8
8
  from tableauserverclient.models.target import Target
9
9
 
10
10
 
11
- class TaskItem(object):
11
+ class TaskItem:
12
12
  class Type:
13
13
  ExtractRefresh = "extractRefresh"
14
14
  DataAcceleration = "dataAcceleration"
@@ -48,9 +48,9 @@ class TaskItem(object):
48
48
  )
49
49
 
50
50
  @classmethod
51
- def from_response(cls, xml, ns, task_type=Type.ExtractRefresh) -> List["TaskItem"]:
51
+ def from_response(cls, xml, ns, task_type=Type.ExtractRefresh) -> list["TaskItem"]:
52
52
  parsed_response = fromstring(xml)
53
- all_tasks_xml = parsed_response.findall(".//t:task/t:{}".format(task_type), namespaces=ns)
53
+ all_tasks_xml = parsed_response.findall(f".//t:task/t:{task_type}", namespaces=ns)
54
54
 
55
55
  all_tasks = (TaskItem._parse_element(x, ns) for x in all_tasks_xml)
56
56
 
@@ -2,7 +2,7 @@ import io
2
2
  import xml.etree.ElementTree as ET
3
3
  from datetime import datetime
4
4
  from enum import IntEnum
5
- from typing import Dict, List, Optional, TYPE_CHECKING, Tuple
5
+ from typing import Optional, TYPE_CHECKING
6
6
 
7
7
  from defusedxml.ElementTree import fromstring
8
8
 
@@ -18,10 +18,35 @@ if TYPE_CHECKING:
18
18
  from tableauserverclient.server import Pager
19
19
 
20
20
 
21
- class UserItem(object):
21
+ class UserItem:
22
+ """
23
+ The UserItem class contains the members or attributes for the view
24
+ resources on Tableau Server. The UserItem class defines the information you
25
+ can request or query from Tableau Server. The class attributes correspond
26
+ to the attributes of a server request or response payload.
27
+
28
+
29
+ Parameters
30
+ ----------
31
+ name: str
32
+ The name of the user.
33
+
34
+ site_role: str
35
+ The role of the user on the site.
36
+
37
+ auth_setting: str
38
+ Required attribute for Tableau Cloud. How the user autenticates to the
39
+ server.
40
+ """
41
+
22
42
  tag_name: str = "user"
23
43
 
24
44
  class Roles:
45
+ """
46
+ The Roles class contains the possible roles for a user on Tableau
47
+ Server.
48
+ """
49
+
25
50
  Interactor = "Interactor"
26
51
  Publisher = "Publisher"
27
52
  ServerAdministrator = "ServerAdministrator"
@@ -43,6 +68,11 @@ class UserItem(object):
43
68
  SupportUser = "SupportUser"
44
69
 
45
70
  class Auth:
71
+ """
72
+ The Auth class contains the possible authentication settings for a user
73
+ on Tableau Cloud.
74
+ """
75
+
46
76
  OpenID = "OpenID"
47
77
  SAML = "SAML"
48
78
  TableauIDWithMFA = "TableauIDWithMFA"
@@ -57,7 +87,7 @@ class UserItem(object):
57
87
  self._id: Optional[str] = None
58
88
  self._last_login: Optional[datetime] = None
59
89
  self._workbooks = None
60
- self._favorites: Optional[Dict[str, List]] = None
90
+ self._favorites: Optional[dict[str, list]] = None
61
91
  self._groups = None
62
92
  self.email: Optional[str] = None
63
93
  self.fullname: Optional[str] = None
@@ -69,7 +99,7 @@ class UserItem(object):
69
99
 
70
100
  def __str__(self) -> str:
71
101
  str_site_role = self.site_role or "None"
72
- return "<User {} name={} role={}>".format(self.id, self.name, str_site_role)
102
+ return f"<User {self.id} name={self.name} role={str_site_role}>"
73
103
 
74
104
  def __repr__(self):
75
105
  return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
@@ -141,7 +171,7 @@ class UserItem(object):
141
171
  return self._workbooks()
142
172
 
143
173
  @property
144
- def favorites(self) -> Dict[str, List]:
174
+ def favorites(self) -> dict[str, list]:
145
175
  if self._favorites is None:
146
176
  error = "User item must be populated with favorites first."
147
177
  raise UnpopulatedPropertyError(error)
@@ -210,12 +240,12 @@ class UserItem(object):
210
240
  self._domain_name = domain_name
211
241
 
212
242
  @classmethod
213
- def from_response(cls, resp, ns) -> List["UserItem"]:
243
+ def from_response(cls, resp, ns) -> list["UserItem"]:
214
244
  element_name = ".//t:user"
215
245
  return cls._parse_xml(element_name, resp, ns)
216
246
 
217
247
  @classmethod
218
- def from_response_as_owner(cls, resp, ns) -> List["UserItem"]:
248
+ def from_response_as_owner(cls, resp, ns) -> list["UserItem"]:
219
249
  element_name = ".//t:owner"
220
250
  return cls._parse_xml(element_name, resp, ns)
221
251
 
@@ -283,7 +313,7 @@ class UserItem(object):
283
313
  domain_name,
284
314
  )
285
315
 
286
- class CSVImport(object):
316
+ class CSVImport:
287
317
  """
288
318
  This class includes hardcoded options and logic for the CSV file format defined for user import
289
319
  https://help.tableau.com/current/server/en-us/users_import.htm
@@ -308,7 +338,7 @@ class UserItem(object):
308
338
  if line is None or line is False or line == "\n" or line == "":
309
339
  return None
310
340
  line = line.strip().lower()
311
- values: List[str] = list(map(str.strip, line.split(",")))
341
+ values: list[str] = list(map(str.strip, line.split(",")))
312
342
  user = UserItem(values[UserItem.CSVImport.ColumnType.USERNAME])
313
343
  if len(values) > 1:
314
344
  if len(values) > UserItem.CSVImport.ColumnType.MAX:
@@ -337,7 +367,7 @@ class UserItem(object):
337
367
  # Read through an entire CSV file meant for user import
338
368
  # Return the number of valid lines and a list of all the invalid lines
339
369
  @staticmethod
340
- def validate_file_for_import(csv_file: io.TextIOWrapper, logger) -> Tuple[int, List[str]]:
370
+ def validate_file_for_import(csv_file: io.TextIOWrapper, logger) -> tuple[int, list[str]]:
341
371
  num_valid_lines = 0
342
372
  invalid_lines = []
343
373
  csv_file.seek(0) # set to start of file in case it has been read earlier
@@ -345,11 +375,11 @@ class UserItem(object):
345
375
  while line and line != "":
346
376
  try:
347
377
  # do not print passwords
348
- logger.info("Reading user {}".format(line[:4]))
378
+ logger.info(f"Reading user {line[:4]}")
349
379
  UserItem.CSVImport._validate_import_line_or_throw(line, logger)
350
380
  num_valid_lines += 1
351
381
  except Exception as exc:
352
- logger.info("Error parsing {}: {}".format(line[:4], exc))
382
+ logger.info(f"Error parsing {line[:4]}: {exc}")
353
383
  invalid_lines.append(line)
354
384
  line = csv_file.readline()
355
385
  return num_valid_lines, invalid_lines
@@ -358,7 +388,7 @@ class UserItem(object):
358
388
  # Iterate through each field and validate the given value against hardcoded constraints
359
389
  @staticmethod
360
390
  def _validate_import_line_or_throw(incoming, logger) -> None:
361
- _valid_attributes: List[List[str]] = [
391
+ _valid_attributes: list[list[str]] = [
362
392
  [],
363
393
  [],
364
394
  [],
@@ -373,23 +403,23 @@ class UserItem(object):
373
403
  if len(line) > UserItem.CSVImport.ColumnType.MAX:
374
404
  raise AttributeError("Too many attributes in line")
375
405
  username = line[UserItem.CSVImport.ColumnType.USERNAME.value]
376
- logger.debug("> details - {}".format(username))
406
+ logger.debug(f"> details - {username}")
377
407
  UserItem.validate_username_or_throw(username)
378
408
  for i in range(1, len(line)):
379
- logger.debug("column {}: {}".format(UserItem.CSVImport.ColumnType(i).name, line[i]))
409
+ logger.debug(f"column {UserItem.CSVImport.ColumnType(i).name}: {line[i]}")
380
410
  UserItem.CSVImport._validate_attribute_value(
381
411
  line[i], _valid_attributes[i], UserItem.CSVImport.ColumnType(i)
382
412
  )
383
413
 
384
414
  # Given a restricted set of possible values, confirm the item is in that set
385
415
  @staticmethod
386
- def _validate_attribute_value(item: str, possible_values: List[str], column_type) -> None:
416
+ def _validate_attribute_value(item: str, possible_values: list[str], column_type) -> None:
387
417
  if item is None or item == "":
388
418
  # value can be empty for any column except user, which is checked elsewhere
389
419
  return
390
420
  if item in possible_values or possible_values == []:
391
421
  return
392
- raise AttributeError("Invalid value {} for {}".format(item, column_type))
422
+ raise AttributeError(f"Invalid value {item} for {column_type}")
393
423
 
394
424
  # https://help.tableau.com/current/server/en-us/csvguidelines.htm#settings_and_site_roles
395
425
  # This logic is hardcoded to match the existing rules for import csv files
@@ -1,7 +1,8 @@
1
1
  import copy
2
2
  from datetime import datetime
3
3
  from requests import Response
4
- from typing import Callable, Iterator, List, Optional, Set
4
+ from typing import Callable, Optional
5
+ from collections.abc import Iterator
5
6
 
6
7
  from defusedxml.ElementTree import fromstring
7
8
 
@@ -11,13 +12,13 @@ from .permissions_item import PermissionsRule
11
12
  from .tag_item import TagItem
12
13
 
13
14
 
14
- class ViewItem(object):
15
+ class ViewItem:
15
16
  def __init__(self) -> None:
16
17
  self._content_url: Optional[str] = None
17
18
  self._created_at: Optional[datetime] = None
18
19
  self._id: Optional[str] = None
19
20
  self._image: Optional[Callable[[], bytes]] = None
20
- self._initial_tags: Set[str] = set()
21
+ self._initial_tags: set[str] = set()
21
22
  self._name: Optional[str] = None
22
23
  self._owner_id: Optional[str] = None
23
24
  self._preview_image: Optional[Callable[[], bytes]] = None
@@ -29,15 +30,15 @@ class ViewItem(object):
29
30
  self._sheet_type: Optional[str] = None
30
31
  self._updated_at: Optional[datetime] = None
31
32
  self._workbook_id: Optional[str] = None
32
- self._permissions: Optional[Callable[[], List[PermissionsRule]]] = None
33
- self.tags: Set[str] = set()
33
+ self._permissions: Optional[Callable[[], list[PermissionsRule]]] = None
34
+ self.tags: set[str] = set()
34
35
  self._data_acceleration_config = {
35
36
  "acceleration_enabled": None,
36
37
  "acceleration_status": None,
37
38
  }
38
39
 
39
40
  def __str__(self):
40
- return "<ViewItem {0} '{1}' contentUrl='{2}' project={3}>".format(
41
+ return "<ViewItem {} '{}' contentUrl='{}' project={}>".format(
41
42
  self._id, self.name, self.content_url, self.project_id
42
43
  )
43
44
 
@@ -146,21 +147,21 @@ class ViewItem(object):
146
147
  self._data_acceleration_config = value
147
148
 
148
149
  @property
149
- def permissions(self) -> List[PermissionsRule]:
150
+ def permissions(self) -> list[PermissionsRule]:
150
151
  if self._permissions is None:
151
152
  error = "View item must be populated with permissions first."
152
153
  raise UnpopulatedPropertyError(error)
153
154
  return self._permissions()
154
155
 
155
- def _set_permissions(self, permissions: Callable[[], List[PermissionsRule]]) -> None:
156
+ def _set_permissions(self, permissions: Callable[[], list[PermissionsRule]]) -> None:
156
157
  self._permissions = permissions
157
158
 
158
159
  @classmethod
159
- def from_response(cls, resp: "Response", ns, workbook_id="") -> List["ViewItem"]:
160
+ def from_response(cls, resp: "Response", ns, workbook_id="") -> list["ViewItem"]:
160
161
  return cls.from_xml_element(fromstring(resp), ns, workbook_id)
161
162
 
162
163
  @classmethod
163
- def from_xml_element(cls, parsed_response, ns, workbook_id="") -> List["ViewItem"]:
164
+ def from_xml_element(cls, parsed_response, ns, workbook_id="") -> list["ViewItem"]:
164
165
  all_view_items = list()
165
166
  all_view_xml = parsed_response.findall(".//t:view", namespaces=ns)
166
167
  for view_xml in all_view_xml:
@@ -0,0 +1,78 @@
1
+ import datetime as dt
2
+ import json
3
+ from typing import Callable, Optional
4
+ from collections.abc import Iterable
5
+ from xml.etree.ElementTree import Element
6
+
7
+ from defusedxml.ElementTree import fromstring
8
+
9
+ from tableauserverclient.datetime_helpers import parse_datetime
10
+ from tableauserverclient.models.connection_item import ConnectionItem
11
+ from tableauserverclient.models.exceptions import UnpopulatedPropertyError
12
+ from tableauserverclient.models.permissions_item import PermissionsRule
13
+
14
+
15
+ class VirtualConnectionItem:
16
+ def __init__(self, name: str) -> None:
17
+ self.name = name
18
+ self.created_at: Optional[dt.datetime] = None
19
+ self.has_extracts: Optional[bool] = None
20
+ self._id: Optional[str] = None
21
+ self.is_certified: Optional[bool] = None
22
+ self.updated_at: Optional[dt.datetime] = None
23
+ self.webpage_url: Optional[str] = None
24
+ self._connections: Optional[Callable[[], Iterable[ConnectionItem]]] = None
25
+ self.project_id: Optional[str] = None
26
+ self.owner_id: Optional[str] = None
27
+ self.content: Optional[dict[str, dict]] = None
28
+ self.certification_note: Optional[str] = None
29
+
30
+ def __str__(self) -> str:
31
+ return f"{self.__class__.__qualname__}(name={self.name})"
32
+
33
+ def __repr__(self) -> str:
34
+ return f"<{self!s}>"
35
+
36
+ def _set_permissions(self, permissions):
37
+ self._permissions = permissions
38
+
39
+ @property
40
+ def id(self) -> Optional[str]:
41
+ return self._id
42
+
43
+ @property
44
+ def permissions(self) -> list[PermissionsRule]:
45
+ if self._permissions is None:
46
+ error = "Workbook item must be populated with permissions first."
47
+ raise UnpopulatedPropertyError(error)
48
+ return self._permissions()
49
+
50
+ @property
51
+ def connections(self) -> Iterable[ConnectionItem]:
52
+ if self._connections is None:
53
+ raise AttributeError("connections not populated. Call populate_connections() first.")
54
+ return self._connections()
55
+
56
+ @classmethod
57
+ def from_response(cls, response: bytes, ns: dict[str, str]) -> list["VirtualConnectionItem"]:
58
+ parsed_response = fromstring(response)
59
+ return [cls.from_xml(xml, ns) for xml in parsed_response.findall(".//t:virtualConnection[@name]", ns)]
60
+
61
+ @classmethod
62
+ def from_xml(cls, xml: Element, ns: dict[str, str]) -> "VirtualConnectionItem":
63
+ v_conn = cls(xml.get("name", ""))
64
+ v_conn._id = xml.get("id", None)
65
+ v_conn.webpage_url = xml.get("webpageUrl", None)
66
+ v_conn.created_at = parse_datetime(xml.get("createdAt", None))
67
+ v_conn.updated_at = parse_datetime(xml.get("updatedAt", None))
68
+ v_conn.is_certified = string_to_bool(s) if (s := xml.get("isCertified", None)) else None
69
+ v_conn.certification_note = xml.get("certificationNote", None)
70
+ v_conn.has_extracts = string_to_bool(s) if (s := xml.get("hasExtracts", None)) else None
71
+ v_conn.project_id = p.get("id", None) if ((p := xml.find(".//t:project[@id]", ns)) is not None) else None
72
+ v_conn.owner_id = o.get("id", None) if ((o := xml.find(".//t:owner[@id]", ns)) is not None) else None
73
+ v_conn.content = json.loads(c.text or "{}") if ((c := xml.find(".//t:content", ns)) is not None) else None
74
+ return v_conn
75
+
76
+
77
+ def string_to_bool(s: str) -> bool:
78
+ return s.lower() in ["true", "1", "t", "y", "yes"]
@@ -1,6 +1,6 @@
1
1
  import re
2
2
  import xml.etree.ElementTree as ET
3
- from typing import List, Optional, Tuple, Type
3
+ from typing import Optional
4
4
 
5
5
  from defusedxml.ElementTree import fromstring
6
6
 
@@ -13,7 +13,7 @@ def _parse_event(events):
13
13
  return NAMESPACE_RE.sub("", event.tag)
14
14
 
15
15
 
16
- class WebhookItem(object):
16
+ class WebhookItem:
17
17
  def __init__(self):
18
18
  self._id: Optional[str] = None
19
19
  self.name: Optional[str] = None
@@ -45,10 +45,10 @@ class WebhookItem(object):
45
45
 
46
46
  @event.setter
47
47
  def event(self, value: str) -> None:
48
- self._event = "webhook-source-event-{}".format(value)
48
+ self._event = f"webhook-source-event-{value}"
49
49
 
50
50
  @classmethod
51
- def from_response(cls: Type["WebhookItem"], resp: bytes, ns) -> List["WebhookItem"]:
51
+ def from_response(cls: type["WebhookItem"], resp: bytes, ns) -> list["WebhookItem"]:
52
52
  all_webhooks_items = list()
53
53
  parsed_response = fromstring(resp)
54
54
  all_webhooks_xml = parsed_response.findall(".//t:webhook", namespaces=ns)
@@ -61,7 +61,7 @@ class WebhookItem(object):
61
61
  return all_webhooks_items
62
62
 
63
63
  @staticmethod
64
- def _parse_element(webhook_xml: ET.Element, ns) -> Tuple:
64
+ def _parse_element(webhook_xml: ET.Element, ns) -> tuple:
65
65
  id = webhook_xml.get("id", None)
66
66
  name = webhook_xml.get("name", None)
67
67
 
@@ -82,4 +82,4 @@ class WebhookItem(object):
82
82
  return id, name, url, event, owner_id
83
83
 
84
84
  def __repr__(self) -> str:
85
- return "<Webhook id={} name={} url={} event={}>".format(self.id, self.name, self.url, self.event)
85
+ return f"<Webhook id={self.id} name={self.name} url={self.url} event={self.event}>"
@@ -2,7 +2,7 @@ import copy
2
2
  import datetime
3
3
  import uuid
4
4
  import xml.etree.ElementTree as ET
5
- from typing import Callable, Dict, List, Optional, Set
5
+ from typing import Callable, Optional
6
6
 
7
7
  from defusedxml.ElementTree import fromstring
8
8
 
@@ -20,7 +20,85 @@ from .view_item import ViewItem
20
20
  from .data_freshness_policy_item import DataFreshnessPolicyItem
21
21
 
22
22
 
23
- class WorkbookItem(object):
23
+ class WorkbookItem:
24
+ """
25
+ The workbook resources for Tableau are defined in the WorkbookItem class.
26
+ The class corresponds to the workbook resources you can access using the
27
+ Tableau REST API. Some workbook methods take an instance of the WorkbookItem
28
+ class as arguments. The workbook item specifies the project.
29
+
30
+ Parameters
31
+ ----------
32
+ project_id : Optional[str], optional
33
+ The project ID for the workbook, by default None.
34
+
35
+ name : Optional[str], optional
36
+ The name of the workbook, by default None.
37
+
38
+ show_tabs : bool, optional
39
+ Determines whether the workbook shows tabs for the view.
40
+
41
+ Attributes
42
+ ----------
43
+ connections : list[ConnectionItem]
44
+ The list of data connections (ConnectionItem) for the data sources used
45
+ by the workbook. You must first call the workbooks.populate_connections
46
+ method to access this data. See the ConnectionItem class.
47
+
48
+ content_url : Optional[str]
49
+ The name of the workbook as it appears in the URL.
50
+
51
+ created_at : Optional[datetime.datetime]
52
+ The date and time the workbook was created.
53
+
54
+ description : Optional[str]
55
+ User-defined description of the workbook.
56
+
57
+ id : Optional[str]
58
+ The identifier for the workbook. You need this value to query a specific
59
+ workbook or to delete a workbook with the get_by_id and delete methods.
60
+
61
+ owner_id : Optional[str]
62
+ The identifier for the owner (UserItem) of the workbook.
63
+
64
+ preview_image : bytes
65
+ The thumbnail image for the view. You must first call the
66
+ workbooks.populate_preview_image method to access this data.
67
+
68
+ project_name : Optional[str]
69
+ The name of the project that contains the workbook.
70
+
71
+ size: int
72
+ The size of the workbook in megabytes.
73
+
74
+ hidden_views: Optional[list[str]]
75
+ List of string names of views that need to be hidden when the workbook
76
+ is published.
77
+
78
+ tags: set[str]
79
+ The set of tags associated with the workbook.
80
+
81
+ updated_at : Optional[datetime.datetime]
82
+ The date and time the workbook was last updated.
83
+
84
+ views : list[ViewItem]
85
+ The list of views (ViewItem) for the workbook. You must first call the
86
+ workbooks.populate_views method to access this data. See the ViewItem
87
+ class.
88
+
89
+ web_page_url : Optional[str]
90
+ The full URL for the workbook.
91
+
92
+ Examples
93
+ --------
94
+ # creating a new instance of a WorkbookItem
95
+ >>> import tableauserverclient as TSC
96
+
97
+ >>> # Create new workbook_item with project id '3a8b6148-493c-11e6-a621-6f3499394a39'
98
+
99
+ >>> new_workbook = TSC.WorkbookItem('3a8b6148-493c-11e6-a621-6f3499394a39')
100
+ """
101
+
24
102
  def __init__(self, project_id: Optional[str] = None, name: Optional[str] = None, show_tabs: bool = False) -> None:
25
103
  self._connections = None
26
104
  self._content_url = None
@@ -35,15 +113,15 @@ class WorkbookItem(object):
35
113
  self._revisions = None
36
114
  self._size = None
37
115
  self._updated_at = None
38
- self._views: Optional[Callable[[], List[ViewItem]]] = None
116
+ self._views: Optional[Callable[[], list[ViewItem]]] = None
39
117
  self.name = name
40
118
  self._description = None
41
119
  self.owner_id: Optional[str] = None
42
120
  # workaround for Personal Space workbooks without a project
43
121
  self.project_id: Optional[str] = project_id or uuid.uuid4().__str__()
44
122
  self.show_tabs = show_tabs
45
- self.hidden_views: Optional[List[str]] = None
46
- self.tags: Set[str] = set()
123
+ self.hidden_views: Optional[list[str]] = None
124
+ self.tags: set[str] = set()
47
125
  self.data_acceleration_config = {
48
126
  "acceleration_enabled": None,
49
127
  "accelerate_now": None,
@@ -56,7 +134,7 @@ class WorkbookItem(object):
56
134
  return None
57
135
 
58
136
  def __str__(self):
59
- return "<WorkbookItem {0} '{1}' contentUrl='{2}' project={3}>".format(
137
+ return "<WorkbookItem {} '{}' contentUrl='{}' project={}>".format(
60
138
  self._id, self.name, self.content_url, self.project_id
61
139
  )
62
140
 
@@ -64,14 +142,14 @@ class WorkbookItem(object):
64
142
  return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
65
143
 
66
144
  @property
67
- def connections(self) -> List[ConnectionItem]:
145
+ def connections(self) -> list[ConnectionItem]:
68
146
  if self._connections is None:
69
147
  error = "Workbook item must be populated with connections first."
70
148
  raise UnpopulatedPropertyError(error)
71
149
  return self._connections()
72
150
 
73
151
  @property
74
- def permissions(self) -> List[PermissionsRule]:
152
+ def permissions(self) -> list[PermissionsRule]:
75
153
  if self._permissions is None:
76
154
  error = "Workbook item must be populated with permissions first."
77
155
  raise UnpopulatedPropertyError(error)
@@ -152,7 +230,7 @@ class WorkbookItem(object):
152
230
  return self._updated_at
153
231
 
154
232
  @property
155
- def views(self) -> List[ViewItem]:
233
+ def views(self) -> list[ViewItem]:
156
234
  # Views can be set in an initial workbook response OR by a call
157
235
  # to Server. Without getting too fancy, I think we can rely on
158
236
  # returning a list from the response, until they call
@@ -191,7 +269,7 @@ class WorkbookItem(object):
191
269
  self._data_freshness_policy = value
192
270
 
193
271
  @property
194
- def revisions(self) -> List[RevisionItem]:
272
+ def revisions(self) -> list[RevisionItem]:
195
273
  if self._revisions is None:
196
274
  error = "Workbook item must be populated with revisions first."
197
275
  raise UnpopulatedPropertyError(error)
@@ -203,7 +281,7 @@ class WorkbookItem(object):
203
281
  def _set_permissions(self, permissions):
204
282
  self._permissions = permissions
205
283
 
206
- def _set_views(self, views: Callable[[], List[ViewItem]]) -> None:
284
+ def _set_views(self, views: Callable[[], list[ViewItem]]) -> None:
207
285
  self._views = views
208
286
 
209
287
  def _set_pdf(self, pdf: Callable[[], bytes]) -> None:
@@ -316,7 +394,7 @@ class WorkbookItem(object):
316
394
  self.data_freshness_policy = data_freshness_policy
317
395
 
318
396
  @classmethod
319
- def from_response(cls, resp: str, ns: Dict[str, str]) -> List["WorkbookItem"]:
397
+ def from_response(cls, resp: str, ns: dict[str, str]) -> list["WorkbookItem"]:
320
398
  all_workbook_items = list()
321
399
  parsed_response = fromstring(resp)
322
400
  all_workbook_xml = parsed_response.findall(".//t:workbook", namespaces=ns)
@@ -11,7 +11,7 @@ class UnknownNamespaceError(Exception):
11
11
  pass
12
12
 
13
13
 
14
- class Namespace(object):
14
+ class Namespace:
15
15
  def __init__(self):
16
16
  self._namespace = {"t": NEW_NAMESPACE}
17
17
  self._detected = False
@@ -11,7 +11,7 @@ from tableauserverclient.server.filter import Filter
11
11
  from tableauserverclient.server.sort import Sort
12
12
  from tableauserverclient.server.server import Server
13
13
  from tableauserverclient.server.pager import Pager
14
- from tableauserverclient.server.endpoint.exceptions import NotSignedInError
14
+ from tableauserverclient.server.endpoint.exceptions import FailedSignInError, NotSignedInError
15
15
 
16
16
  from tableauserverclient.server.endpoint import (
17
17
  Auth,
@@ -57,6 +57,7 @@ __all__ = [
57
57
  "Sort",
58
58
  "Server",
59
59
  "Pager",
60
+ "FailedSignInError",
60
61
  "NotSignedInError",
61
62
  "Auth",
62
63
  "CustomViews",
@@ -12,7 +12,9 @@ from tableauserverclient.server.endpoint.flow_runs_endpoint import FlowRuns
12
12
  from tableauserverclient.server.endpoint.flows_endpoint import Flows
13
13
  from tableauserverclient.server.endpoint.flow_task_endpoint import FlowTasks
14
14
  from tableauserverclient.server.endpoint.groups_endpoint import Groups
15
+ from tableauserverclient.server.endpoint.groupsets_endpoint import GroupSets
15
16
  from tableauserverclient.server.endpoint.jobs_endpoint import Jobs
17
+ from tableauserverclient.server.endpoint.linked_tasks_endpoint import LinkedTasks
16
18
  from tableauserverclient.server.endpoint.metadata_endpoint import Metadata
17
19
  from tableauserverclient.server.endpoint.metrics_endpoint import Metrics
18
20
  from tableauserverclient.server.endpoint.projects_endpoint import Projects
@@ -21,9 +23,11 @@ from tableauserverclient.server.endpoint.server_info_endpoint import ServerInfo
21
23
  from tableauserverclient.server.endpoint.sites_endpoint import Sites
22
24
  from tableauserverclient.server.endpoint.subscriptions_endpoint import Subscriptions
23
25
  from tableauserverclient.server.endpoint.tables_endpoint import Tables
26
+ from tableauserverclient.server.endpoint.resource_tagger import Tags
24
27
  from tableauserverclient.server.endpoint.tasks_endpoint import Tasks
25
28
  from tableauserverclient.server.endpoint.users_endpoint import Users
26
29
  from tableauserverclient.server.endpoint.views_endpoint import Views
30
+ from tableauserverclient.server.endpoint.virtual_connections_endpoint import VirtualConnections
27
31
  from tableauserverclient.server.endpoint.webhooks_endpoint import Webhooks
28
32
  from tableauserverclient.server.endpoint.workbooks_endpoint import Workbooks
29
33
 
@@ -43,7 +47,9 @@ __all__ = [
43
47
  "Flows",
44
48
  "FlowTasks",
45
49
  "Groups",
50
+ "GroupSets",
46
51
  "Jobs",
52
+ "LinkedTasks",
47
53
  "Metadata",
48
54
  "Metrics",
49
55
  "Projects",
@@ -53,9 +59,11 @@ __all__ = [
53
59
  "Sites",
54
60
  "Subscriptions",
55
61
  "Tables",
62
+ "Tags",
56
63
  "Tasks",
57
64
  "Users",
58
65
  "Views",
66
+ "VirtualConnections",
59
67
  "Webhooks",
60
68
  "Workbooks",
61
69
  ]