tableauserverclient 0.33__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.
- tableauserverclient/__init__.py +28 -22
- tableauserverclient/_version.py +3 -3
- tableauserverclient/config.py +5 -3
- tableauserverclient/models/column_item.py +1 -1
- tableauserverclient/models/connection_credentials.py +1 -1
- tableauserverclient/models/connection_item.py +6 -6
- tableauserverclient/models/custom_view_item.py +29 -6
- tableauserverclient/models/data_acceleration_report_item.py +2 -2
- tableauserverclient/models/data_alert_item.py +5 -5
- tableauserverclient/models/data_freshness_policy_item.py +6 -6
- tableauserverclient/models/database_item.py +3 -3
- tableauserverclient/models/datasource_item.py +10 -10
- tableauserverclient/models/dqw_item.py +1 -1
- tableauserverclient/models/favorites_item.py +5 -6
- tableauserverclient/models/fileupload_item.py +1 -1
- tableauserverclient/models/flow_item.py +6 -6
- tableauserverclient/models/flow_run_item.py +3 -3
- tableauserverclient/models/group_item.py +4 -4
- tableauserverclient/models/groupset_item.py +4 -4
- tableauserverclient/models/interval_item.py +9 -9
- tableauserverclient/models/job_item.py +8 -8
- tableauserverclient/models/linked_tasks_item.py +5 -5
- tableauserverclient/models/metric_item.py +5 -5
- tableauserverclient/models/pagination_item.py +1 -1
- tableauserverclient/models/permissions_item.py +12 -10
- tableauserverclient/models/project_item.py +35 -19
- tableauserverclient/models/property_decorators.py +12 -11
- tableauserverclient/models/reference_item.py +2 -2
- tableauserverclient/models/revision_item.py +3 -3
- tableauserverclient/models/schedule_item.py +2 -2
- tableauserverclient/models/server_info_item.py +26 -6
- tableauserverclient/models/site_item.py +69 -3
- tableauserverclient/models/subscription_item.py +3 -3
- tableauserverclient/models/table_item.py +1 -1
- tableauserverclient/models/tableau_auth.py +115 -5
- tableauserverclient/models/tableau_types.py +2 -2
- tableauserverclient/models/tag_item.py +3 -4
- tableauserverclient/models/task_item.py +4 -4
- tableauserverclient/models/user_item.py +47 -17
- tableauserverclient/models/view_item.py +11 -10
- tableauserverclient/models/virtual_connection_item.py +6 -5
- tableauserverclient/models/webhook_item.py +6 -6
- tableauserverclient/models/workbook_item.py +90 -12
- tableauserverclient/namespace.py +1 -1
- tableauserverclient/server/__init__.py +2 -1
- tableauserverclient/server/endpoint/auth_endpoint.py +65 -8
- tableauserverclient/server/endpoint/custom_views_endpoint.py +62 -18
- tableauserverclient/server/endpoint/data_acceleration_report_endpoint.py +2 -2
- tableauserverclient/server/endpoint/data_alert_endpoint.py +14 -14
- tableauserverclient/server/endpoint/databases_endpoint.py +13 -12
- tableauserverclient/server/endpoint/datasources_endpoint.py +49 -54
- tableauserverclient/server/endpoint/default_permissions_endpoint.py +19 -18
- tableauserverclient/server/endpoint/dqw_endpoint.py +9 -9
- tableauserverclient/server/endpoint/endpoint.py +19 -21
- tableauserverclient/server/endpoint/exceptions.py +23 -7
- tableauserverclient/server/endpoint/favorites_endpoint.py +31 -31
- tableauserverclient/server/endpoint/fileuploads_endpoint.py +9 -11
- tableauserverclient/server/endpoint/flow_runs_endpoint.py +15 -13
- tableauserverclient/server/endpoint/flow_task_endpoint.py +2 -2
- tableauserverclient/server/endpoint/flows_endpoint.py +30 -29
- tableauserverclient/server/endpoint/groups_endpoint.py +18 -17
- tableauserverclient/server/endpoint/groupsets_endpoint.py +2 -2
- tableauserverclient/server/endpoint/jobs_endpoint.py +7 -7
- tableauserverclient/server/endpoint/linked_tasks_endpoint.py +2 -2
- tableauserverclient/server/endpoint/metadata_endpoint.py +2 -2
- tableauserverclient/server/endpoint/metrics_endpoint.py +10 -10
- tableauserverclient/server/endpoint/permissions_endpoint.py +13 -15
- tableauserverclient/server/endpoint/projects_endpoint.py +81 -30
- tableauserverclient/server/endpoint/resource_tagger.py +14 -13
- tableauserverclient/server/endpoint/schedules_endpoint.py +17 -18
- tableauserverclient/server/endpoint/server_info_endpoint.py +40 -5
- tableauserverclient/server/endpoint/sites_endpoint.py +282 -17
- tableauserverclient/server/endpoint/subscriptions_endpoint.py +10 -10
- tableauserverclient/server/endpoint/tables_endpoint.py +15 -14
- tableauserverclient/server/endpoint/tasks_endpoint.py +8 -8
- tableauserverclient/server/endpoint/users_endpoint.py +366 -19
- tableauserverclient/server/endpoint/views_endpoint.py +19 -18
- tableauserverclient/server/endpoint/virtual_connections_endpoint.py +6 -5
- tableauserverclient/server/endpoint/webhooks_endpoint.py +11 -11
- tableauserverclient/server/endpoint/workbooks_endpoint.py +647 -61
- tableauserverclient/server/filter.py +2 -2
- tableauserverclient/server/pager.py +5 -6
- tableauserverclient/server/query.py +68 -19
- tableauserverclient/server/request_factory.py +37 -36
- tableauserverclient/server/request_options.py +123 -145
- tableauserverclient/server/server.py +65 -9
- tableauserverclient/server/sort.py +2 -2
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/METADATA +6 -6
- tableauserverclient-0.34.dist-info/RECORD +106 -0
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/WHEEL +1 -1
- tableauserverclient-0.33.dist-info/RECORD +0 -106
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/LICENSE +0 -0
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/LICENSE.versioneer +0 -0
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Optional
|
|
2
2
|
import xml.etree.ElementTree as ET
|
|
3
3
|
|
|
4
4
|
from defusedxml.ElementTree import fromstring
|
|
@@ -13,7 +13,7 @@ class GroupSetItem:
|
|
|
13
13
|
def __init__(self, name: Optional[str] = None) -> None:
|
|
14
14
|
self.name = name
|
|
15
15
|
self.id: Optional[str] = None
|
|
16
|
-
self.groups:
|
|
16
|
+
self.groups: list["GroupItem"] = []
|
|
17
17
|
self.group_count: int = 0
|
|
18
18
|
|
|
19
19
|
def __str__(self) -> str:
|
|
@@ -25,13 +25,13 @@ class GroupSetItem:
|
|
|
25
25
|
return self.__str__()
|
|
26
26
|
|
|
27
27
|
@classmethod
|
|
28
|
-
def from_response(cls, response: bytes, ns:
|
|
28
|
+
def from_response(cls, response: bytes, ns: dict[str, str]) -> list["GroupSetItem"]:
|
|
29
29
|
parsed_response = fromstring(response)
|
|
30
30
|
all_groupset_xml = parsed_response.findall(".//t:groupSet", namespaces=ns)
|
|
31
31
|
return [cls.from_xml(xml, ns) for xml in all_groupset_xml]
|
|
32
32
|
|
|
33
33
|
@classmethod
|
|
34
|
-
def from_xml(cls, groupset_xml: ET.Element, ns:
|
|
34
|
+
def from_xml(cls, groupset_xml: ET.Element, ns: dict[str, str]) -> "GroupSetItem":
|
|
35
35
|
def get_group(group_xml: ET.Element) -> GroupItem:
|
|
36
36
|
group_item = GroupItem()
|
|
37
37
|
group_item._id = group_xml.get("id")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from .property_decorators import property_is_valid_time, property_not_nullable
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
class IntervalItem
|
|
4
|
+
class IntervalItem:
|
|
5
5
|
class Frequency:
|
|
6
6
|
Hourly = "Hourly"
|
|
7
7
|
Daily = "Daily"
|
|
@@ -25,7 +25,7 @@ class IntervalItem(object):
|
|
|
25
25
|
LastDay = "LastDay"
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
class HourlyInterval
|
|
28
|
+
class HourlyInterval:
|
|
29
29
|
def __init__(self, start_time, end_time, interval_value):
|
|
30
30
|
self.start_time = start_time
|
|
31
31
|
self.end_time = end_time
|
|
@@ -73,12 +73,12 @@ class HourlyInterval(object):
|
|
|
73
73
|
for interval in intervals:
|
|
74
74
|
# if an hourly interval is a string, then it is a weekDay interval
|
|
75
75
|
if isinstance(interval, str) and not interval.isnumeric() and not hasattr(IntervalItem.Day, interval):
|
|
76
|
-
error = "Invalid weekDay interval {}"
|
|
76
|
+
error = f"Invalid weekDay interval {interval}"
|
|
77
77
|
raise ValueError(error)
|
|
78
78
|
|
|
79
79
|
# if an hourly interval is a number, it is an hours or minutes interval
|
|
80
80
|
if isinstance(interval, (int, float)) and float(interval) not in VALID_INTERVALS:
|
|
81
|
-
error = "Invalid interval {} not in {
|
|
81
|
+
error = f"Invalid interval {interval} not in {str(VALID_INTERVALS)}"
|
|
82
82
|
raise ValueError(error)
|
|
83
83
|
|
|
84
84
|
self._interval = intervals
|
|
@@ -108,7 +108,7 @@ class HourlyInterval(object):
|
|
|
108
108
|
return interval_type_pairs
|
|
109
109
|
|
|
110
110
|
|
|
111
|
-
class DailyInterval
|
|
111
|
+
class DailyInterval:
|
|
112
112
|
def __init__(self, start_time, *interval_values):
|
|
113
113
|
self.start_time = start_time
|
|
114
114
|
self.interval = interval_values
|
|
@@ -141,12 +141,12 @@ class DailyInterval(object):
|
|
|
141
141
|
for interval in intervals:
|
|
142
142
|
# if an hourly interval is a string, then it is a weekDay interval
|
|
143
143
|
if isinstance(interval, str) and not interval.isnumeric() and not hasattr(IntervalItem.Day, interval):
|
|
144
|
-
error = "Invalid weekDay interval {}"
|
|
144
|
+
error = f"Invalid weekDay interval {interval}"
|
|
145
145
|
raise ValueError(error)
|
|
146
146
|
|
|
147
147
|
# if an hourly interval is a number, it is an hours or minutes interval
|
|
148
148
|
if isinstance(interval, (int, float)) and float(interval) not in VALID_INTERVALS:
|
|
149
|
-
error = "Invalid interval {} not in {
|
|
149
|
+
error = f"Invalid interval {interval} not in {str(VALID_INTERVALS)}"
|
|
150
150
|
raise ValueError(error)
|
|
151
151
|
|
|
152
152
|
self._interval = intervals
|
|
@@ -176,7 +176,7 @@ class DailyInterval(object):
|
|
|
176
176
|
return interval_type_pairs
|
|
177
177
|
|
|
178
178
|
|
|
179
|
-
class WeeklyInterval
|
|
179
|
+
class WeeklyInterval:
|
|
180
180
|
def __init__(self, start_time, *interval_values):
|
|
181
181
|
self.start_time = start_time
|
|
182
182
|
self.interval = interval_values
|
|
@@ -213,7 +213,7 @@ class WeeklyInterval(object):
|
|
|
213
213
|
return [(IntervalItem.Occurrence.WeekDay, day) for day in self.interval]
|
|
214
214
|
|
|
215
215
|
|
|
216
|
-
class MonthlyInterval
|
|
216
|
+
class MonthlyInterval:
|
|
217
217
|
def __init__(self, start_time, interval_value):
|
|
218
218
|
self.start_time = start_time
|
|
219
219
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import datetime
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Optional
|
|
3
3
|
|
|
4
4
|
from defusedxml.ElementTree import fromstring
|
|
5
5
|
|
|
@@ -7,7 +7,7 @@ from tableauserverclient.datetime_helpers import parse_datetime
|
|
|
7
7
|
from tableauserverclient.models.flow_run_item import FlowRunItem
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class JobItem
|
|
10
|
+
class JobItem:
|
|
11
11
|
class FinishCode:
|
|
12
12
|
"""
|
|
13
13
|
Status codes as documented on
|
|
@@ -27,7 +27,7 @@ class JobItem(object):
|
|
|
27
27
|
started_at: Optional[datetime.datetime] = None,
|
|
28
28
|
completed_at: Optional[datetime.datetime] = None,
|
|
29
29
|
finish_code: int = 0,
|
|
30
|
-
notes: Optional[
|
|
30
|
+
notes: Optional[list[str]] = None,
|
|
31
31
|
mode: Optional[str] = None,
|
|
32
32
|
workbook_id: Optional[str] = None,
|
|
33
33
|
datasource_id: Optional[str] = None,
|
|
@@ -43,7 +43,7 @@ class JobItem(object):
|
|
|
43
43
|
self._started_at = started_at
|
|
44
44
|
self._completed_at = completed_at
|
|
45
45
|
self._finish_code = finish_code
|
|
46
|
-
self._notes:
|
|
46
|
+
self._notes: list[str] = notes or []
|
|
47
47
|
self._mode = mode
|
|
48
48
|
self._workbook_id = workbook_id
|
|
49
49
|
self._datasource_id = datasource_id
|
|
@@ -81,7 +81,7 @@ class JobItem(object):
|
|
|
81
81
|
return self._finish_code
|
|
82
82
|
|
|
83
83
|
@property
|
|
84
|
-
def notes(self) ->
|
|
84
|
+
def notes(self) -> list[str]:
|
|
85
85
|
return self._notes
|
|
86
86
|
|
|
87
87
|
@property
|
|
@@ -139,7 +139,7 @@ class JobItem(object):
|
|
|
139
139
|
return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
|
|
140
140
|
|
|
141
141
|
@classmethod
|
|
142
|
-
def from_response(cls, xml, ns) ->
|
|
142
|
+
def from_response(cls, xml, ns) -> list["JobItem"]:
|
|
143
143
|
parsed_response = fromstring(xml)
|
|
144
144
|
all_tasks_xml = parsed_response.findall(".//t:job", namespaces=ns)
|
|
145
145
|
|
|
@@ -191,7 +191,7 @@ class JobItem(object):
|
|
|
191
191
|
)
|
|
192
192
|
|
|
193
193
|
|
|
194
|
-
class BackgroundJobItem
|
|
194
|
+
class BackgroundJobItem:
|
|
195
195
|
class Status:
|
|
196
196
|
Pending: str = "Pending"
|
|
197
197
|
InProgress: str = "InProgress"
|
|
@@ -270,7 +270,7 @@ class BackgroundJobItem(object):
|
|
|
270
270
|
return self._priority
|
|
271
271
|
|
|
272
272
|
@classmethod
|
|
273
|
-
def from_response(cls, xml, ns) ->
|
|
273
|
+
def from_response(cls, xml, ns) -> list["BackgroundJobItem"]:
|
|
274
274
|
parsed_response = fromstring(xml)
|
|
275
275
|
all_tasks_xml = parsed_response.findall(".//t:backgroundJob", namespaces=ns)
|
|
276
276
|
return [cls._parse_element(x, ns) for x in all_tasks_xml]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import datetime as dt
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Optional
|
|
3
3
|
|
|
4
4
|
from defusedxml.ElementTree import fromstring
|
|
5
5
|
|
|
@@ -14,7 +14,7 @@ class LinkedTaskItem:
|
|
|
14
14
|
self.schedule: Optional[ScheduleItem] = None
|
|
15
15
|
|
|
16
16
|
@classmethod
|
|
17
|
-
def from_response(cls, resp: bytes, namespace) ->
|
|
17
|
+
def from_response(cls, resp: bytes, namespace) -> list["LinkedTaskItem"]:
|
|
18
18
|
parsed_response = fromstring(resp)
|
|
19
19
|
return [
|
|
20
20
|
cls._parse_element(x, namespace)
|
|
@@ -35,10 +35,10 @@ class LinkedTaskStepItem:
|
|
|
35
35
|
self.id: Optional[str] = None
|
|
36
36
|
self.step_number: Optional[int] = None
|
|
37
37
|
self.stop_downstream_on_failure: Optional[bool] = None
|
|
38
|
-
self.task_details:
|
|
38
|
+
self.task_details: list[LinkedTaskFlowRunItem] = []
|
|
39
39
|
|
|
40
40
|
@classmethod
|
|
41
|
-
def from_task_xml(cls, xml, namespace) ->
|
|
41
|
+
def from_task_xml(cls, xml, namespace) -> list["LinkedTaskStepItem"]:
|
|
42
42
|
return [cls._parse_element(x, namespace) for x in xml.findall(".//t:linkedTaskSteps[@id]", namespace)]
|
|
43
43
|
|
|
44
44
|
@classmethod
|
|
@@ -61,7 +61,7 @@ class LinkedTaskFlowRunItem:
|
|
|
61
61
|
self.flow_name: Optional[str] = None
|
|
62
62
|
|
|
63
63
|
@classmethod
|
|
64
|
-
def _parse_element(cls, xml, namespace) ->
|
|
64
|
+
def _parse_element(cls, xml, namespace) -> list["LinkedTaskFlowRunItem"]:
|
|
65
65
|
all_tasks = []
|
|
66
66
|
for flow_run in xml.findall(".//t:flowRun[@id]", namespace):
|
|
67
67
|
task = cls()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import xml.etree.ElementTree as ET
|
|
2
2
|
from datetime import datetime
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Optional
|
|
4
4
|
|
|
5
5
|
from tableauserverclient.datetime_helpers import parse_datetime
|
|
6
6
|
from .property_decorators import property_is_boolean, property_is_datetime
|
|
@@ -8,7 +8,7 @@ from .tag_item import TagItem
|
|
|
8
8
|
from .permissions_item import Permission
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class MetricItem
|
|
11
|
+
class MetricItem:
|
|
12
12
|
def __init__(self, name: Optional[str] = None):
|
|
13
13
|
self._id: Optional[str] = None
|
|
14
14
|
self._name: Optional[str] = name
|
|
@@ -21,8 +21,8 @@ class MetricItem(object):
|
|
|
21
21
|
self._project_name: Optional[str] = None
|
|
22
22
|
self._owner_id: Optional[str] = None
|
|
23
23
|
self._view_id: Optional[str] = None
|
|
24
|
-
self._initial_tags:
|
|
25
|
-
self.tags:
|
|
24
|
+
self._initial_tags: set[str] = set()
|
|
25
|
+
self.tags: set[str] = set()
|
|
26
26
|
self._permissions: Optional[Permission] = None
|
|
27
27
|
|
|
28
28
|
@property
|
|
@@ -126,7 +126,7 @@ class MetricItem(object):
|
|
|
126
126
|
cls,
|
|
127
127
|
resp: bytes,
|
|
128
128
|
ns,
|
|
129
|
-
) ->
|
|
129
|
+
) -> list["MetricItem"]:
|
|
130
130
|
all_metric_items = list()
|
|
131
131
|
parsed_response = ET.fromstring(resp)
|
|
132
132
|
all_metric_xml = parsed_response.findall(".//t:metric", namespaces=ns)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import xml.etree.ElementTree as ET
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Optional
|
|
3
3
|
|
|
4
4
|
from defusedxml.ElementTree import fromstring
|
|
5
5
|
|
|
@@ -36,23 +36,25 @@ class Permission:
|
|
|
36
36
|
ShareView = "ShareView"
|
|
37
37
|
ViewComments = "ViewComments"
|
|
38
38
|
ViewUnderlyingData = "ViewUnderlyingData"
|
|
39
|
+
VizqlDataApiAccess = "VizqlDataApiAccess"
|
|
39
40
|
WebAuthoring = "WebAuthoring"
|
|
40
41
|
Write = "Write"
|
|
41
42
|
RunExplainData = "RunExplainData"
|
|
42
43
|
CreateRefreshMetrics = "CreateRefreshMetrics"
|
|
43
44
|
SaveAs = "SaveAs"
|
|
45
|
+
PulseMetricDefine = "PulseMetricDefine"
|
|
44
46
|
|
|
45
47
|
def __repr__(self):
|
|
46
48
|
return "<Enum Capability: AddComment | ChangeHierarchy | ChangePermission ... (17 more) >"
|
|
47
49
|
|
|
48
50
|
|
|
49
51
|
class PermissionsRule:
|
|
50
|
-
def __init__(self, grantee: ResourceReference, capabilities:
|
|
52
|
+
def __init__(self, grantee: ResourceReference, capabilities: dict[str, str]) -> None:
|
|
51
53
|
self.grantee = grantee
|
|
52
54
|
self.capabilities = capabilities
|
|
53
55
|
|
|
54
56
|
def __repr__(self):
|
|
55
|
-
return "<PermissionsRule grantee={}, capabilities={}>"
|
|
57
|
+
return f"<PermissionsRule grantee={self.grantee}, capabilities={self.capabilities}>"
|
|
56
58
|
|
|
57
59
|
def __eq__(self, other: object) -> bool:
|
|
58
60
|
if not hasattr(other, "grantee") or not hasattr(other, "capabilities"):
|
|
@@ -66,7 +68,7 @@ class PermissionsRule:
|
|
|
66
68
|
if self.capabilities == other.capabilities:
|
|
67
69
|
return self
|
|
68
70
|
|
|
69
|
-
capabilities =
|
|
71
|
+
capabilities = {*self.capabilities.keys(), *other.capabilities.keys()}
|
|
70
72
|
new_capabilities = {}
|
|
71
73
|
for capability in capabilities:
|
|
72
74
|
if (self.capabilities.get(capability), other.capabilities.get(capability)) == (
|
|
@@ -86,7 +88,7 @@ class PermissionsRule:
|
|
|
86
88
|
if self.capabilities == other.capabilities:
|
|
87
89
|
return self
|
|
88
90
|
|
|
89
|
-
capabilities =
|
|
91
|
+
capabilities = {*self.capabilities.keys(), *other.capabilities.keys()}
|
|
90
92
|
new_capabilities = {}
|
|
91
93
|
for capability in capabilities:
|
|
92
94
|
if Permission.Mode.Allow in (self.capabilities.get(capability), other.capabilities.get(capability)):
|
|
@@ -100,14 +102,14 @@ class PermissionsRule:
|
|
|
100
102
|
return PermissionsRule(self.grantee, new_capabilities)
|
|
101
103
|
|
|
102
104
|
@classmethod
|
|
103
|
-
def from_response(cls, resp, ns=None) ->
|
|
105
|
+
def from_response(cls, resp, ns=None) -> list["PermissionsRule"]:
|
|
104
106
|
parsed_response = fromstring(resp)
|
|
105
107
|
|
|
106
108
|
rules = []
|
|
107
109
|
permissions_rules_list_xml = parsed_response.findall(".//t:granteeCapabilities", namespaces=ns)
|
|
108
110
|
|
|
109
111
|
for grantee_capability_xml in permissions_rules_list_xml:
|
|
110
|
-
capability_dict:
|
|
112
|
+
capability_dict: dict[str, str] = {}
|
|
111
113
|
|
|
112
114
|
grantee = PermissionsRule._parse_grantee_element(grantee_capability_xml, ns)
|
|
113
115
|
|
|
@@ -116,7 +118,7 @@ class PermissionsRule:
|
|
|
116
118
|
mode = capability_xml.get("mode")
|
|
117
119
|
|
|
118
120
|
if name is None or mode is None:
|
|
119
|
-
logger.error("Capability was not valid: {}"
|
|
121
|
+
logger.error(f"Capability was not valid: {capability_xml}")
|
|
120
122
|
raise UnpopulatedPropertyError()
|
|
121
123
|
else:
|
|
122
124
|
capability_dict[name] = mode
|
|
@@ -127,7 +129,7 @@ class PermissionsRule:
|
|
|
127
129
|
return rules
|
|
128
130
|
|
|
129
131
|
@staticmethod
|
|
130
|
-
def _parse_grantee_element(grantee_capability_xml: ET.Element, ns: Optional[
|
|
132
|
+
def _parse_grantee_element(grantee_capability_xml: ET.Element, ns: Optional[dict[str, str]]) -> ResourceReference:
|
|
131
133
|
"""Use Xpath magic and some string splitting to get the right object type from the xml"""
|
|
132
134
|
|
|
133
135
|
# Get the first element in the tree with an 'id' attribute
|
|
@@ -146,6 +148,6 @@ class PermissionsRule:
|
|
|
146
148
|
elif grantee_type == "groupSet":
|
|
147
149
|
grantee = GroupSetItem.as_reference(grantee_id)
|
|
148
150
|
else:
|
|
149
|
-
raise UnknownGranteeTypeError("No support for grantee type of {}"
|
|
151
|
+
raise UnknownGranteeTypeError(f"No support for grantee type of {grantee_type}")
|
|
150
152
|
|
|
151
153
|
return grantee
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import xml.etree.ElementTree as ET
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Optional
|
|
4
4
|
|
|
5
5
|
from defusedxml.ElementTree import fromstring
|
|
6
6
|
|
|
@@ -8,14 +8,16 @@ from tableauserverclient.models.exceptions import UnpopulatedPropertyError
|
|
|
8
8
|
from tableauserverclient.models.property_decorators import property_is_enum, property_not_empty
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class ProjectItem
|
|
11
|
+
class ProjectItem:
|
|
12
|
+
ERROR_MSG = "Project item must be populated with permissions first."
|
|
13
|
+
|
|
12
14
|
class ContentPermissions:
|
|
13
15
|
LockedToProject: str = "LockedToProject"
|
|
14
16
|
ManagedByOwner: str = "ManagedByOwner"
|
|
15
17
|
LockedToProjectWithoutNested: str = "LockedToProjectWithoutNested"
|
|
16
18
|
|
|
17
19
|
def __repr__(self):
|
|
18
|
-
return "<Project {
|
|
20
|
+
return "<Project {} {} parent={} permissions={}>".format(
|
|
19
21
|
self._id, self.name, self.parent_id or "None (Top level)", self.content_permissions or "Not Set"
|
|
20
22
|
)
|
|
21
23
|
|
|
@@ -43,6 +45,9 @@ class ProjectItem(object):
|
|
|
43
45
|
self._default_lens_permissions = None
|
|
44
46
|
self._default_datarole_permissions = None
|
|
45
47
|
self._default_metric_permissions = None
|
|
48
|
+
self._default_virtualconnection_permissions = None
|
|
49
|
+
self._default_database_permissions = None
|
|
50
|
+
self._default_table_permissions = None
|
|
46
51
|
|
|
47
52
|
@property
|
|
48
53
|
def content_permissions(self):
|
|
@@ -56,52 +61,63 @@ class ProjectItem(object):
|
|
|
56
61
|
@property
|
|
57
62
|
def permissions(self):
|
|
58
63
|
if self._permissions is None:
|
|
59
|
-
|
|
60
|
-
raise UnpopulatedPropertyError(error)
|
|
64
|
+
raise UnpopulatedPropertyError(self.ERROR_MSG)
|
|
61
65
|
return self._permissions()
|
|
62
66
|
|
|
63
67
|
@property
|
|
64
68
|
def default_datasource_permissions(self):
|
|
65
69
|
if self._default_datasource_permissions is None:
|
|
66
|
-
|
|
67
|
-
raise UnpopulatedPropertyError(error)
|
|
70
|
+
raise UnpopulatedPropertyError(self.ERROR_MSG)
|
|
68
71
|
return self._default_datasource_permissions()
|
|
69
72
|
|
|
70
73
|
@property
|
|
71
74
|
def default_workbook_permissions(self):
|
|
72
75
|
if self._default_workbook_permissions is None:
|
|
73
|
-
|
|
74
|
-
raise UnpopulatedPropertyError(error)
|
|
76
|
+
raise UnpopulatedPropertyError(self.ERROR_MSG)
|
|
75
77
|
return self._default_workbook_permissions()
|
|
76
78
|
|
|
77
79
|
@property
|
|
78
80
|
def default_flow_permissions(self):
|
|
79
81
|
if self._default_flow_permissions is None:
|
|
80
|
-
|
|
81
|
-
raise UnpopulatedPropertyError(error)
|
|
82
|
+
raise UnpopulatedPropertyError(self.ERROR_MSG)
|
|
82
83
|
return self._default_flow_permissions()
|
|
83
84
|
|
|
84
85
|
@property
|
|
85
86
|
def default_lens_permissions(self):
|
|
86
87
|
if self._default_lens_permissions is None:
|
|
87
|
-
|
|
88
|
-
raise UnpopulatedPropertyError(error)
|
|
88
|
+
raise UnpopulatedPropertyError(self.ERROR_MSG)
|
|
89
89
|
return self._default_lens_permissions()
|
|
90
90
|
|
|
91
91
|
@property
|
|
92
92
|
def default_datarole_permissions(self):
|
|
93
93
|
if self._default_datarole_permissions is None:
|
|
94
|
-
|
|
95
|
-
raise UnpopulatedPropertyError(error)
|
|
94
|
+
raise UnpopulatedPropertyError(self.ERROR_MSG)
|
|
96
95
|
return self._default_datarole_permissions()
|
|
97
96
|
|
|
98
97
|
@property
|
|
99
98
|
def default_metric_permissions(self):
|
|
100
99
|
if self._default_metric_permissions is None:
|
|
101
|
-
|
|
102
|
-
raise UnpopulatedPropertyError(error)
|
|
100
|
+
raise UnpopulatedPropertyError(self.ERROR_MSG)
|
|
103
101
|
return self._default_metric_permissions()
|
|
104
102
|
|
|
103
|
+
@property
|
|
104
|
+
def default_virtualconnection_permissions(self):
|
|
105
|
+
if self._default_virtualconnection_permissions is None:
|
|
106
|
+
raise UnpopulatedPropertyError(self.ERROR_MSG)
|
|
107
|
+
return self._default_virtualconnection_permissions()
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def default_database_permissions(self):
|
|
111
|
+
if self._default_database_permissions is None:
|
|
112
|
+
raise UnpopulatedPropertyError(self.ERROR_MSG)
|
|
113
|
+
return self._default_database_permissions()
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def default_table_permissions(self):
|
|
117
|
+
if self._default_table_permissions is None:
|
|
118
|
+
raise UnpopulatedPropertyError(self.ERROR_MSG)
|
|
119
|
+
return self._default_table_permissions()
|
|
120
|
+
|
|
105
121
|
@property
|
|
106
122
|
def id(self) -> Optional[str]:
|
|
107
123
|
return self._id
|
|
@@ -158,7 +174,7 @@ class ProjectItem(object):
|
|
|
158
174
|
self._permissions = permissions
|
|
159
175
|
|
|
160
176
|
def _set_default_permissions(self, permissions, content_type):
|
|
161
|
-
attr = "_default_{
|
|
177
|
+
attr = f"_default_{content_type}_permissions"
|
|
162
178
|
setattr(
|
|
163
179
|
self,
|
|
164
180
|
attr,
|
|
@@ -166,7 +182,7 @@ class ProjectItem(object):
|
|
|
166
182
|
)
|
|
167
183
|
|
|
168
184
|
@classmethod
|
|
169
|
-
def from_response(cls, resp, ns) ->
|
|
185
|
+
def from_response(cls, resp, ns) -> list["ProjectItem"]:
|
|
170
186
|
all_project_items = list()
|
|
171
187
|
parsed_response = fromstring(resp)
|
|
172
188
|
all_project_xml = parsed_response.findall(".//t:project", namespaces=ns)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import re
|
|
3
3
|
from functools import wraps
|
|
4
|
-
from typing import Any,
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
from collections.abc import Container
|
|
5
6
|
|
|
6
7
|
from tableauserverclient.datetime_helpers import parse_datetime
|
|
7
8
|
|
|
@@ -11,7 +12,7 @@ def property_is_enum(enum_type):
|
|
|
11
12
|
@wraps(func)
|
|
12
13
|
def wrapper(self, value):
|
|
13
14
|
if value is not None and not hasattr(enum_type, value):
|
|
14
|
-
error = "Invalid value: {
|
|
15
|
+
error = f"Invalid value: {value}. {func.__name__} must be of type {enum_type.__name__}."
|
|
15
16
|
raise ValueError(error)
|
|
16
17
|
return func(self, value)
|
|
17
18
|
|
|
@@ -24,7 +25,7 @@ def property_is_boolean(func):
|
|
|
24
25
|
@wraps(func)
|
|
25
26
|
def wrapper(self, value):
|
|
26
27
|
if not isinstance(value, bool):
|
|
27
|
-
error = "Boolean expected for {
|
|
28
|
+
error = f"Boolean expected for {func.__name__} flag."
|
|
28
29
|
raise ValueError(error)
|
|
29
30
|
return func(self, value)
|
|
30
31
|
|
|
@@ -35,7 +36,7 @@ def property_not_nullable(func):
|
|
|
35
36
|
@wraps(func)
|
|
36
37
|
def wrapper(self, value):
|
|
37
38
|
if value is None:
|
|
38
|
-
error = "{
|
|
39
|
+
error = f"{func.__name__} must be defined."
|
|
39
40
|
raise ValueError(error)
|
|
40
41
|
return func(self, value)
|
|
41
42
|
|
|
@@ -46,7 +47,7 @@ def property_not_empty(func):
|
|
|
46
47
|
@wraps(func)
|
|
47
48
|
def wrapper(self, value):
|
|
48
49
|
if not value:
|
|
49
|
-
error = "{
|
|
50
|
+
error = f"{func.__name__} must not be empty."
|
|
50
51
|
raise ValueError(error)
|
|
51
52
|
return func(self, value)
|
|
52
53
|
|
|
@@ -66,7 +67,7 @@ def property_is_valid_time(func):
|
|
|
66
67
|
return wrapper
|
|
67
68
|
|
|
68
69
|
|
|
69
|
-
def property_is_int(range:
|
|
70
|
+
def property_is_int(range: tuple[int, int], allowed: Optional[Container[Any]] = None):
|
|
70
71
|
"""Takes a range of ints and a list of exemptions to check against
|
|
71
72
|
when setting a property on a model. The range is a tuple of (min, max) and the
|
|
72
73
|
allowed list (empty by default) allows values outside that range.
|
|
@@ -81,7 +82,7 @@ def property_is_int(range: Tuple[int, int], allowed: Optional[Container[Any]] =
|
|
|
81
82
|
def property_type_decorator(func):
|
|
82
83
|
@wraps(func)
|
|
83
84
|
def wrapper(self, value):
|
|
84
|
-
error = "Invalid property defined: '{}'. Integer value expected."
|
|
85
|
+
error = f"Invalid property defined: '{value}'. Integer value expected."
|
|
85
86
|
|
|
86
87
|
if range is None:
|
|
87
88
|
if isinstance(value, int):
|
|
@@ -133,7 +134,7 @@ def property_is_datetime(func):
|
|
|
133
134
|
return func(self, value)
|
|
134
135
|
if not isinstance(value, str):
|
|
135
136
|
raise ValueError(
|
|
136
|
-
"Cannot convert {} into a datetime, cannot update {
|
|
137
|
+
f"Cannot convert {value.__class__.__name__} into a datetime, cannot update {func.__name__}"
|
|
137
138
|
)
|
|
138
139
|
|
|
139
140
|
dt = parse_datetime(value)
|
|
@@ -146,11 +147,11 @@ def property_is_data_acceleration_config(func):
|
|
|
146
147
|
@wraps(func)
|
|
147
148
|
def wrapper(self, value):
|
|
148
149
|
if not isinstance(value, dict):
|
|
149
|
-
raise ValueError("{} is not type 'dict', cannot update {
|
|
150
|
+
raise ValueError(f"{value.__class__.__name__} is not type 'dict', cannot update {func.__name__})")
|
|
150
151
|
if len(value) < 2 or not all(attr in value.keys() for attr in ("acceleration_enabled", "accelerate_now")):
|
|
151
|
-
error = "{} should have 2 keys "
|
|
152
|
+
error = f"{func.__name__} should have 2 keys "
|
|
152
153
|
error += "'acceleration_enabled' and 'accelerate_now'"
|
|
153
|
-
error += "instead you have {
|
|
154
|
+
error += f"instead you have {value.keys()}"
|
|
154
155
|
raise ValueError(error)
|
|
155
156
|
return func(self, value)
|
|
156
157
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
class ResourceReference
|
|
1
|
+
class ResourceReference:
|
|
2
2
|
def __init__(self, id_, tag_name):
|
|
3
3
|
self.id = id_
|
|
4
4
|
self.tag_name = tag_name
|
|
5
5
|
|
|
6
6
|
def __str__(self):
|
|
7
|
-
return "<ResourceReference id={} tag={}>"
|
|
7
|
+
return f"<ResourceReference id={self._id} tag={self._tag_name}>"
|
|
8
8
|
|
|
9
9
|
__repr__ = __str__
|
|
10
10
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Optional
|
|
3
3
|
|
|
4
4
|
from defusedxml.ElementTree import fromstring
|
|
5
5
|
|
|
6
6
|
from tableauserverclient.datetime_helpers import parse_datetime
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class RevisionItem
|
|
9
|
+
class RevisionItem:
|
|
10
10
|
def __init__(self):
|
|
11
11
|
self._resource_id: Optional[str] = None
|
|
12
12
|
self._resource_name: Optional[str] = None
|
|
@@ -56,7 +56,7 @@ class RevisionItem(object):
|
|
|
56
56
|
)
|
|
57
57
|
|
|
58
58
|
@classmethod
|
|
59
|
-
def from_response(cls, resp: bytes, ns, resource_item) ->
|
|
59
|
+
def from_response(cls, resp: bytes, ns, resource_item) -> list["RevisionItem"]:
|
|
60
60
|
all_revision_items = list()
|
|
61
61
|
parsed_response = fromstring(resp)
|
|
62
62
|
all_revision_xml = parsed_response.findall(".//t:revision", namespaces=ns)
|
|
@@ -19,7 +19,7 @@ from .property_decorators import (
|
|
|
19
19
|
Interval = Union[HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval]
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
class ScheduleItem
|
|
22
|
+
class ScheduleItem:
|
|
23
23
|
class Type:
|
|
24
24
|
Extract = "Extract"
|
|
25
25
|
Flow = "Flow"
|
|
@@ -336,7 +336,7 @@ class ScheduleItem(object):
|
|
|
336
336
|
all_task_xml = parsed_response.findall(".//t:task", namespaces=ns)
|
|
337
337
|
|
|
338
338
|
error = (
|
|
339
|
-
"Status {}: {
|
|
339
|
+
f"Status {response.status_code}: {response.reason}"
|
|
340
340
|
if response.status_code < 200 or response.status_code >= 300
|
|
341
341
|
else None
|
|
342
342
|
)
|