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
|
@@ -5,7 +5,8 @@ import logging
|
|
|
5
5
|
import os
|
|
6
6
|
from contextlib import closing
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import Optional, TYPE_CHECKING, Union
|
|
9
|
+
from collections.abc import Iterable
|
|
9
10
|
|
|
10
11
|
from tableauserverclient.helpers.headers import fix_filename
|
|
11
12
|
|
|
@@ -53,18 +54,18 @@ PathOrFileW = Union[FilePath, FileObjectW]
|
|
|
53
54
|
|
|
54
55
|
class Flows(QuerysetEndpoint[FlowItem], TaggingMixin[FlowItem]):
|
|
55
56
|
def __init__(self, parent_srv):
|
|
56
|
-
super(
|
|
57
|
+
super().__init__(parent_srv)
|
|
57
58
|
self._resource_tagger = _ResourceTagger(parent_srv)
|
|
58
59
|
self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
|
|
59
60
|
self._data_quality_warnings = _DataQualityWarningEndpoint(self.parent_srv, "flow")
|
|
60
61
|
|
|
61
62
|
@property
|
|
62
63
|
def baseurl(self) -> str:
|
|
63
|
-
return "{
|
|
64
|
+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/flows"
|
|
64
65
|
|
|
65
66
|
# Get all flows
|
|
66
67
|
@api(version="3.3")
|
|
67
|
-
def get(self, req_options: Optional["RequestOptions"] = None) ->
|
|
68
|
+
def get(self, req_options: Optional["RequestOptions"] = None) -> tuple[list[FlowItem], PaginationItem]:
|
|
68
69
|
logger.info("Querying all flows on site")
|
|
69
70
|
url = self.baseurl
|
|
70
71
|
server_response = self.get_request(url, req_options)
|
|
@@ -78,8 +79,8 @@ class Flows(QuerysetEndpoint[FlowItem], TaggingMixin[FlowItem]):
|
|
|
78
79
|
if not flow_id:
|
|
79
80
|
error = "Flow ID undefined."
|
|
80
81
|
raise ValueError(error)
|
|
81
|
-
logger.info("Querying single flow (ID: {
|
|
82
|
-
url = "{
|
|
82
|
+
logger.info(f"Querying single flow (ID: {flow_id})")
|
|
83
|
+
url = f"{self.baseurl}/{flow_id}"
|
|
83
84
|
server_response = self.get_request(url)
|
|
84
85
|
return FlowItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
85
86
|
|
|
@@ -94,10 +95,10 @@ class Flows(QuerysetEndpoint[FlowItem], TaggingMixin[FlowItem]):
|
|
|
94
95
|
return self._get_flow_connections(flow_item)
|
|
95
96
|
|
|
96
97
|
flow_item._set_connections(connections_fetcher)
|
|
97
|
-
logger.info("Populated connections for flow (ID: {
|
|
98
|
+
logger.info(f"Populated connections for flow (ID: {flow_item.id})")
|
|
98
99
|
|
|
99
|
-
def _get_flow_connections(self, flow_item, req_options: Optional["RequestOptions"] = None) ->
|
|
100
|
-
url = "{
|
|
100
|
+
def _get_flow_connections(self, flow_item, req_options: Optional["RequestOptions"] = None) -> list[ConnectionItem]:
|
|
101
|
+
url = f"{self.baseurl}/{flow_item.id}/connections"
|
|
101
102
|
server_response = self.get_request(url, req_options)
|
|
102
103
|
connections = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
103
104
|
return connections
|
|
@@ -108,9 +109,9 @@ class Flows(QuerysetEndpoint[FlowItem], TaggingMixin[FlowItem]):
|
|
|
108
109
|
if not flow_id:
|
|
109
110
|
error = "Flow ID undefined."
|
|
110
111
|
raise ValueError(error)
|
|
111
|
-
url = "{
|
|
112
|
+
url = f"{self.baseurl}/{flow_id}"
|
|
112
113
|
self.delete_request(url)
|
|
113
|
-
logger.info("Deleted single flow (ID: {
|
|
114
|
+
logger.info(f"Deleted single flow (ID: {flow_id})")
|
|
114
115
|
|
|
115
116
|
# Download 1 flow by id
|
|
116
117
|
@api(version="3.3")
|
|
@@ -118,7 +119,7 @@ class Flows(QuerysetEndpoint[FlowItem], TaggingMixin[FlowItem]):
|
|
|
118
119
|
if not flow_id:
|
|
119
120
|
error = "Flow ID undefined."
|
|
120
121
|
raise ValueError(error)
|
|
121
|
-
url = "{
|
|
122
|
+
url = f"{self.baseurl}/{flow_id}/content"
|
|
122
123
|
|
|
123
124
|
with closing(self.get_request(url, parameters={"stream": True})) as server_response:
|
|
124
125
|
m = Message()
|
|
@@ -137,7 +138,7 @@ class Flows(QuerysetEndpoint[FlowItem], TaggingMixin[FlowItem]):
|
|
|
137
138
|
f.write(chunk)
|
|
138
139
|
return_path = os.path.abspath(download_path)
|
|
139
140
|
|
|
140
|
-
logger.info("Downloaded flow to {
|
|
141
|
+
logger.info(f"Downloaded flow to {return_path} (ID: {flow_id})")
|
|
141
142
|
return return_path
|
|
142
143
|
|
|
143
144
|
# Update flow
|
|
@@ -150,28 +151,28 @@ class Flows(QuerysetEndpoint[FlowItem], TaggingMixin[FlowItem]):
|
|
|
150
151
|
self._resource_tagger.update_tags(self.baseurl, flow_item)
|
|
151
152
|
|
|
152
153
|
# Update the flow itself
|
|
153
|
-
url = "{
|
|
154
|
+
url = f"{self.baseurl}/{flow_item.id}"
|
|
154
155
|
update_req = RequestFactory.Flow.update_req(flow_item)
|
|
155
156
|
server_response = self.put_request(url, update_req)
|
|
156
|
-
logger.info("Updated flow item (ID: {
|
|
157
|
+
logger.info(f"Updated flow item (ID: {flow_item.id})")
|
|
157
158
|
updated_flow = copy.copy(flow_item)
|
|
158
159
|
return updated_flow._parse_common_elements(server_response.content, self.parent_srv.namespace)
|
|
159
160
|
|
|
160
161
|
# Update flow connections
|
|
161
162
|
@api(version="3.3")
|
|
162
163
|
def update_connection(self, flow_item: FlowItem, connection_item: ConnectionItem) -> ConnectionItem:
|
|
163
|
-
url = "{
|
|
164
|
+
url = f"{self.baseurl}/{flow_item.id}/connections/{connection_item.id}"
|
|
164
165
|
|
|
165
166
|
update_req = RequestFactory.Connection.update_req(connection_item)
|
|
166
167
|
server_response = self.put_request(url, update_req)
|
|
167
168
|
connection = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
168
169
|
|
|
169
|
-
logger.info("Updated flow item (ID: {
|
|
170
|
+
logger.info(f"Updated flow item (ID: {flow_item.id} & connection item {connection_item.id}")
|
|
170
171
|
return connection
|
|
171
172
|
|
|
172
173
|
@api(version="3.3")
|
|
173
174
|
def refresh(self, flow_item: FlowItem) -> JobItem:
|
|
174
|
-
url = "{
|
|
175
|
+
url = f"{self.baseurl}/{flow_item.id}/run"
|
|
175
176
|
empty_req = RequestFactory.Empty.empty_req()
|
|
176
177
|
server_response = self.post_request(url, empty_req)
|
|
177
178
|
new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
@@ -180,7 +181,7 @@ class Flows(QuerysetEndpoint[FlowItem], TaggingMixin[FlowItem]):
|
|
|
180
181
|
# Publish flow
|
|
181
182
|
@api(version="3.3")
|
|
182
183
|
def publish(
|
|
183
|
-
self, flow_item: FlowItem, file: PathOrFileR, mode: str, connections: Optional[
|
|
184
|
+
self, flow_item: FlowItem, file: PathOrFileR, mode: str, connections: Optional[list[ConnectionItem]] = None
|
|
184
185
|
) -> FlowItem:
|
|
185
186
|
if not mode or not hasattr(self.parent_srv.PublishMode, mode):
|
|
186
187
|
error = "Invalid mode defined."
|
|
@@ -189,7 +190,7 @@ class Flows(QuerysetEndpoint[FlowItem], TaggingMixin[FlowItem]):
|
|
|
189
190
|
if isinstance(file, (str, os.PathLike)):
|
|
190
191
|
if not os.path.isfile(file):
|
|
191
192
|
error = "File path does not lead to an existing file."
|
|
192
|
-
raise
|
|
193
|
+
raise OSError(error)
|
|
193
194
|
|
|
194
195
|
filename = os.path.basename(file)
|
|
195
196
|
file_extension = os.path.splitext(filename)[1][1:]
|
|
@@ -213,30 +214,30 @@ class Flows(QuerysetEndpoint[FlowItem], TaggingMixin[FlowItem]):
|
|
|
213
214
|
elif file_type == "xml":
|
|
214
215
|
file_extension = "tfl"
|
|
215
216
|
else:
|
|
216
|
-
error = "Unsupported file type {}!"
|
|
217
|
+
error = f"Unsupported file type {file_type}!"
|
|
217
218
|
raise ValueError(error)
|
|
218
219
|
|
|
219
220
|
# Generate filename for file object.
|
|
220
221
|
# This is needed when publishing the flow in a single request
|
|
221
|
-
filename = "{}.{}"
|
|
222
|
+
filename = f"{flow_item.name}.{file_extension}"
|
|
222
223
|
file_size = get_file_object_size(file)
|
|
223
224
|
|
|
224
225
|
else:
|
|
225
226
|
raise TypeError("file should be a filepath or file object.")
|
|
226
227
|
|
|
227
228
|
# Construct the url with the defined mode
|
|
228
|
-
url = "{
|
|
229
|
+
url = f"{self.baseurl}?flowType={file_extension}"
|
|
229
230
|
if mode == self.parent_srv.PublishMode.Overwrite or mode == self.parent_srv.PublishMode.Append:
|
|
230
|
-
url += "&{
|
|
231
|
+
url += f"&{mode.lower()}=true"
|
|
231
232
|
|
|
232
233
|
# Determine if chunking is required (64MB is the limit for single upload method)
|
|
233
234
|
if file_size >= FILESIZE_LIMIT:
|
|
234
|
-
logger.info("Publishing {
|
|
235
|
+
logger.info(f"Publishing {filename} to server with chunking method (flow over 64MB)")
|
|
235
236
|
upload_session_id = self.parent_srv.fileuploads.upload(file)
|
|
236
|
-
url = "{
|
|
237
|
+
url = f"{url}&uploadSessionId={upload_session_id}"
|
|
237
238
|
xml_request, content_type = RequestFactory.Flow.publish_req_chunked(flow_item, connections)
|
|
238
239
|
else:
|
|
239
|
-
logger.info("Publishing {
|
|
240
|
+
logger.info(f"Publishing {filename} to server")
|
|
240
241
|
|
|
241
242
|
if isinstance(file, (str, Path)):
|
|
242
243
|
with open(file, "rb") as f:
|
|
@@ -259,7 +260,7 @@ class Flows(QuerysetEndpoint[FlowItem], TaggingMixin[FlowItem]):
|
|
|
259
260
|
raise err
|
|
260
261
|
else:
|
|
261
262
|
new_flow = FlowItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
262
|
-
logger.info("Published {
|
|
263
|
+
logger.info(f"Published {filename} (ID: {new_flow.id})")
|
|
263
264
|
return new_flow
|
|
264
265
|
|
|
265
266
|
@api(version="3.3")
|
|
@@ -294,7 +295,7 @@ class Flows(QuerysetEndpoint[FlowItem], TaggingMixin[FlowItem]):
|
|
|
294
295
|
@api(version="3.3")
|
|
295
296
|
def schedule_flow_run(
|
|
296
297
|
self, schedule_id: str, item: FlowItem
|
|
297
|
-
) ->
|
|
298
|
+
) -> list["AddResponse"]: # actually should return a task
|
|
298
299
|
return self.parent_srv.schedules.add_to_schedule(schedule_id, flow=item)
|
|
299
300
|
|
|
300
301
|
def filter(self, *invalid, page_size: Optional[int] = None, **kwargs) -> QuerySet[FlowItem]:
|
|
@@ -8,7 +8,8 @@ from tableauserverclient.server.pager import Pager
|
|
|
8
8
|
|
|
9
9
|
from tableauserverclient.helpers.logging import logger
|
|
10
10
|
|
|
11
|
-
from typing import
|
|
11
|
+
from typing import Optional, TYPE_CHECKING, Union
|
|
12
|
+
from collections.abc import Iterable
|
|
12
13
|
|
|
13
14
|
from tableauserverclient.server.query import QuerySet
|
|
14
15
|
|
|
@@ -19,10 +20,10 @@ if TYPE_CHECKING:
|
|
|
19
20
|
class Groups(QuerysetEndpoint[GroupItem]):
|
|
20
21
|
@property
|
|
21
22
|
def baseurl(self) -> str:
|
|
22
|
-
return "{
|
|
23
|
+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/groups"
|
|
23
24
|
|
|
24
25
|
@api(version="2.0")
|
|
25
|
-
def get(self, req_options: Optional["RequestOptions"] = None) ->
|
|
26
|
+
def get(self, req_options: Optional["RequestOptions"] = None) -> tuple[list[GroupItem], PaginationItem]:
|
|
26
27
|
"""Gets all groups"""
|
|
27
28
|
logger.info("Querying all groups on site")
|
|
28
29
|
url = self.baseurl
|
|
@@ -50,12 +51,12 @@ class Groups(QuerysetEndpoint[GroupItem]):
|
|
|
50
51
|
|
|
51
52
|
def _get_users_for_group(
|
|
52
53
|
self, group_item: GroupItem, req_options: Optional["RequestOptions"] = None
|
|
53
|
-
) ->
|
|
54
|
-
url = "{
|
|
54
|
+
) -> tuple[list[UserItem], PaginationItem]:
|
|
55
|
+
url = f"{self.baseurl}/{group_item.id}/users"
|
|
55
56
|
server_response = self.get_request(url, req_options)
|
|
56
57
|
user_item = UserItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
57
58
|
pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
58
|
-
logger.info("Populated users for group (ID: {
|
|
59
|
+
logger.info(f"Populated users for group (ID: {group_item.id})")
|
|
59
60
|
return user_item, pagination_item
|
|
60
61
|
|
|
61
62
|
@api(version="2.0")
|
|
@@ -64,13 +65,13 @@ class Groups(QuerysetEndpoint[GroupItem]):
|
|
|
64
65
|
if not group_id:
|
|
65
66
|
error = "Group ID undefined."
|
|
66
67
|
raise ValueError(error)
|
|
67
|
-
url = "{
|
|
68
|
+
url = f"{self.baseurl}/{group_id}"
|
|
68
69
|
self.delete_request(url)
|
|
69
|
-
logger.info("Deleted single group (ID: {
|
|
70
|
+
logger.info(f"Deleted single group (ID: {group_id})")
|
|
70
71
|
|
|
71
72
|
@api(version="2.0")
|
|
72
73
|
def update(self, group_item: GroupItem, as_job: bool = False) -> Union[GroupItem, JobItem]:
|
|
73
|
-
url = "{
|
|
74
|
+
url = f"{self.baseurl}/{group_item.id}"
|
|
74
75
|
|
|
75
76
|
if not group_item.id:
|
|
76
77
|
error = "Group item missing ID."
|
|
@@ -83,7 +84,7 @@ class Groups(QuerysetEndpoint[GroupItem]):
|
|
|
83
84
|
|
|
84
85
|
update_req = RequestFactory.Group.update_req(group_item)
|
|
85
86
|
server_response = self.put_request(url, update_req)
|
|
86
|
-
logger.info("Updated group item (ID: {
|
|
87
|
+
logger.info(f"Updated group item (ID: {group_item.id})")
|
|
87
88
|
if as_job:
|
|
88
89
|
return JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
89
90
|
else:
|
|
@@ -118,9 +119,9 @@ class Groups(QuerysetEndpoint[GroupItem]):
|
|
|
118
119
|
if not user_id:
|
|
119
120
|
error = "User ID undefined."
|
|
120
121
|
raise ValueError(error)
|
|
121
|
-
url = "{
|
|
122
|
+
url = f"{self.baseurl}/{group_item.id}/users/{user_id}"
|
|
122
123
|
self.delete_request(url)
|
|
123
|
-
logger.info("Removed user (id: {
|
|
124
|
+
logger.info(f"Removed user (id: {user_id}) from group (ID: {group_item.id})")
|
|
124
125
|
|
|
125
126
|
@api(version="3.21")
|
|
126
127
|
def remove_users(self, group_item: GroupItem, users: Iterable[Union[str, UserItem]]) -> None:
|
|
@@ -132,7 +133,7 @@ class Groups(QuerysetEndpoint[GroupItem]):
|
|
|
132
133
|
url = f"{self.baseurl}/{group_id}/users/remove"
|
|
133
134
|
add_req = RequestFactory.Group.remove_users_req(users)
|
|
134
135
|
_ = self.put_request(url, add_req)
|
|
135
|
-
logger.info("Removed users to group (ID: {
|
|
136
|
+
logger.info(f"Removed users to group (ID: {group_item.id})")
|
|
136
137
|
return None
|
|
137
138
|
|
|
138
139
|
@api(version="2.0")
|
|
@@ -144,15 +145,15 @@ class Groups(QuerysetEndpoint[GroupItem]):
|
|
|
144
145
|
if not user_id:
|
|
145
146
|
error = "User ID undefined."
|
|
146
147
|
raise ValueError(error)
|
|
147
|
-
url = "{
|
|
148
|
+
url = f"{self.baseurl}/{group_item.id}/users"
|
|
148
149
|
add_req = RequestFactory.Group.add_user_req(user_id)
|
|
149
150
|
server_response = self.post_request(url, add_req)
|
|
150
151
|
user = UserItem.from_response(server_response.content, self.parent_srv.namespace).pop()
|
|
151
|
-
logger.info("Added user (id: {
|
|
152
|
+
logger.info(f"Added user (id: {user_id}) to group (ID: {group_item.id})")
|
|
152
153
|
return user
|
|
153
154
|
|
|
154
155
|
@api(version="3.21")
|
|
155
|
-
def add_users(self, group_item: GroupItem, users: Iterable[Union[str, UserItem]]) ->
|
|
156
|
+
def add_users(self, group_item: GroupItem, users: Iterable[Union[str, UserItem]]) -> list[UserItem]:
|
|
156
157
|
"""Adds multiple users to 1 group"""
|
|
157
158
|
group_id = group_item.id if hasattr(group_item, "id") else group_item
|
|
158
159
|
if not isinstance(group_id, str):
|
|
@@ -162,7 +163,7 @@ class Groups(QuerysetEndpoint[GroupItem]):
|
|
|
162
163
|
add_req = RequestFactory.Group.add_users_req(users)
|
|
163
164
|
server_response = self.post_request(url, add_req)
|
|
164
165
|
users = UserItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
165
|
-
logger.info("Added users to group (ID: {
|
|
166
|
+
logger.info(f"Added users to group (ID: {group_item.id})")
|
|
166
167
|
return users
|
|
167
168
|
|
|
168
169
|
def filter(self, *invalid, page_size: Optional[int] = None, **kwargs) -> QuerySet[GroupItem]:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Literal, Optional, TYPE_CHECKING, Union
|
|
2
2
|
|
|
3
3
|
from tableauserverclient.helpers.logging import logger
|
|
4
4
|
from tableauserverclient.models.group_item import GroupItem
|
|
@@ -27,7 +27,7 @@ class GroupSets(QuerysetEndpoint[GroupSetItem]):
|
|
|
27
27
|
self,
|
|
28
28
|
request_options: Optional[RequestOptions] = None,
|
|
29
29
|
result_level: Optional[Literal["members", "local"]] = None,
|
|
30
|
-
) ->
|
|
30
|
+
) -> tuple[list[GroupSetItem], PaginationItem]:
|
|
31
31
|
logger.info("Querying all group sets on site")
|
|
32
32
|
url = self.baseurl
|
|
33
33
|
if result_level:
|
|
@@ -11,24 +11,24 @@ from tableauserverclient.exponential_backoff import ExponentialBackoffTimer
|
|
|
11
11
|
|
|
12
12
|
from tableauserverclient.helpers.logging import logger
|
|
13
13
|
|
|
14
|
-
from typing import
|
|
14
|
+
from typing import Optional, Union
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class Jobs(QuerysetEndpoint[BackgroundJobItem]):
|
|
18
18
|
@property
|
|
19
19
|
def baseurl(self):
|
|
20
|
-
return "{
|
|
20
|
+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/jobs"
|
|
21
21
|
|
|
22
22
|
@overload # type: ignore[override]
|
|
23
23
|
def get(self: Self, job_id: str, req_options: Optional[RequestOptionsBase] = None) -> JobItem: # type: ignore[override]
|
|
24
24
|
...
|
|
25
25
|
|
|
26
26
|
@overload # type: ignore[override]
|
|
27
|
-
def get(self: Self, job_id: RequestOptionsBase, req_options: None) ->
|
|
27
|
+
def get(self: Self, job_id: RequestOptionsBase, req_options: None) -> tuple[list[BackgroundJobItem], PaginationItem]: # type: ignore[override]
|
|
28
28
|
...
|
|
29
29
|
|
|
30
30
|
@overload # type: ignore[override]
|
|
31
|
-
def get(self: Self, job_id: None, req_options: Optional[RequestOptionsBase]) ->
|
|
31
|
+
def get(self: Self, job_id: None, req_options: Optional[RequestOptionsBase]) -> tuple[list[BackgroundJobItem], PaginationItem]: # type: ignore[override]
|
|
32
32
|
...
|
|
33
33
|
|
|
34
34
|
@api(version="2.6")
|
|
@@ -53,13 +53,13 @@ class Jobs(QuerysetEndpoint[BackgroundJobItem]):
|
|
|
53
53
|
if isinstance(job_id, JobItem):
|
|
54
54
|
job_id = job_id.id
|
|
55
55
|
assert isinstance(job_id, str)
|
|
56
|
-
url = "{
|
|
56
|
+
url = f"{self.baseurl}/{job_id}"
|
|
57
57
|
return self.put_request(url)
|
|
58
58
|
|
|
59
59
|
@api(version="2.6")
|
|
60
60
|
def get_by_id(self, job_id: str) -> JobItem:
|
|
61
61
|
logger.info("Query for information about job " + job_id)
|
|
62
|
-
url = "{
|
|
62
|
+
url = f"{self.baseurl}/{job_id}"
|
|
63
63
|
server_response = self.get_request(url)
|
|
64
64
|
new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
65
65
|
return new_job
|
|
@@ -77,7 +77,7 @@ class Jobs(QuerysetEndpoint[BackgroundJobItem]):
|
|
|
77
77
|
job = self.get_by_id(job_id)
|
|
78
78
|
logger.debug(f"\tJob {job_id} progress={job.progress}")
|
|
79
79
|
|
|
80
|
-
logger.info("Job {} Completed: Finish Code: {} - Notes:{
|
|
80
|
+
logger.info(f"Job {job_id} Completed: Finish Code: {job.finish_code} - Notes:{job.notes}")
|
|
81
81
|
|
|
82
82
|
if job.finish_code == JobItem.FinishCode.Success:
|
|
83
83
|
return job
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Optional, Union
|
|
2
2
|
|
|
3
3
|
from tableauserverclient.helpers.logging import logger
|
|
4
4
|
from tableauserverclient.models.linked_tasks_item import LinkedTaskItem, LinkedTaskJobItem
|
|
@@ -18,7 +18,7 @@ class LinkedTasks(QuerysetEndpoint[LinkedTaskItem]):
|
|
|
18
18
|
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/tasks/linked"
|
|
19
19
|
|
|
20
20
|
@api(version="3.15")
|
|
21
|
-
def get(self, req_options: Optional["RequestOptions"] = None) ->
|
|
21
|
+
def get(self, req_options: Optional["RequestOptions"] = None) -> tuple[list[LinkedTaskItem], PaginationItem]:
|
|
22
22
|
logger.info("Querying all linked tasks on site")
|
|
23
23
|
url = self.baseurl
|
|
24
24
|
server_response = self.get_request(url, req_options)
|
|
@@ -50,11 +50,11 @@ def get_page_info(result):
|
|
|
50
50
|
class Metadata(Endpoint):
|
|
51
51
|
@property
|
|
52
52
|
def baseurl(self):
|
|
53
|
-
return "{
|
|
53
|
+
return f"{self.parent_srv.server_address}/api/metadata/graphql"
|
|
54
54
|
|
|
55
55
|
@property
|
|
56
56
|
def control_baseurl(self):
|
|
57
|
-
return "{
|
|
57
|
+
return f"{self.parent_srv.server_address}/api/metadata/v1/control"
|
|
58
58
|
|
|
59
59
|
@api("3.5")
|
|
60
60
|
def query(self, query, variables=None, abort_on_error=False, parameters=None):
|
|
@@ -8,7 +8,7 @@ from tableauserverclient.models import MetricItem, PaginationItem
|
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
10
|
|
|
11
|
-
from typing import
|
|
11
|
+
from typing import Optional, TYPE_CHECKING
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
from ..request_options import RequestOptions
|
|
@@ -20,18 +20,18 @@ from tableauserverclient.helpers.logging import logger
|
|
|
20
20
|
|
|
21
21
|
class Metrics(QuerysetEndpoint[MetricItem]):
|
|
22
22
|
def __init__(self, parent_srv: "Server") -> None:
|
|
23
|
-
super(
|
|
23
|
+
super().__init__(parent_srv)
|
|
24
24
|
self._resource_tagger = _ResourceTagger(parent_srv)
|
|
25
25
|
self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
|
|
26
26
|
self._data_quality_warnings = _DataQualityWarningEndpoint(self.parent_srv, "metric")
|
|
27
27
|
|
|
28
28
|
@property
|
|
29
29
|
def baseurl(self) -> str:
|
|
30
|
-
return "{
|
|
30
|
+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/metrics"
|
|
31
31
|
|
|
32
32
|
# Get all metrics
|
|
33
33
|
@api(version="3.9")
|
|
34
|
-
def get(self, req_options: Optional["RequestOptions"] = None) ->
|
|
34
|
+
def get(self, req_options: Optional["RequestOptions"] = None) -> tuple[list[MetricItem], PaginationItem]:
|
|
35
35
|
logger.info("Querying all metrics on site")
|
|
36
36
|
url = self.baseurl
|
|
37
37
|
server_response = self.get_request(url, req_options)
|
|
@@ -45,8 +45,8 @@ class Metrics(QuerysetEndpoint[MetricItem]):
|
|
|
45
45
|
if not metric_id:
|
|
46
46
|
error = "Metric ID undefined."
|
|
47
47
|
raise ValueError(error)
|
|
48
|
-
logger.info("Querying single metric (ID: {
|
|
49
|
-
url = "{
|
|
48
|
+
logger.info(f"Querying single metric (ID: {metric_id})")
|
|
49
|
+
url = f"{self.baseurl}/{metric_id}"
|
|
50
50
|
server_response = self.get_request(url)
|
|
51
51
|
return MetricItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
52
52
|
|
|
@@ -56,9 +56,9 @@ class Metrics(QuerysetEndpoint[MetricItem]):
|
|
|
56
56
|
if not metric_id:
|
|
57
57
|
error = "Metric ID undefined."
|
|
58
58
|
raise ValueError(error)
|
|
59
|
-
url = "{
|
|
59
|
+
url = f"{self.baseurl}/{metric_id}"
|
|
60
60
|
self.delete_request(url)
|
|
61
|
-
logger.info("Deleted single metric (ID: {
|
|
61
|
+
logger.info(f"Deleted single metric (ID: {metric_id})")
|
|
62
62
|
|
|
63
63
|
# Update metric
|
|
64
64
|
@api(version="3.9")
|
|
@@ -70,8 +70,8 @@ class Metrics(QuerysetEndpoint[MetricItem]):
|
|
|
70
70
|
self._resource_tagger.update_tags(self.baseurl, metric_item)
|
|
71
71
|
|
|
72
72
|
# Update the metric itself
|
|
73
|
-
url = "{
|
|
73
|
+
url = f"{self.baseurl}/{metric_item.id}"
|
|
74
74
|
update_req = RequestFactory.Metric.update_req(metric_item)
|
|
75
75
|
server_response = self.put_request(url, update_req)
|
|
76
|
-
logger.info("Updated metric item (ID: {
|
|
76
|
+
logger.info(f"Updated metric item (ID: {metric_item.id})")
|
|
77
77
|
return MetricItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
@@ -6,7 +6,7 @@ from tableauserverclient.models import TableauItem, PermissionsRule
|
|
|
6
6
|
from .endpoint import Endpoint
|
|
7
7
|
from .exceptions import MissingRequiredFieldError
|
|
8
8
|
|
|
9
|
-
from typing import Callable, TYPE_CHECKING,
|
|
9
|
+
from typing import Callable, TYPE_CHECKING, Optional, Union
|
|
10
10
|
|
|
11
11
|
from tableauserverclient.helpers.logging import logger
|
|
12
12
|
|
|
@@ -25,7 +25,7 @@ class _PermissionsEndpoint(Endpoint):
|
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
def __init__(self, parent_srv: "Server", owner_baseurl: Callable[[], str]) -> None:
|
|
28
|
-
super(
|
|
28
|
+
super().__init__(parent_srv)
|
|
29
29
|
|
|
30
30
|
# owner_baseurl is the baseurl of the parent. The MUST be a lambda
|
|
31
31
|
# since we don't know the full site URL until we sign in. If
|
|
@@ -33,18 +33,18 @@ class _PermissionsEndpoint(Endpoint):
|
|
|
33
33
|
self.owner_baseurl = owner_baseurl
|
|
34
34
|
|
|
35
35
|
def __str__(self):
|
|
36
|
-
return "<PermissionsEndpoint baseurl={}>"
|
|
36
|
+
return f"<PermissionsEndpoint baseurl={self.owner_baseurl}>"
|
|
37
37
|
|
|
38
|
-
def update(self, resource: TableauItem, permissions:
|
|
39
|
-
url = "{
|
|
38
|
+
def update(self, resource: TableauItem, permissions: list[PermissionsRule]) -> list[PermissionsRule]:
|
|
39
|
+
url = f"{self.owner_baseurl()}/{resource.id}/permissions"
|
|
40
40
|
update_req = RequestFactory.Permission.add_req(permissions)
|
|
41
41
|
response = self.put_request(url, update_req)
|
|
42
42
|
permissions = PermissionsRule.from_response(response.content, self.parent_srv.namespace)
|
|
43
|
-
logger.info("Updated permissions for resource {
|
|
43
|
+
logger.info(f"Updated permissions for resource {resource.id}: {permissions}")
|
|
44
44
|
|
|
45
45
|
return permissions
|
|
46
46
|
|
|
47
|
-
def delete(self, resource: TableauItem, rules: Union[PermissionsRule,
|
|
47
|
+
def delete(self, resource: TableauItem, rules: Union[PermissionsRule, list[PermissionsRule]]):
|
|
48
48
|
# Delete is the only endpoint that doesn't take a list of rules
|
|
49
49
|
# so let's fake it to keep it consistent
|
|
50
50
|
# TODO that means we need error handling around the call
|
|
@@ -54,7 +54,7 @@ class _PermissionsEndpoint(Endpoint):
|
|
|
54
54
|
for rule in rules:
|
|
55
55
|
for capability, mode in rule.capabilities.items():
|
|
56
56
|
"/permissions/groups/group-id/capability-name/capability-mode"
|
|
57
|
-
url = "{
|
|
57
|
+
url = "{}/{}/permissions/{}/{}/{}/{}".format(
|
|
58
58
|
self.owner_baseurl(),
|
|
59
59
|
resource.id,
|
|
60
60
|
rule.grantee.tag_name + "s",
|
|
@@ -63,13 +63,11 @@ class _PermissionsEndpoint(Endpoint):
|
|
|
63
63
|
mode,
|
|
64
64
|
)
|
|
65
65
|
|
|
66
|
-
logger.debug("Removing {
|
|
66
|
+
logger.debug(f"Removing {mode} permission for capability {capability}")
|
|
67
67
|
|
|
68
68
|
self.delete_request(url)
|
|
69
69
|
|
|
70
|
-
logger.info(
|
|
71
|
-
"Deleted permission for {0} {1} item {2}".format(rule.grantee.tag_name, rule.grantee.id, resource.id)
|
|
72
|
-
)
|
|
70
|
+
logger.info(f"Deleted permission for {rule.grantee.tag_name} {rule.grantee.id} item {resource.id}")
|
|
73
71
|
|
|
74
72
|
def populate(self, item: TableauItem):
|
|
75
73
|
if not item.id:
|
|
@@ -80,12 +78,12 @@ class _PermissionsEndpoint(Endpoint):
|
|
|
80
78
|
return self._get_permissions(item)
|
|
81
79
|
|
|
82
80
|
item._set_permissions(permission_fetcher)
|
|
83
|
-
logger.info("Populated permissions for item (ID: {
|
|
81
|
+
logger.info(f"Populated permissions for item (ID: {item.id})")
|
|
84
82
|
|
|
85
83
|
def _get_permissions(self, item: TableauItem, req_options: Optional["RequestOptions"] = None):
|
|
86
|
-
url = "{
|
|
84
|
+
url = f"{self.owner_baseurl()}/{item.id}/permissions"
|
|
87
85
|
server_response = self.get_request(url, req_options)
|
|
88
86
|
permissions = PermissionsRule.from_response(server_response.content, self.parent_srv.namespace)
|
|
89
|
-
logger.info("Permissions for resource {
|
|
87
|
+
logger.info(f"Permissions for resource {item.id}: {permissions}")
|
|
90
88
|
|
|
91
89
|
return permissions
|