tableauserverclient 0.25__py3-none-any.whl → 0.27.post0.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. tableauserverclient/__init__.py +42 -1
  2. tableauserverclient/_version.py +4 -4
  3. tableauserverclient/config.py +13 -0
  4. tableauserverclient/datetime_helpers.py +4 -0
  5. tableauserverclient/helpers/logging.py +4 -0
  6. tableauserverclient/models/__init__.py +1 -1
  7. tableauserverclient/models/column_item.py +3 -0
  8. tableauserverclient/models/connection_credentials.py +7 -0
  9. tableauserverclient/models/connection_item.py +1 -1
  10. tableauserverclient/models/custom_view_item.py +5 -0
  11. tableauserverclient/models/data_acceleration_report_item.py +3 -0
  12. tableauserverclient/models/datasource_item.py +10 -54
  13. tableauserverclient/models/favorites_item.py +56 -40
  14. tableauserverclient/models/fileupload_item.py +2 -2
  15. tableauserverclient/models/flow_item.py +30 -25
  16. tableauserverclient/models/group_item.py +1 -4
  17. tableauserverclient/models/interval_item.py +12 -0
  18. tableauserverclient/models/job_item.py +10 -1
  19. tableauserverclient/models/metric_item.py +36 -29
  20. tableauserverclient/models/pagination_item.py +3 -0
  21. tableauserverclient/models/permissions_item.py +8 -5
  22. tableauserverclient/models/project_item.py +11 -13
  23. tableauserverclient/models/schedule_item.py +6 -7
  24. tableauserverclient/models/server_info_item.py +2 -2
  25. tableauserverclient/models/site_item.py +3 -0
  26. tableauserverclient/models/subscription_item.py +8 -0
  27. tableauserverclient/models/table_item.py +6 -0
  28. tableauserverclient/models/tableau_auth.py +41 -6
  29. tableauserverclient/models/tableau_types.py +4 -2
  30. tableauserverclient/models/user_item.py +5 -1
  31. tableauserverclient/models/view_item.py +39 -36
  32. tableauserverclient/models/workbook_item.py +14 -43
  33. tableauserverclient/server/__init__.py +1 -3
  34. tableauserverclient/server/endpoint/__init__.py +1 -5
  35. tableauserverclient/server/endpoint/auth_endpoint.py +29 -8
  36. tableauserverclient/server/endpoint/custom_views_endpoint.py +1 -1
  37. tableauserverclient/server/endpoint/data_acceleration_report_endpoint.py +1 -1
  38. tableauserverclient/server/endpoint/data_alert_endpoint.py +1 -1
  39. tableauserverclient/server/endpoint/databases_endpoint.py +1 -1
  40. tableauserverclient/server/endpoint/datasources_endpoint.py +21 -15
  41. tableauserverclient/server/endpoint/default_permissions_endpoint.py +1 -1
  42. tableauserverclient/server/endpoint/dqw_endpoint.py +1 -1
  43. tableauserverclient/server/endpoint/endpoint.py +98 -11
  44. tableauserverclient/server/endpoint/exceptions.py +1 -5
  45. tableauserverclient/server/endpoint/favorites_endpoint.py +71 -29
  46. tableauserverclient/server/endpoint/fileuploads_endpoint.py +11 -10
  47. tableauserverclient/server/endpoint/flow_runs_endpoint.py +1 -1
  48. tableauserverclient/server/endpoint/flows_endpoint.py +5 -5
  49. tableauserverclient/server/endpoint/groups_endpoint.py +5 -2
  50. tableauserverclient/server/endpoint/jobs_endpoint.py +1 -1
  51. tableauserverclient/server/endpoint/metadata_endpoint.py +1 -1
  52. tableauserverclient/server/endpoint/metrics_endpoint.py +1 -1
  53. tableauserverclient/server/endpoint/permissions_endpoint.py +1 -1
  54. tableauserverclient/server/endpoint/projects_endpoint.py +3 -1
  55. tableauserverclient/server/endpoint/resource_tagger.py +3 -3
  56. tableauserverclient/server/endpoint/schedules_endpoint.py +2 -1
  57. tableauserverclient/server/endpoint/server_info_endpoint.py +2 -4
  58. tableauserverclient/server/endpoint/sites_endpoint.py +1 -1
  59. tableauserverclient/server/endpoint/subscriptions_endpoint.py +1 -1
  60. tableauserverclient/server/endpoint/tables_endpoint.py +1 -1
  61. tableauserverclient/server/endpoint/tasks_endpoint.py +12 -1
  62. tableauserverclient/server/endpoint/users_endpoint.py +1 -1
  63. tableauserverclient/server/endpoint/views_endpoint.py +1 -1
  64. tableauserverclient/server/endpoint/webhooks_endpoint.py +1 -1
  65. tableauserverclient/server/endpoint/workbooks_endpoint.py +4 -2
  66. tableauserverclient/server/exceptions.py +8 -1
  67. tableauserverclient/server/filter.py +5 -1
  68. tableauserverclient/server/request_factory.py +56 -12
  69. tableauserverclient/server/request_options.py +4 -2
  70. tableauserverclient/server/server.py +12 -13
  71. {tableauserverclient-0.25.dist-info → tableauserverclient-0.27.post0.dev1.dist-info}/METADATA +12 -10
  72. tableauserverclient-0.27.post0.dev1.dist-info/RECORD +97 -0
  73. {tableauserverclient-0.25.dist-info → tableauserverclient-0.27.post0.dev1.dist-info}/WHEEL +1 -1
  74. tableauserverclient-0.25.dist-info/RECORD +0 -95
  75. {tableauserverclient-0.25.dist-info → tableauserverclient-0.27.post0.dev1.dist-info}/LICENSE +0 -0
  76. {tableauserverclient-0.25.dist-info → tableauserverclient-0.27.post0.dev1.dist-info}/LICENSE.versioneer +0 -0
  77. {tableauserverclient-0.25.dist-info → tableauserverclient-0.27.post0.dev1.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@ from typing import List, Optional, Set
5
5
  from tableauserverclient.datetime_helpers import parse_datetime
6
6
  from .property_decorators import property_is_boolean, property_is_datetime
7
7
  from .tag_item import TagItem
8
+ from .permissions_item import Permission
8
9
 
9
10
 
10
11
  class MetricItem(object):
@@ -22,6 +23,7 @@ class MetricItem(object):
22
23
  self._view_id: Optional[str] = None
23
24
  self._initial_tags: Set[str] = set()
24
25
  self.tags: Set[str] = set()
26
+ self._permissions: Optional[Permission] = None
25
27
 
26
28
  @property
27
29
  def id(self) -> Optional[str]:
@@ -110,9 +112,15 @@ class MetricItem(object):
110
112
  def view_id(self, value: Optional[str]) -> None:
111
113
  self._view_id = value
112
114
 
113
- def __repr__(self):
115
+ def _set_permissions(self, permissions):
116
+ self._permissions = permissions
117
+
118
+ def __str__(self):
114
119
  return "<MetricItem# name={_name} id={_id} owner_id={_owner_id}>".format(**vars(self))
115
120
 
121
+ def __repr__(self):
122
+ return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
123
+
116
124
  @classmethod
117
125
  def from_response(
118
126
  cls,
@@ -123,36 +131,35 @@ class MetricItem(object):
123
131
  parsed_response = ET.fromstring(resp)
124
132
  all_metric_xml = parsed_response.findall(".//t:metric", namespaces=ns)
125
133
  for metric_xml in all_metric_xml:
126
- metric_item = cls()
127
- metric_item._id = metric_xml.get("id", None)
128
- metric_item._name = metric_xml.get("name", None)
129
- metric_item._description = metric_xml.get("description", None)
130
- metric_item._webpage_url = metric_xml.get("webpageUrl", None)
131
- metric_item._created_at = parse_datetime(metric_xml.get("createdAt", None))
132
- metric_item._updated_at = parse_datetime(metric_xml.get("updatedAt", None))
133
- metric_item._suspended = string_to_bool(metric_xml.get("suspended", ""))
134
- for owner in metric_xml.findall(".//t:owner", namespaces=ns):
135
- metric_item._owner_id = owner.get("id", None)
136
-
137
- for project in metric_xml.findall(".//t:project", namespaces=ns):
138
- metric_item._project_id = project.get("id", None)
139
- metric_item._project_name = project.get("name", None)
140
-
141
- for view in metric_xml.findall(".//t:underlyingView", namespaces=ns):
142
- metric_item._view_id = view.get("id", None)
143
-
144
- tags = set()
145
- tags_elem = metric_xml.find(".//t:tags", namespaces=ns)
146
- if tags_elem is not None:
147
- all_tags = TagItem.from_xml_element(tags_elem, ns)
148
- tags = all_tags
149
-
150
- metric_item.tags = tags
151
- metric_item._initial_tags = tags
152
-
153
- all_metric_items.append(metric_item)
134
+ all_metric_items.append(cls.from_xml(metric_xml, ns))
154
135
  return all_metric_items
155
136
 
137
+ @classmethod
138
+ def from_xml(cls, metric_xml, ns):
139
+ metric_item = cls()
140
+ metric_item._id = metric_xml.get("id", None)
141
+ metric_item._name = metric_xml.get("name", None)
142
+ metric_item._description = metric_xml.get("description", None)
143
+ metric_item._webpage_url = metric_xml.get("webpageUrl", None)
144
+ metric_item._created_at = parse_datetime(metric_xml.get("createdAt", None))
145
+ metric_item._updated_at = parse_datetime(metric_xml.get("updatedAt", None))
146
+ metric_item._suspended = string_to_bool(metric_xml.get("suspended", ""))
147
+ for owner in metric_xml.findall(".//t:owner", namespaces=ns):
148
+ metric_item._owner_id = owner.get("id", None)
149
+ for project in metric_xml.findall(".//t:project", namespaces=ns):
150
+ metric_item._project_id = project.get("id", None)
151
+ metric_item._project_name = project.get("name", None)
152
+ for view in metric_xml.findall(".//t:underlyingView", namespaces=ns):
153
+ metric_item._view_id = view.get("id", None)
154
+ tags = set()
155
+ tags_elem = metric_xml.find(".//t:tags", namespaces=ns)
156
+ if tags_elem is not None:
157
+ all_tags = TagItem.from_xml_element(tags_elem, ns)
158
+ tags = all_tags
159
+ metric_item.tags = tags
160
+ metric_item._initial_tags = tags
161
+ return metric_item
162
+
156
163
 
157
164
  # Used to convert string represented boolean to a boolean type
158
165
  def string_to_bool(s: str) -> bool:
@@ -7,6 +7,9 @@ class PaginationItem(object):
7
7
  self._page_size = None
8
8
  self._total_available = None
9
9
 
10
+ def __repr__(self):
11
+ return f"<PaginationItem page_number={self._page_number} page_size={self._page_size} total={self._total_available}>"
12
+
10
13
  @property
11
14
  def page_number(self) -> int:
12
15
  return self._page_number
@@ -1,4 +1,3 @@
1
- import logging
2
1
  import xml.etree.ElementTree as ET
3
2
  from typing import Dict, List, Optional
4
3
 
@@ -9,7 +8,7 @@ from .group_item import GroupItem
9
8
  from .reference_item import ResourceReference
10
9
  from .user_item import UserItem
11
10
 
12
- logger = logging.getLogger("tableau.models.permissions_item")
11
+ from tableauserverclient.helpers.logging import logger
13
12
 
14
13
 
15
14
  class Permission:
@@ -17,6 +16,9 @@ class Permission:
17
16
  Allow = "Allow"
18
17
  Deny = "Deny"
19
18
 
19
+ def __repr__(self):
20
+ return "<Enum Mode: Allow | Deny>"
21
+
20
22
  class Capability:
21
23
  AddComment = "AddComment"
22
24
  ChangeHierarchy = "ChangeHierarchy"
@@ -39,17 +41,18 @@ class Permission:
39
41
  CreateRefreshMetrics = "CreateRefreshMetrics"
40
42
  SaveAs = "SaveAs"
41
43
 
44
+ def __repr__(self):
45
+ return "<Enum Capability: AddComment | ChangeHierarchy | ChangePermission ... (17 more) >"
46
+
42
47
 
43
48
  class PermissionsRule(object):
44
49
  def __init__(self, grantee: ResourceReference, capabilities: Dict[str, str]) -> None:
45
50
  self.grantee = grantee
46
51
  self.capabilities = capabilities
47
52
 
48
- def __str__(self):
53
+ def __repr__(self):
49
54
  return "<PermissionsRule grantee={}, capabilities={}>".format(self.grantee, self.capabilities)
50
55
 
51
- __repr__ = __str__
52
-
53
56
  @classmethod
54
57
  def from_response(cls, resp, ns=None) -> List["PermissionsRule"]:
55
58
  parsed_response = fromstring(resp)
@@ -21,10 +21,11 @@ class ProjectItem(object):
21
21
 
22
22
  def __init__(
23
23
  self,
24
- name: str,
24
+ name: Optional[str] = None,
25
25
  description: Optional[str] = None,
26
26
  content_permissions: Optional[str] = None,
27
27
  parent_id: Optional[str] = None,
28
+ samples: Optional[bool] = None,
28
29
  ) -> None:
29
30
  self._content_permissions = None
30
31
  self._id: Optional[str] = None
@@ -32,6 +33,7 @@ class ProjectItem(object):
32
33
  self.name: str = name
33
34
  self.content_permissions: Optional[str] = content_permissions
34
35
  self.parent_id: Optional[str] = parent_id
36
+ self._samples: Optional[bool] = samples
35
37
 
36
38
  self._permissions = None
37
39
  self._default_workbook_permissions = None
@@ -104,11 +106,10 @@ class ProjectItem(object):
104
106
  return self._id
105
107
 
106
108
  @property
107
- def name(self) -> str:
109
+ def name(self) -> Optional[str]:
108
110
  return self._name
109
111
 
110
112
  @name.setter
111
- @property_not_empty
112
113
  def name(self, value: str) -> None:
113
114
  self._name = value
114
115
 
@@ -173,19 +174,16 @@ class ProjectItem(object):
173
174
  all_project_xml = parsed_response.findall(".//t:project", namespaces=ns)
174
175
 
175
176
  for project_xml in all_project_xml:
176
- (
177
- id,
178
- name,
179
- description,
180
- content_permissions,
181
- parent_id,
182
- owner_id,
183
- ) = cls._parse_element(project_xml)
184
- project_item = cls(name)
185
- project_item._set_values(id, name, description, content_permissions, parent_id, owner_id)
177
+ project_item = cls.from_xml(project_xml)
186
178
  all_project_items.append(project_item)
187
179
  return all_project_items
188
180
 
181
+ @classmethod
182
+ def from_xml(cls, project_xml, namespace=None) -> "ProjectItem":
183
+ project_item = cls()
184
+ project_item._set_values(*cls._parse_element(project_xml))
185
+ return project_item
186
+
189
187
  @staticmethod
190
188
  def _parse_element(project_xml):
191
189
  id = project_xml.get("id", None)
@@ -14,8 +14,6 @@ from .interval_item import (
14
14
  )
15
15
  from .property_decorators import (
16
16
  property_is_enum,
17
- property_not_nullable,
18
- property_is_int,
19
17
  )
20
18
 
21
19
  Interval = Union[HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval]
@@ -27,6 +25,7 @@ class ScheduleItem(object):
27
25
  Flow = "Flow"
28
26
  Subscription = "Subscription"
29
27
  DataAcceleration = "DataAcceleration"
28
+ ActiveDirectorySync = "ActiveDirectorySync"
30
29
 
31
30
  class ExecutionOrder:
32
31
  Parallel = "Parallel"
@@ -49,9 +48,12 @@ class ScheduleItem(object):
49
48
  self.priority: int = priority
50
49
  self.schedule_type: str = schedule_type
51
50
 
52
- def __repr__(self):
51
+ def __str__(self):
53
52
  return '<Schedule#{_id} "{_name}" {interval_item}>'.format(**vars(self))
54
53
 
54
+ def __repr__(self):
55
+ return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
56
+
55
57
  @property
56
58
  def created_at(self) -> Optional[datetime]:
57
59
  return self._created_at
@@ -74,11 +76,10 @@ class ScheduleItem(object):
74
76
  return self._id
75
77
 
76
78
  @property
77
- def name(self) -> str:
79
+ def name(self) -> Optional[str]:
78
80
  return self._name
79
81
 
80
82
  @name.setter
81
- @property_not_nullable
82
83
  def name(self, value: str):
83
84
  self._name = value
84
85
 
@@ -91,7 +92,6 @@ class ScheduleItem(object):
91
92
  return self._priority
92
93
 
93
94
  @priority.setter
94
- @property_is_int(range=(1, 100))
95
95
  def priority(self, value: int):
96
96
  self._priority = value
97
97
 
@@ -101,7 +101,6 @@ class ScheduleItem(object):
101
101
 
102
102
  @schedule_type.setter
103
103
  @property_is_enum(Type)
104
- @property_not_nullable
105
104
  def schedule_type(self, value: str):
106
105
  self._schedule_type = value
107
106
 
@@ -3,6 +3,7 @@ import warnings
3
3
  import xml
4
4
 
5
5
  from defusedxml.ElementTree import fromstring
6
+ from tableauserverclient.helpers.logging import logger
6
7
 
7
8
 
8
9
  class ServerInfoItem(object):
@@ -11,7 +12,7 @@ class ServerInfoItem(object):
11
12
  self._build_number = build_number
12
13
  self._rest_api_version = rest_api_version
13
14
 
14
- def __str__(self):
15
+ def __repr__(self):
15
16
  return (
16
17
  "ServerInfoItem: [product version: "
17
18
  + self._product_version
@@ -36,7 +37,6 @@ class ServerInfoItem(object):
36
37
 
37
38
  @classmethod
38
39
  def from_response(cls, resp, ns):
39
- logger = logging.getLogger("TSC.ServerInfo")
40
40
  try:
41
41
  parsed_response = fromstring(resp)
42
42
  except xml.etree.ElementTree.ParseError as error:
@@ -39,6 +39,9 @@ class SiteItem(object):
39
39
  + ">"
40
40
  )
41
41
 
42
+ def __repr__(self):
43
+ return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
44
+
42
45
  class AdminMode:
43
46
  ContentAndUsers: str = "ContentAndUsers"
44
47
  ContentOnly: str = "ContentOnly"
@@ -4,6 +4,7 @@ from defusedxml.ElementTree import fromstring
4
4
 
5
5
  from .property_decorators import property_is_boolean
6
6
  from .target import Target
7
+ from tableauserverclient.models import ScheduleItem
7
8
 
8
9
  if TYPE_CHECKING:
9
10
  from .target import Target
@@ -23,6 +24,7 @@ class SubscriptionItem(object):
23
24
  self.suspended = False
24
25
  self.target = target
25
26
  self.user_id = user_id
27
+ self.schedule = None
26
28
 
27
29
  def __repr__(self) -> str:
28
30
  if self.id is not None:
@@ -92,9 +94,14 @@ class SubscriptionItem(object):
92
94
 
93
95
  # Schedule element
94
96
  schedule_id = None
97
+ schedule = None
95
98
  if schedule_element is not None:
96
99
  schedule_id = schedule_element.get("id", None)
97
100
 
101
+ # If schedule id is not provided, then TOL with full schedule provided
102
+ if schedule_id is None:
103
+ schedule = ScheduleItem.from_element(element, ns)
104
+
98
105
  # Content element
99
106
  target = None
100
107
  send_if_view_empty = None
@@ -127,6 +134,7 @@ class SubscriptionItem(object):
127
134
  sub.page_size_option = page_size_option
128
135
  sub.send_if_view_empty = send_if_view_empty
129
136
  sub.suspended = suspended
137
+ sub.schedule = schedule
130
138
 
131
139
  return sub
132
140
 
@@ -19,6 +19,12 @@ class TableItem(object):
19
19
  self._columns = None
20
20
  self._data_quality_warnings = None
21
21
 
22
+ def __str__(self):
23
+ return f"<{self.__class__.__name__} {self._id} {self._name} >"
24
+
25
+ def __repr__(self):
26
+ return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
27
+
22
28
  @property
23
29
  def permissions(self):
24
30
  if self._permissions is None:
@@ -1,13 +1,18 @@
1
- class Credentials:
1
+ import abc
2
+
3
+
4
+ class Credentials(abc.ABC):
2
5
  def __init__(self, site_id=None, user_id_to_impersonate=None):
3
6
  self.site_id = site_id or ""
4
7
  self.user_id_to_impersonate = user_id_to_impersonate or None
5
8
 
6
9
  @property
10
+ @abc.abstractmethod
7
11
  def credentials(self):
8
12
  credentials = "Credentials can be username/password, Personal Access Token, or JWT"
9
13
  +"This method returns values to set as an attribute on the credentials element of the request"
10
14
 
15
+ @abc.abstractmethod
11
16
  def __repr__(self):
12
17
  return "All Credentials types must have a debug display that does not print secrets"
13
18
 
@@ -38,7 +43,11 @@ class TableauAuth(Credentials):
38
43
  return {"name": self.username, "password": self.password}
39
44
 
40
45
  def __repr__(self):
41
- return "<Credentials username={} password={}>".format(self.username, "<redacted>")
46
+ if self.user_id_to_impersonate:
47
+ uid = f", user_id_to_impersonate=f{self.user_id_to_impersonate}"
48
+ else:
49
+ uid = ""
50
+ return f"<Credentials username={self.username} password=redacted (site={self.site_id}{uid})>"
42
51
 
43
52
  @property
44
53
  def site(self):
@@ -51,11 +60,12 @@ class TableauAuth(Credentials):
51
60
  self.site_id = value
52
61
 
53
62
 
63
+ # A Tableau-generated Personal Access Token
54
64
  class PersonalAccessTokenAuth(Credentials):
55
- def __init__(self, token_name, personal_access_token, site_id=None):
65
+ def __init__(self, token_name, personal_access_token, site_id=None, user_id_to_impersonate=None):
56
66
  if personal_access_token is None or token_name is None:
57
67
  raise TabError("Must provide a token and token name when using PAT authentication")
58
- super().__init__(site_id=site_id)
68
+ super().__init__(site_id=site_id, user_id_to_impersonate=user_id_to_impersonate)
59
69
  self.token_name = token_name
60
70
  self.personal_access_token = personal_access_token
61
71
 
@@ -67,6 +77,31 @@ class PersonalAccessTokenAuth(Credentials):
67
77
  }
68
78
 
69
79
  def __repr__(self):
70
- return "<PersonalAccessToken name={} token={}>(site={})".format(
71
- self.token_name, self.personal_access_token[:2] + "...", self.site_id
80
+ if self.user_id_to_impersonate:
81
+ uid = f", user_id_to_impersonate=f{self.user_id_to_impersonate}"
82
+ else:
83
+ uid = ""
84
+ return (
85
+ f"<PersonalAccessToken name={self.token_name} token={self.personal_access_token[:2]}..."
86
+ f"(site={self.site_id}{uid} >"
72
87
  )
88
+
89
+
90
+ # A standard JWT generated specifically for Tableau
91
+ class JWTAuth(Credentials):
92
+ def __init__(self, jwt: str, site_id=None, user_id_to_impersonate=None):
93
+ if jwt is None:
94
+ raise TabError("Must provide a JWT token when using JWT authentication")
95
+ super().__init__(site_id, user_id_to_impersonate)
96
+ self.jwt = jwt
97
+
98
+ @property
99
+ def credentials(self):
100
+ return {"jwt": self.jwt}
101
+
102
+ def __repr__(self):
103
+ if self.user_id_to_impersonate:
104
+ uid = f", user_id_to_impersonate=f{self.user_id_to_impersonate}"
105
+ else:
106
+ uid = ""
107
+ return f"<{self.__class__.__qualname__} jwt={self.jwt[:5]}... (site={self.site_id}{uid})>"
@@ -5,23 +5,25 @@ from .flow_item import FlowItem
5
5
  from .project_item import ProjectItem
6
6
  from .view_item import ViewItem
7
7
  from .workbook_item import WorkbookItem
8
+ from .metric_item import MetricItem
8
9
 
9
10
 
10
11
  class Resource:
11
12
  Database = "database"
12
13
  Datarole = "datarole"
14
+ Table = "table"
13
15
  Datasource = "datasource"
14
16
  Flow = "flow"
15
17
  Lens = "lens"
16
18
  Metric = "metric"
17
19
  Project = "project"
18
- Table = "table"
19
20
  View = "view"
20
21
  Workbook = "workbook"
21
22
 
22
23
 
23
24
  # resource types that have permissions, can be renamed, etc
24
- TableauItem = Union[DatasourceItem, FlowItem, ProjectItem, ViewItem, WorkbookItem]
25
+ # todo: refactoring: should actually define TableauItem as an interface and let all these implement it
26
+ TableauItem = Union[DatasourceItem, FlowItem, MetricItem, ProjectItem, ViewItem, WorkbookItem]
25
27
 
26
28
 
27
29
  def plural_type(content_type: Resource) -> str:
@@ -45,6 +45,7 @@ class UserItem(object):
45
45
  class Auth:
46
46
  OpenID = "OpenID"
47
47
  SAML = "SAML"
48
+ TableauIDWithMFA = "TableauIDWithMFA"
48
49
  ServerDefault = "ServerDefault"
49
50
 
50
51
  def __init__(
@@ -66,10 +67,13 @@ class UserItem(object):
66
67
 
67
68
  return None
68
69
 
69
- def __repr__(self) -> str:
70
+ def __str__(self) -> str:
70
71
  str_site_role = self.site_role or "None"
71
72
  return "<User {} name={} role={}>".format(self.id, self.name, str_site_role)
72
73
 
74
+ def __repr__(self):
75
+ return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
76
+
73
77
  @property
74
78
  def auth_setting(self) -> Optional[str]:
75
79
  return self._auth_setting
@@ -1,5 +1,6 @@
1
1
  import copy
2
2
  from datetime import datetime
3
+ from requests import Response
3
4
  from typing import Callable, Iterator, List, Optional, Set
4
5
 
5
6
  from defusedxml.ElementTree import fromstring
@@ -31,11 +32,14 @@ class ViewItem(object):
31
32
  self._permissions: Optional[Callable[[], List[PermissionsRule]]] = None
32
33
  self.tags: Set[str] = set()
33
34
 
34
- def __repr__(self):
35
+ def __str__(self):
35
36
  return "<ViewItem {0} '{1}' contentUrl='{2}' project={3}>".format(
36
37
  self._id, self.name, self.content_url, self.project_id
37
38
  )
38
39
 
40
+ def __repr__(self):
41
+ return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
42
+
39
43
  def _set_preview_image(self, preview_image):
40
44
  self._preview_image = preview_image
41
45
 
@@ -140,7 +144,7 @@ class ViewItem(object):
140
144
  self._permissions = permissions
141
145
 
142
146
  @classmethod
143
- def from_response(cls, resp, ns, workbook_id="") -> List["ViewItem"]:
147
+ def from_response(cls, resp: "Response", ns, workbook_id="") -> List["ViewItem"]:
144
148
  return cls.from_xml_element(fromstring(resp), ns, workbook_id)
145
149
 
146
150
  @classmethod
@@ -148,39 +152,38 @@ class ViewItem(object):
148
152
  all_view_items = list()
149
153
  all_view_xml = parsed_response.findall(".//t:view", namespaces=ns)
150
154
  for view_xml in all_view_xml:
151
- view_item = cls()
152
- usage_elem = view_xml.find(".//t:usage", namespaces=ns)
153
- workbook_elem = view_xml.find(".//t:workbook", namespaces=ns)
154
- owner_elem = view_xml.find(".//t:owner", namespaces=ns)
155
- project_elem = view_xml.find(".//t:project", namespaces=ns)
156
- tags_elem = view_xml.find(".//t:tags", namespaces=ns)
157
- view_item._created_at = parse_datetime(view_xml.get("createdAt", None))
158
- view_item._updated_at = parse_datetime(view_xml.get("updatedAt", None))
159
- view_item._id = view_xml.get("id", None)
160
- view_item._name = view_xml.get("name", None)
161
- view_item._content_url = view_xml.get("contentUrl", None)
162
- view_item._sheet_type = view_xml.get("sheetType", None)
163
-
164
- if usage_elem is not None:
165
- total_view = usage_elem.get("totalViewCount", None)
166
- if total_view:
167
- view_item._total_views = int(total_view)
168
-
169
- if owner_elem is not None:
170
- view_item._owner_id = owner_elem.get("id", None)
171
-
172
- if project_elem is not None:
173
- view_item._project_id = project_elem.get("id", None)
174
-
175
- if workbook_id:
176
- view_item._workbook_id = workbook_id
177
- elif workbook_elem is not None:
178
- view_item._workbook_id = workbook_elem.get("id", None)
179
-
180
- if tags_elem is not None:
181
- tags = TagItem.from_xml_element(tags_elem, ns)
182
- view_item.tags = tags
183
- view_item._initial_tags = copy.copy(tags)
184
-
155
+ view_item = cls.from_xml(view_xml, ns, workbook_id)
185
156
  all_view_items.append(view_item)
186
157
  return all_view_items
158
+
159
+ @classmethod
160
+ def from_xml(cls, view_xml, ns, workbook_id="") -> "ViewItem":
161
+ view_item = cls()
162
+ usage_elem = view_xml.find(".//t:usage", namespaces=ns)
163
+ workbook_elem = view_xml.find(".//t:workbook", namespaces=ns)
164
+ owner_elem = view_xml.find(".//t:owner", namespaces=ns)
165
+ project_elem = view_xml.find(".//t:project", namespaces=ns)
166
+ tags_elem = view_xml.find(".//t:tags", namespaces=ns)
167
+ view_item._created_at = parse_datetime(view_xml.get("createdAt", None))
168
+ view_item._updated_at = parse_datetime(view_xml.get("updatedAt", None))
169
+ view_item._id = view_xml.get("id", None)
170
+ view_item._name = view_xml.get("name", None)
171
+ view_item._content_url = view_xml.get("contentUrl", None)
172
+ view_item._sheet_type = view_xml.get("sheetType", None)
173
+ if usage_elem is not None:
174
+ total_view = usage_elem.get("totalViewCount", None)
175
+ if total_view:
176
+ view_item._total_views = int(total_view)
177
+ if owner_elem is not None:
178
+ view_item._owner_id = owner_elem.get("id", None)
179
+ if project_elem is not None:
180
+ view_item._project_id = project_elem.get("id", None)
181
+ if workbook_id:
182
+ view_item._workbook_id = workbook_id
183
+ elif workbook_elem is not None:
184
+ view_item._workbook_id = workbook_elem.get("id", None)
185
+ if tags_elem is not None:
186
+ tags = TagItem.from_xml_element(tags_elem, ns)
187
+ view_item.tags = tags
188
+ view_item._initial_tags = copy.copy(tags)
189
+ return view_item