tableauserverclient 0.32__py3-none-any.whl → 0.34__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tableauserverclient/__init__.py +34 -18
- tableauserverclient/_version.py +3 -3
- tableauserverclient/config.py +20 -6
- tableauserverclient/models/__init__.py +12 -0
- tableauserverclient/models/column_item.py +1 -1
- tableauserverclient/models/connection_credentials.py +1 -1
- tableauserverclient/models/connection_item.py +10 -8
- 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 +8 -2
- 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 +12 -12
- tableauserverclient/models/flow_run_item.py +3 -3
- tableauserverclient/models/group_item.py +4 -4
- tableauserverclient/models/groupset_item.py +53 -0
- tableauserverclient/models/interval_item.py +36 -23
- tableauserverclient/models/job_item.py +26 -10
- tableauserverclient/models/linked_tasks_item.py +102 -0
- tableauserverclient/models/metric_item.py +5 -5
- tableauserverclient/models/pagination_item.py +1 -1
- tableauserverclient/models/permissions_item.py +19 -14
- 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 +11 -9
- 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 +78 -0
- 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/__init__.py +8 -0
- tableauserverclient/server/endpoint/auth_endpoint.py +68 -11
- tableauserverclient/server/endpoint/custom_views_endpoint.py +124 -19
- 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 +32 -17
- tableauserverclient/server/endpoint/datasources_endpoint.py +150 -59
- tableauserverclient/server/endpoint/default_permissions_endpoint.py +19 -18
- tableauserverclient/server/endpoint/dqw_endpoint.py +9 -9
- tableauserverclient/server/endpoint/endpoint.py +47 -31
- tableauserverclient/server/endpoint/exceptions.py +23 -7
- tableauserverclient/server/endpoint/favorites_endpoint.py +31 -31
- tableauserverclient/server/endpoint/fileuploads_endpoint.py +11 -13
- tableauserverclient/server/endpoint/flow_runs_endpoint.py +59 -17
- tableauserverclient/server/endpoint/flow_task_endpoint.py +2 -2
- tableauserverclient/server/endpoint/flows_endpoint.py +73 -35
- tableauserverclient/server/endpoint/groups_endpoint.py +96 -27
- tableauserverclient/server/endpoint/groupsets_endpoint.py +127 -0
- tableauserverclient/server/endpoint/jobs_endpoint.py +79 -12
- tableauserverclient/server/endpoint/linked_tasks_endpoint.py +45 -0
- 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 +124 -30
- tableauserverclient/server/endpoint/resource_tagger.py +139 -6
- 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 +33 -19
- tableauserverclient/server/endpoint/tasks_endpoint.py +8 -8
- tableauserverclient/server/endpoint/users_endpoint.py +405 -19
- tableauserverclient/server/endpoint/views_endpoint.py +111 -25
- tableauserverclient/server/endpoint/virtual_connections_endpoint.py +174 -0
- tableauserverclient/server/endpoint/webhooks_endpoint.py +11 -11
- tableauserverclient/server/endpoint/workbooks_endpoint.py +735 -68
- tableauserverclient/server/filter.py +2 -2
- tableauserverclient/server/pager.py +8 -10
- tableauserverclient/server/query.py +70 -20
- tableauserverclient/server/request_factory.py +213 -41
- tableauserverclient/server/request_options.py +125 -145
- tableauserverclient/server/server.py +73 -9
- tableauserverclient/server/sort.py +2 -2
- {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/METADATA +17 -17
- tableauserverclient-0.34.dist-info/RECORD +106 -0
- {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/WHEEL +1 -1
- tableauserverclient-0.32.dist-info/RECORD +0 -100
- {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/LICENSE +0 -0
- {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/LICENSE.versioneer +0 -0
- {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/top_level.txt +0 -0
|
@@ -7,11 +7,13 @@ from contextlib import closing
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
9
|
from tableauserverclient.helpers.headers import fix_filename
|
|
10
|
+
from tableauserverclient.models.permissions_item import PermissionsRule
|
|
11
|
+
from tableauserverclient.server.query import QuerySet
|
|
10
12
|
|
|
11
|
-
from .endpoint import QuerysetEndpoint, api, parameter_added_in
|
|
12
|
-
from .exceptions import InternalServerError, MissingRequiredFieldError
|
|
13
|
-
from .permissions_endpoint import _PermissionsEndpoint
|
|
14
|
-
from .resource_tagger import
|
|
13
|
+
from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api, parameter_added_in
|
|
14
|
+
from tableauserverclient.server.endpoint.exceptions import InternalServerError, MissingRequiredFieldError
|
|
15
|
+
from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
|
|
16
|
+
from tableauserverclient.server.endpoint.resource_tagger import TaggingMixin
|
|
15
17
|
|
|
16
18
|
from tableauserverclient.filesys_helpers import (
|
|
17
19
|
to_filename,
|
|
@@ -24,19 +26,17 @@ from tableauserverclient.models import WorkbookItem, ConnectionItem, ViewItem, P
|
|
|
24
26
|
from tableauserverclient.server import RequestFactory
|
|
25
27
|
|
|
26
28
|
from typing import (
|
|
27
|
-
List,
|
|
28
29
|
Optional,
|
|
29
|
-
Sequence,
|
|
30
|
-
Tuple,
|
|
31
30
|
TYPE_CHECKING,
|
|
32
31
|
Union,
|
|
33
32
|
)
|
|
33
|
+
from collections.abc import Iterable, Sequence
|
|
34
34
|
|
|
35
35
|
if TYPE_CHECKING:
|
|
36
36
|
from tableauserverclient.server import Server
|
|
37
37
|
from tableauserverclient.server.request_options import RequestOptions
|
|
38
|
-
from tableauserverclient.models import DatasourceItem
|
|
39
|
-
from .schedules_endpoint import AddResponse
|
|
38
|
+
from tableauserverclient.models import DatasourceItem
|
|
39
|
+
from tableauserverclient.server.endpoint.schedules_endpoint import AddResponse
|
|
40
40
|
|
|
41
41
|
io_types_r = (io.BytesIO, io.BufferedReader)
|
|
42
42
|
io_types_w = (io.BytesIO, io.BufferedWriter)
|
|
@@ -56,21 +56,36 @@ PathOrFileR = Union[FilePath, FileObjectR]
|
|
|
56
56
|
PathOrFileW = Union[FilePath, FileObjectW]
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
59
|
+
class Workbooks(QuerysetEndpoint[WorkbookItem], TaggingMixin[WorkbookItem]):
|
|
60
60
|
def __init__(self, parent_srv: "Server") -> None:
|
|
61
|
-
super(
|
|
62
|
-
self._resource_tagger = _ResourceTagger(parent_srv)
|
|
61
|
+
super().__init__(parent_srv)
|
|
63
62
|
self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
|
|
64
63
|
|
|
65
64
|
return None
|
|
66
65
|
|
|
67
66
|
@property
|
|
68
67
|
def baseurl(self) -> str:
|
|
69
|
-
return "{
|
|
68
|
+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/workbooks"
|
|
70
69
|
|
|
71
70
|
# Get all workbooks on site
|
|
72
71
|
@api(version="2.0")
|
|
73
|
-
def get(self, req_options: Optional["RequestOptions"] = None) ->
|
|
72
|
+
def get(self, req_options: Optional["RequestOptions"] = None) -> tuple[list[WorkbookItem], PaginationItem]:
|
|
73
|
+
"""
|
|
74
|
+
Queries the server and returns information about the workbooks the site.
|
|
75
|
+
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
req_options : RequestOptions, optional
|
|
79
|
+
(Optional) You can pass the method a request object that contains
|
|
80
|
+
additional parameters to filter the request. For example, if you
|
|
81
|
+
were searching for a specific workbook, you could specify the name
|
|
82
|
+
of the workbook or the name of the owner.
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
Tuple containing one page's worth of workbook items and pagination
|
|
87
|
+
information.
|
|
88
|
+
"""
|
|
74
89
|
logger.info("Querying all workbooks on site")
|
|
75
90
|
url = self.baseurl
|
|
76
91
|
server_response = self.get_request(url, req_options)
|
|
@@ -81,18 +96,44 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
81
96
|
# Get 1 workbook
|
|
82
97
|
@api(version="2.0")
|
|
83
98
|
def get_by_id(self, workbook_id: str) -> WorkbookItem:
|
|
99
|
+
"""
|
|
100
|
+
Returns information about the specified workbook on the site.
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
workbook_id : str
|
|
105
|
+
The workbook ID.
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
WorkbookItem
|
|
110
|
+
The workbook item.
|
|
111
|
+
"""
|
|
84
112
|
if not workbook_id:
|
|
85
113
|
error = "Workbook ID undefined."
|
|
86
114
|
raise ValueError(error)
|
|
87
|
-
logger.info("Querying single workbook (ID: {
|
|
88
|
-
url = "{
|
|
115
|
+
logger.info(f"Querying single workbook (ID: {workbook_id})")
|
|
116
|
+
url = f"{self.baseurl}/{workbook_id}"
|
|
89
117
|
server_response = self.get_request(url)
|
|
90
118
|
return WorkbookItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
91
119
|
|
|
92
120
|
@api(version="2.8")
|
|
93
121
|
def refresh(self, workbook_item: Union[WorkbookItem, str]) -> JobItem:
|
|
122
|
+
"""
|
|
123
|
+
Refreshes the extract of an existing workbook.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
workbook_item : WorkbookItem | str
|
|
128
|
+
The workbook item or workbook ID.
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
JobItem
|
|
133
|
+
The job item.
|
|
134
|
+
"""
|
|
94
135
|
id_ = getattr(workbook_item, "id", workbook_item)
|
|
95
|
-
url = "{
|
|
136
|
+
url = f"{self.baseurl}/{id_}/refresh"
|
|
96
137
|
empty_req = RequestFactory.Empty.empty_req()
|
|
97
138
|
server_response = self.post_request(url, empty_req)
|
|
98
139
|
new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
@@ -105,10 +146,37 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
105
146
|
workbook_item: WorkbookItem,
|
|
106
147
|
encrypt: bool = False,
|
|
107
148
|
includeAll: bool = True,
|
|
108
|
-
datasources: Optional[
|
|
149
|
+
datasources: Optional[list["DatasourceItem"]] = None,
|
|
109
150
|
) -> JobItem:
|
|
151
|
+
"""
|
|
152
|
+
Create one or more extracts on 1 workbook, optionally encrypted.
|
|
153
|
+
|
|
154
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm#create_extracts_for_workbook
|
|
155
|
+
|
|
156
|
+
Parameters
|
|
157
|
+
----------
|
|
158
|
+
workbook_item : WorkbookItem
|
|
159
|
+
The workbook item to create extracts for.
|
|
160
|
+
|
|
161
|
+
encrypt : bool, default False
|
|
162
|
+
Set to True to encrypt the extracts.
|
|
163
|
+
|
|
164
|
+
includeAll : bool, default True
|
|
165
|
+
If True, all data sources in the workbook will have an extract
|
|
166
|
+
created for them. If False, then a data source must be supplied in
|
|
167
|
+
the request.
|
|
168
|
+
|
|
169
|
+
datasources : list[DatasourceItem] | None
|
|
170
|
+
List of DatasourceItem objects for the data sources to create
|
|
171
|
+
extracts for. Only required if includeAll is False.
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
-------
|
|
175
|
+
JobItem
|
|
176
|
+
The job item for the extract creation.
|
|
177
|
+
"""
|
|
110
178
|
id_ = getattr(workbook_item, "id", workbook_item)
|
|
111
|
-
url = "{
|
|
179
|
+
url = f"{self.baseurl}/{id_}/createExtract?encrypt={encrypt}"
|
|
112
180
|
|
|
113
181
|
datasource_req = RequestFactory.Workbook.embedded_extract_req(includeAll, datasources)
|
|
114
182
|
server_response = self.post_request(url, datasource_req)
|
|
@@ -118,8 +186,31 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
118
186
|
# delete all the extracts on 1 workbook
|
|
119
187
|
@api(version="3.3")
|
|
120
188
|
def delete_extract(self, workbook_item: WorkbookItem, includeAll: bool = True, datasources=None) -> JobItem:
|
|
189
|
+
"""
|
|
190
|
+
Delete all extracts of embedded datasources on 1 workbook.
|
|
191
|
+
|
|
192
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm#delete_extracts_from_workbook
|
|
193
|
+
|
|
194
|
+
Parameters
|
|
195
|
+
----------
|
|
196
|
+
workbook_item : WorkbookItem
|
|
197
|
+
The workbook item to delete extracts from.
|
|
198
|
+
|
|
199
|
+
includeAll : bool, default True
|
|
200
|
+
If True, all data sources in the workbook will have their extracts
|
|
201
|
+
deleted. If False, then a data source must be supplied in the
|
|
202
|
+
request.
|
|
203
|
+
|
|
204
|
+
datasources : list[DatasourceItem] | None
|
|
205
|
+
List of DatasourceItem objects for the data sources to delete
|
|
206
|
+
extracts from. Only required if includeAll is False.
|
|
207
|
+
|
|
208
|
+
Returns
|
|
209
|
+
-------
|
|
210
|
+
JobItem
|
|
211
|
+
"""
|
|
121
212
|
id_ = getattr(workbook_item, "id", workbook_item)
|
|
122
|
-
url = "{
|
|
213
|
+
url = f"{self.baseurl}/{id_}/deleteExtract"
|
|
123
214
|
datasource_req = RequestFactory.Workbook.embedded_extract_req(includeAll, datasources)
|
|
124
215
|
server_response = self.post_request(url, datasource_req)
|
|
125
216
|
new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
@@ -128,12 +219,24 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
128
219
|
# Delete 1 workbook by id
|
|
129
220
|
@api(version="2.0")
|
|
130
221
|
def delete(self, workbook_id: str) -> None:
|
|
222
|
+
"""
|
|
223
|
+
Deletes a workbook with the specified ID.
|
|
224
|
+
|
|
225
|
+
Parameters
|
|
226
|
+
----------
|
|
227
|
+
workbook_id : str
|
|
228
|
+
The workbook ID.
|
|
229
|
+
|
|
230
|
+
Returns
|
|
231
|
+
-------
|
|
232
|
+
None
|
|
233
|
+
"""
|
|
131
234
|
if not workbook_id:
|
|
132
235
|
error = "Workbook ID undefined."
|
|
133
236
|
raise ValueError(error)
|
|
134
|
-
url = "{
|
|
237
|
+
url = f"{self.baseurl}/{workbook_id}"
|
|
135
238
|
self.delete_request(url)
|
|
136
|
-
logger.info("Deleted single workbook (ID: {
|
|
239
|
+
logger.info(f"Deleted single workbook (ID: {workbook_id})")
|
|
137
240
|
|
|
138
241
|
# Update workbook
|
|
139
242
|
@api(version="2.0")
|
|
@@ -143,34 +246,77 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
143
246
|
workbook_item: WorkbookItem,
|
|
144
247
|
include_view_acceleration_status: bool = False,
|
|
145
248
|
) -> WorkbookItem:
|
|
249
|
+
"""
|
|
250
|
+
Modifies an existing workbook. Use this method to change the owner or
|
|
251
|
+
the project that the workbook belongs to, or to change whether the
|
|
252
|
+
workbook shows views in tabs. The workbook item must include the
|
|
253
|
+
workbook ID and overrides the existing settings.
|
|
254
|
+
|
|
255
|
+
See https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_workbooks_and_views.htm#update_workbook
|
|
256
|
+
for a list of fields that can be updated.
|
|
257
|
+
|
|
258
|
+
Parameters
|
|
259
|
+
----------
|
|
260
|
+
workbook_item : WorkbookItem
|
|
261
|
+
The workbook item to update. ID is required. Other fields are
|
|
262
|
+
optional. Any fields that are not specified will not be changed.
|
|
263
|
+
|
|
264
|
+
include_view_acceleration_status : bool, default False
|
|
265
|
+
Set to True to include the view acceleration status in the response.
|
|
266
|
+
|
|
267
|
+
Returns
|
|
268
|
+
-------
|
|
269
|
+
WorkbookItem
|
|
270
|
+
The updated workbook item.
|
|
271
|
+
"""
|
|
146
272
|
if not workbook_item.id:
|
|
147
273
|
error = "Workbook item missing ID. Workbook must be retrieved from server first."
|
|
148
274
|
raise MissingRequiredFieldError(error)
|
|
149
275
|
|
|
150
|
-
self.
|
|
276
|
+
self.update_tags(workbook_item)
|
|
151
277
|
|
|
152
278
|
# Update the workbook itself
|
|
153
|
-
url = "{
|
|
279
|
+
url = f"{self.baseurl}/{workbook_item.id}"
|
|
154
280
|
if include_view_acceleration_status:
|
|
155
281
|
url += "?includeViewAccelerationStatus=True"
|
|
156
282
|
|
|
157
283
|
update_req = RequestFactory.Workbook.update_req(workbook_item)
|
|
158
284
|
server_response = self.put_request(url, update_req)
|
|
159
|
-
logger.info("Updated workbook item (ID: {
|
|
285
|
+
logger.info(f"Updated workbook item (ID: {workbook_item.id})")
|
|
160
286
|
updated_workbook = copy.copy(workbook_item)
|
|
161
287
|
return updated_workbook._parse_common_tags(server_response.content, self.parent_srv.namespace)
|
|
162
288
|
|
|
163
289
|
# Update workbook_connection
|
|
164
290
|
@api(version="2.3")
|
|
165
291
|
def update_connection(self, workbook_item: WorkbookItem, connection_item: ConnectionItem) -> ConnectionItem:
|
|
166
|
-
|
|
292
|
+
"""
|
|
293
|
+
Updates a workbook connection information (server addres, server port,
|
|
294
|
+
user name, and password).
|
|
295
|
+
|
|
296
|
+
The workbook connections must be populated before the strings can be
|
|
297
|
+
updated.
|
|
298
|
+
|
|
299
|
+
Rest API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm#update_workbook_connection
|
|
300
|
+
|
|
301
|
+
Parameters
|
|
302
|
+
----------
|
|
303
|
+
workbook_item : WorkbookItem
|
|
304
|
+
The workbook item to update.
|
|
305
|
+
|
|
306
|
+
connection_item : ConnectionItem
|
|
307
|
+
The connection item to update.
|
|
308
|
+
|
|
309
|
+
Returns
|
|
310
|
+
-------
|
|
311
|
+
ConnectionItem
|
|
312
|
+
The updated connection item.
|
|
313
|
+
"""
|
|
314
|
+
url = f"{self.baseurl}/{workbook_item.id}/connections/{connection_item.id}"
|
|
167
315
|
update_req = RequestFactory.Connection.update_req(connection_item)
|
|
168
316
|
server_response = self.put_request(url, update_req)
|
|
169
317
|
connection = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
170
318
|
|
|
171
|
-
logger.info(
|
|
172
|
-
"Updated workbook item (ID: {0} & connection item {1})".format(workbook_item.id, connection_item.id)
|
|
173
|
-
)
|
|
319
|
+
logger.info(f"Updated workbook item (ID: {workbook_item.id} & connection item {connection_item.id})")
|
|
174
320
|
return connection
|
|
175
321
|
|
|
176
322
|
# Download workbook contents with option of passing in filepath
|
|
@@ -182,7 +328,35 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
182
328
|
workbook_id: str,
|
|
183
329
|
filepath: Optional[PathOrFileW] = None,
|
|
184
330
|
include_extract: bool = True,
|
|
185
|
-
) ->
|
|
331
|
+
) -> PathOrFileW:
|
|
332
|
+
"""
|
|
333
|
+
Downloads a workbook to the specified directory (optional).
|
|
334
|
+
|
|
335
|
+
Parameters
|
|
336
|
+
----------
|
|
337
|
+
workbook_id : str
|
|
338
|
+
The workbook ID.
|
|
339
|
+
|
|
340
|
+
filepath : Path or File object, optional
|
|
341
|
+
Downloads the file to the location you specify. If no location is
|
|
342
|
+
specified, the file is downloaded to the current working directory.
|
|
343
|
+
The default is Filepath=None.
|
|
344
|
+
|
|
345
|
+
include_extract : bool, default True
|
|
346
|
+
Set to False to exclude the extract from the download. The default
|
|
347
|
+
is True.
|
|
348
|
+
|
|
349
|
+
Returns
|
|
350
|
+
-------
|
|
351
|
+
Path or File object
|
|
352
|
+
The path to the downloaded workbook or the file object.
|
|
353
|
+
|
|
354
|
+
Raises
|
|
355
|
+
------
|
|
356
|
+
ValueError
|
|
357
|
+
If the workbook ID is not defined.
|
|
358
|
+
"""
|
|
359
|
+
|
|
186
360
|
return self.download_revision(
|
|
187
361
|
workbook_id,
|
|
188
362
|
None,
|
|
@@ -193,18 +367,48 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
193
367
|
# Get all views of workbook
|
|
194
368
|
@api(version="2.0")
|
|
195
369
|
def populate_views(self, workbook_item: WorkbookItem, usage: bool = False) -> None:
|
|
370
|
+
"""
|
|
371
|
+
Populates (or gets) a list of views for a workbook.
|
|
372
|
+
|
|
373
|
+
You must first call this method to populate views before you can iterate
|
|
374
|
+
through the views.
|
|
375
|
+
|
|
376
|
+
This method retrieves the view information for the specified workbook.
|
|
377
|
+
The REST API is designed to return only the information you ask for
|
|
378
|
+
explicitly. When you query for all the workbooks, the view information
|
|
379
|
+
is not included. Use this method to retrieve the views. The method adds
|
|
380
|
+
the list of views to the workbook item (workbook_item.views). This is a
|
|
381
|
+
list of ViewItem.
|
|
382
|
+
|
|
383
|
+
Parameters
|
|
384
|
+
----------
|
|
385
|
+
workbook_item : WorkbookItem
|
|
386
|
+
The workbook item to populate views for.
|
|
387
|
+
|
|
388
|
+
usage : bool, default False
|
|
389
|
+
Set to True to include usage statistics for each view.
|
|
390
|
+
|
|
391
|
+
Returns
|
|
392
|
+
-------
|
|
393
|
+
None
|
|
394
|
+
|
|
395
|
+
Raises
|
|
396
|
+
------
|
|
397
|
+
MissingRequiredFieldError
|
|
398
|
+
If the workbook item is missing an ID.
|
|
399
|
+
"""
|
|
196
400
|
if not workbook_item.id:
|
|
197
401
|
error = "Workbook item missing ID. Workbook must be retrieved from server first."
|
|
198
402
|
raise MissingRequiredFieldError(error)
|
|
199
403
|
|
|
200
|
-
def view_fetcher() ->
|
|
404
|
+
def view_fetcher() -> list[ViewItem]:
|
|
201
405
|
return self._get_views_for_workbook(workbook_item, usage)
|
|
202
406
|
|
|
203
407
|
workbook_item._set_views(view_fetcher)
|
|
204
|
-
logger.info("Populated views for workbook (ID: {
|
|
408
|
+
logger.info(f"Populated views for workbook (ID: {workbook_item.id})")
|
|
205
409
|
|
|
206
|
-
def _get_views_for_workbook(self, workbook_item: WorkbookItem, usage: bool) ->
|
|
207
|
-
url = "{
|
|
410
|
+
def _get_views_for_workbook(self, workbook_item: WorkbookItem, usage: bool) -> list[ViewItem]:
|
|
411
|
+
url = f"{self.baseurl}/{workbook_item.id}/views"
|
|
208
412
|
if usage:
|
|
209
413
|
url += "?includeUsageStatistics=true"
|
|
210
414
|
server_response = self.get_request(url)
|
|
@@ -218,6 +422,36 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
218
422
|
# Get all connections of workbook
|
|
219
423
|
@api(version="2.0")
|
|
220
424
|
def populate_connections(self, workbook_item: WorkbookItem) -> None:
|
|
425
|
+
"""
|
|
426
|
+
Populates a list of data source connections for the specified workbook.
|
|
427
|
+
|
|
428
|
+
You must populate connections before you can iterate through the
|
|
429
|
+
connections.
|
|
430
|
+
|
|
431
|
+
This method retrieves the data source connection information for the
|
|
432
|
+
specified workbook. The REST API is designed to return only the
|
|
433
|
+
information you ask for explicitly. When you query all the workbooks,
|
|
434
|
+
the data source connection information is not included. Use this method
|
|
435
|
+
to retrieve the connection information for any data sources used by the
|
|
436
|
+
workbook. The method adds the list of data connections to the workbook
|
|
437
|
+
item (workbook_item.connections). This is a list of ConnectionItem.
|
|
438
|
+
|
|
439
|
+
REST API docs: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm#query_workbook_connections
|
|
440
|
+
|
|
441
|
+
Parameters
|
|
442
|
+
----------
|
|
443
|
+
workbook_item : WorkbookItem
|
|
444
|
+
The workbook item to populate connections for.
|
|
445
|
+
|
|
446
|
+
Returns
|
|
447
|
+
-------
|
|
448
|
+
None
|
|
449
|
+
|
|
450
|
+
Raises
|
|
451
|
+
------
|
|
452
|
+
MissingRequiredFieldError
|
|
453
|
+
If the workbook item is missing an ID.
|
|
454
|
+
"""
|
|
221
455
|
if not workbook_item.id:
|
|
222
456
|
error = "Workbook item missing ID. Workbook must be retrieved from server first."
|
|
223
457
|
raise MissingRequiredFieldError(error)
|
|
@@ -226,12 +460,12 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
226
460
|
return self._get_workbook_connections(workbook_item)
|
|
227
461
|
|
|
228
462
|
workbook_item._set_connections(connection_fetcher)
|
|
229
|
-
logger.info("Populated connections for workbook (ID: {
|
|
463
|
+
logger.info(f"Populated connections for workbook (ID: {workbook_item.id})")
|
|
230
464
|
|
|
231
465
|
def _get_workbook_connections(
|
|
232
466
|
self, workbook_item: WorkbookItem, req_options: Optional["RequestOptions"] = None
|
|
233
|
-
) ->
|
|
234
|
-
url = "{
|
|
467
|
+
) -> list[ConnectionItem]:
|
|
468
|
+
url = f"{self.baseurl}/{workbook_item.id}/connections"
|
|
235
469
|
server_response = self.get_request(url, req_options)
|
|
236
470
|
connections = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
237
471
|
return connections
|
|
@@ -239,6 +473,34 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
239
473
|
# Get the pdf of the entire workbook if its tabs are enabled, pdf of the default view if its tabs are disabled
|
|
240
474
|
@api(version="3.4")
|
|
241
475
|
def populate_pdf(self, workbook_item: WorkbookItem, req_options: Optional["RequestOptions"] = None) -> None:
|
|
476
|
+
"""
|
|
477
|
+
Populates the PDF for the specified workbook item.
|
|
478
|
+
|
|
479
|
+
This method populates a PDF with image(s) of the workbook view(s) you
|
|
480
|
+
specify.
|
|
481
|
+
|
|
482
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm#download_workbook_pdf
|
|
483
|
+
|
|
484
|
+
Parameters
|
|
485
|
+
----------
|
|
486
|
+
workbook_item : WorkbookItem
|
|
487
|
+
The workbook item to populate the PDF for.
|
|
488
|
+
|
|
489
|
+
req_options : RequestOptions, optional
|
|
490
|
+
(Optional) You can pass in request options to specify the page type
|
|
491
|
+
and orientation of the PDF content, as well as the maximum age of
|
|
492
|
+
the PDF rendered on the server. See PDFRequestOptions class for more
|
|
493
|
+
details.
|
|
494
|
+
|
|
495
|
+
Returns
|
|
496
|
+
-------
|
|
497
|
+
None
|
|
498
|
+
|
|
499
|
+
Raises
|
|
500
|
+
------
|
|
501
|
+
MissingRequiredFieldError
|
|
502
|
+
If the workbook item is missing an ID.
|
|
503
|
+
"""
|
|
242
504
|
if not workbook_item.id:
|
|
243
505
|
error = "Workbook item missing ID."
|
|
244
506
|
raise MissingRequiredFieldError(error)
|
|
@@ -247,16 +509,46 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
247
509
|
return self._get_wb_pdf(workbook_item, req_options)
|
|
248
510
|
|
|
249
511
|
workbook_item._set_pdf(pdf_fetcher)
|
|
250
|
-
logger.info("Populated pdf for workbook (ID: {
|
|
512
|
+
logger.info(f"Populated pdf for workbook (ID: {workbook_item.id})")
|
|
251
513
|
|
|
252
514
|
def _get_wb_pdf(self, workbook_item: WorkbookItem, req_options: Optional["RequestOptions"]) -> bytes:
|
|
253
|
-
url = "{
|
|
515
|
+
url = f"{self.baseurl}/{workbook_item.id}/pdf"
|
|
254
516
|
server_response = self.get_request(url, req_options)
|
|
255
517
|
pdf = server_response.content
|
|
256
518
|
return pdf
|
|
257
519
|
|
|
258
520
|
@api(version="3.8")
|
|
259
521
|
def populate_powerpoint(self, workbook_item: WorkbookItem, req_options: Optional["RequestOptions"] = None) -> None:
|
|
522
|
+
"""
|
|
523
|
+
Populates the PowerPoint for the specified workbook item.
|
|
524
|
+
|
|
525
|
+
This method populates a PowerPoint with image(s) of the workbook view(s) you
|
|
526
|
+
specify.
|
|
527
|
+
|
|
528
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm#download_workbook_powerpoint
|
|
529
|
+
|
|
530
|
+
Parameters
|
|
531
|
+
----------
|
|
532
|
+
workbook_item : WorkbookItem
|
|
533
|
+
The workbook item to populate the PDF for.
|
|
534
|
+
|
|
535
|
+
req_options : RequestOptions, optional
|
|
536
|
+
(Optional) You can pass in request options to specify the maximum
|
|
537
|
+
number of minutes a workbook .pptx will be cached before being
|
|
538
|
+
refreshed. To prevent multiple .pptx requests from overloading the
|
|
539
|
+
server, the shortest interval you can set is one minute. There is no
|
|
540
|
+
maximum value, but the server job enacting the caching action may
|
|
541
|
+
expire before a long cache period is reached.
|
|
542
|
+
|
|
543
|
+
Returns
|
|
544
|
+
-------
|
|
545
|
+
None
|
|
546
|
+
|
|
547
|
+
Raises
|
|
548
|
+
------
|
|
549
|
+
MissingRequiredFieldError
|
|
550
|
+
If the workbook item is missing an ID.
|
|
551
|
+
"""
|
|
260
552
|
if not workbook_item.id:
|
|
261
553
|
error = "Workbook item missing ID."
|
|
262
554
|
raise MissingRequiredFieldError(error)
|
|
@@ -265,10 +557,10 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
265
557
|
return self._get_wb_pptx(workbook_item, req_options)
|
|
266
558
|
|
|
267
559
|
workbook_item._set_powerpoint(pptx_fetcher)
|
|
268
|
-
logger.info("Populated powerpoint for workbook (ID: {
|
|
560
|
+
logger.info(f"Populated powerpoint for workbook (ID: {workbook_item.id})")
|
|
269
561
|
|
|
270
562
|
def _get_wb_pptx(self, workbook_item: WorkbookItem, req_options: Optional["RequestOptions"]) -> bytes:
|
|
271
|
-
url = "{
|
|
563
|
+
url = f"{self.baseurl}/{workbook_item.id}/powerpoint"
|
|
272
564
|
server_response = self.get_request(url, req_options)
|
|
273
565
|
pptx = server_response.content
|
|
274
566
|
return pptx
|
|
@@ -276,6 +568,26 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
276
568
|
# Get preview image of workbook
|
|
277
569
|
@api(version="2.0")
|
|
278
570
|
def populate_preview_image(self, workbook_item: WorkbookItem) -> None:
|
|
571
|
+
"""
|
|
572
|
+
This method gets the preview image (thumbnail) for the specified workbook item.
|
|
573
|
+
|
|
574
|
+
This method uses the workbook's ID to get the preview image. The method
|
|
575
|
+
adds the preview image to the workbook item (workbook_item.preview_image).
|
|
576
|
+
|
|
577
|
+
Parameters
|
|
578
|
+
----------
|
|
579
|
+
workbook_item : WorkbookItem
|
|
580
|
+
The workbook item to populate the preview image for.
|
|
581
|
+
|
|
582
|
+
Returns
|
|
583
|
+
-------
|
|
584
|
+
None
|
|
585
|
+
|
|
586
|
+
Raises
|
|
587
|
+
------
|
|
588
|
+
MissingRequiredFieldError
|
|
589
|
+
If the workbook item is missing an ID.
|
|
590
|
+
"""
|
|
279
591
|
if not workbook_item.id:
|
|
280
592
|
error = "Workbook item missing ID. Workbook must be retrieved from server first."
|
|
281
593
|
raise MissingRequiredFieldError(error)
|
|
@@ -284,24 +596,75 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
284
596
|
return self._get_wb_preview_image(workbook_item)
|
|
285
597
|
|
|
286
598
|
workbook_item._set_preview_image(image_fetcher)
|
|
287
|
-
logger.info("Populated preview image for workbook (ID: {
|
|
599
|
+
logger.info(f"Populated preview image for workbook (ID: {workbook_item.id})")
|
|
288
600
|
|
|
289
601
|
def _get_wb_preview_image(self, workbook_item: WorkbookItem) -> bytes:
|
|
290
|
-
url = "{
|
|
602
|
+
url = f"{self.baseurl}/{workbook_item.id}/previewImage"
|
|
291
603
|
server_response = self.get_request(url)
|
|
292
604
|
preview_image = server_response.content
|
|
293
605
|
return preview_image
|
|
294
606
|
|
|
295
607
|
@api(version="2.0")
|
|
296
608
|
def populate_permissions(self, item: WorkbookItem) -> None:
|
|
609
|
+
"""
|
|
610
|
+
Populates the permissions for the specified workbook item.
|
|
611
|
+
|
|
612
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_permissions.htm#query_workbook_permissions
|
|
613
|
+
|
|
614
|
+
Parameters
|
|
615
|
+
----------
|
|
616
|
+
item : WorkbookItem
|
|
617
|
+
The workbook item to populate permissions for.
|
|
618
|
+
|
|
619
|
+
Returns
|
|
620
|
+
-------
|
|
621
|
+
None
|
|
622
|
+
"""
|
|
297
623
|
self._permissions.populate(item)
|
|
298
624
|
|
|
299
625
|
@api(version="2.0")
|
|
300
|
-
def update_permissions(self, resource, rules):
|
|
626
|
+
def update_permissions(self, resource: WorkbookItem, rules: list[PermissionsRule]) -> list[PermissionsRule]:
|
|
627
|
+
"""
|
|
628
|
+
Updates the permissions for the specified workbook item. The method
|
|
629
|
+
replaces the existing permissions with the new permissions. Any missing
|
|
630
|
+
permissions are removed.
|
|
631
|
+
|
|
632
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_permissions.htm#replace_permissions_for_content
|
|
633
|
+
|
|
634
|
+
Parameters
|
|
635
|
+
----------
|
|
636
|
+
resource : WorkbookItem
|
|
637
|
+
The workbook item to update permissions for.
|
|
638
|
+
|
|
639
|
+
rules : list[PermissionsRule]
|
|
640
|
+
A list of permissions rules to apply to the workbook item.
|
|
641
|
+
|
|
642
|
+
Returns
|
|
643
|
+
-------
|
|
644
|
+
list[PermissionsRule]
|
|
645
|
+
The updated permissions rules.
|
|
646
|
+
"""
|
|
301
647
|
return self._permissions.update(resource, rules)
|
|
302
648
|
|
|
303
649
|
@api(version="2.0")
|
|
304
|
-
def delete_permission(self, item, capability_item):
|
|
650
|
+
def delete_permission(self, item: WorkbookItem, capability_item: PermissionsRule) -> None:
|
|
651
|
+
"""
|
|
652
|
+
Deletes a single permission rule from the specified workbook item.
|
|
653
|
+
|
|
654
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_permissions.htm#delete_workbook_permission
|
|
655
|
+
|
|
656
|
+
Parameters
|
|
657
|
+
----------
|
|
658
|
+
item : WorkbookItem
|
|
659
|
+
The workbook item to delete the permission from.
|
|
660
|
+
|
|
661
|
+
capability_item : PermissionsRule
|
|
662
|
+
The permission rule to delete.
|
|
663
|
+
|
|
664
|
+
Returns
|
|
665
|
+
-------
|
|
666
|
+
None
|
|
667
|
+
"""
|
|
305
668
|
return self._permissions.delete(item, capability_item)
|
|
306
669
|
|
|
307
670
|
@api(version="2.0")
|
|
@@ -317,10 +680,87 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
317
680
|
skip_connection_check: bool = False,
|
|
318
681
|
parameters=None,
|
|
319
682
|
):
|
|
683
|
+
"""
|
|
684
|
+
Publish a workbook to the specified site.
|
|
685
|
+
|
|
686
|
+
Note: The REST API cannot automatically include extracts or other
|
|
687
|
+
resources that the workbook uses. Therefore, a .twb file that uses data
|
|
688
|
+
from an Excel or csv file on a local computer cannot be published,
|
|
689
|
+
unless you package the data and workbook in a .twbx file, or publish the
|
|
690
|
+
data source separately.
|
|
691
|
+
|
|
692
|
+
For workbooks that are larger than 64 MB, the publish method
|
|
693
|
+
automatically takes care of chunking the file in parts for uploading.
|
|
694
|
+
Using this method is considerably more convenient than calling the
|
|
695
|
+
publish REST APIs directly.
|
|
696
|
+
|
|
697
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_workbooks_and_views.htm#publish_workbook
|
|
698
|
+
|
|
699
|
+
Parameters
|
|
700
|
+
----------
|
|
701
|
+
workbook_item : WorkbookItem
|
|
702
|
+
The workbook_item specifies the workbook you are publishing. When
|
|
703
|
+
you are adding a workbook, you need to first create a new instance
|
|
704
|
+
of a workbook_item that includes a project_id of an existing
|
|
705
|
+
project. The name of the workbook will be the name of the file,
|
|
706
|
+
unless you also specify a name for the new workbook when you create
|
|
707
|
+
the instance.
|
|
708
|
+
|
|
709
|
+
file : Path or File object
|
|
710
|
+
The file path or file object of the workbook to publish. When
|
|
711
|
+
providing a file object, you must also specifiy the name of the
|
|
712
|
+
workbook in your instance of the workbook_itemworkbook_item , as
|
|
713
|
+
the name cannot be derived from the file name.
|
|
714
|
+
|
|
715
|
+
mode : str
|
|
716
|
+
Specifies whether you are publishing a new workbook (CreateNew) or
|
|
717
|
+
overwriting an existing workbook (Overwrite). You cannot appending
|
|
718
|
+
workbooks. You can also use the publish mode attributes, for
|
|
719
|
+
example: TSC.Server.PublishMode.Overwrite.
|
|
720
|
+
|
|
721
|
+
connections : list[ConnectionItem] | None
|
|
722
|
+
List of ConnectionItems objects for the connections created within
|
|
723
|
+
the workbook.
|
|
724
|
+
|
|
725
|
+
as_job : bool, default False
|
|
726
|
+
Set to True to run the upload as a job (asynchronous upload). If set
|
|
727
|
+
to True a job will start to perform the publishing process and a Job
|
|
728
|
+
object is returned. Defaults to False.
|
|
729
|
+
|
|
730
|
+
skip_connection_check : bool, default False
|
|
731
|
+
Set to True to skip connection check at time of upload. Publishing
|
|
732
|
+
will succeed but unchecked connection issues may result in a
|
|
733
|
+
non-functioning workbook. Defaults to False.
|
|
734
|
+
|
|
735
|
+
Raises
|
|
736
|
+
------
|
|
737
|
+
OSError
|
|
738
|
+
If the file path does not lead to an existing file.
|
|
739
|
+
|
|
740
|
+
ServerResponseError
|
|
741
|
+
If the server response is not successful.
|
|
742
|
+
|
|
743
|
+
TypeError
|
|
744
|
+
If the file is not a file path or file object.
|
|
745
|
+
|
|
746
|
+
ValueError
|
|
747
|
+
If the file extension is not supported
|
|
748
|
+
|
|
749
|
+
ValueError
|
|
750
|
+
If the mode is invalid.
|
|
751
|
+
|
|
752
|
+
ValueError
|
|
753
|
+
Workbooks cannot be appended.
|
|
754
|
+
|
|
755
|
+
Returns
|
|
756
|
+
-------
|
|
757
|
+
WorkbookItem | JobItem
|
|
758
|
+
The workbook item or job item that was published.
|
|
759
|
+
"""
|
|
320
760
|
if isinstance(file, (str, os.PathLike)):
|
|
321
761
|
if not os.path.isfile(file):
|
|
322
762
|
error = "File path does not lead to an existing file."
|
|
323
|
-
raise
|
|
763
|
+
raise OSError(error)
|
|
324
764
|
|
|
325
765
|
filename = os.path.basename(file)
|
|
326
766
|
file_extension = os.path.splitext(filename)[1][1:]
|
|
@@ -344,12 +784,12 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
344
784
|
elif file_type == "xml":
|
|
345
785
|
file_extension = "twb"
|
|
346
786
|
else:
|
|
347
|
-
error = "Unsupported file type {}!"
|
|
787
|
+
error = f"Unsupported file type {file_type}!"
|
|
348
788
|
raise ValueError(error)
|
|
349
789
|
|
|
350
790
|
# Generate filename for file object.
|
|
351
791
|
# This is needed when publishing the workbook in a single request
|
|
352
|
-
filename = "{}.{}"
|
|
792
|
+
filename = f"{workbook_item.name}.{file_extension}"
|
|
353
793
|
file_size = get_file_object_size(file)
|
|
354
794
|
|
|
355
795
|
else:
|
|
@@ -360,30 +800,30 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
360
800
|
raise ValueError(error)
|
|
361
801
|
|
|
362
802
|
# Construct the url with the defined mode
|
|
363
|
-
url = "{
|
|
803
|
+
url = f"{self.baseurl}?workbookType={file_extension}"
|
|
364
804
|
if mode == self.parent_srv.PublishMode.Overwrite:
|
|
365
|
-
url += "&{
|
|
805
|
+
url += f"&{mode.lower()}=true"
|
|
366
806
|
elif mode == self.parent_srv.PublishMode.Append:
|
|
367
807
|
error = "Workbooks cannot be appended."
|
|
368
808
|
raise ValueError(error)
|
|
369
809
|
|
|
370
810
|
if as_job:
|
|
371
|
-
url += "&{
|
|
811
|
+
url += "&{}=true".format("asJob")
|
|
372
812
|
|
|
373
813
|
if skip_connection_check:
|
|
374
|
-
url += "&{
|
|
814
|
+
url += "&{}=true".format("skipConnectionCheck")
|
|
375
815
|
|
|
376
816
|
# Determine if chunking is required (64MB is the limit for single upload method)
|
|
377
817
|
if file_size >= FILESIZE_LIMIT:
|
|
378
|
-
logger.info("Publishing {
|
|
818
|
+
logger.info(f"Publishing {workbook_item.name} to server with chunking method (workbook over 64MB)")
|
|
379
819
|
upload_session_id = self.parent_srv.fileuploads.upload(file)
|
|
380
|
-
url = "{
|
|
820
|
+
url = f"{url}&uploadSessionId={upload_session_id}"
|
|
381
821
|
xml_request, content_type = RequestFactory.Workbook.publish_req_chunked(
|
|
382
822
|
workbook_item,
|
|
383
823
|
connections=connections,
|
|
384
824
|
)
|
|
385
825
|
else:
|
|
386
|
-
logger.info("Publishing {
|
|
826
|
+
logger.info(f"Publishing {filename} to server")
|
|
387
827
|
|
|
388
828
|
if isinstance(file, (str, Path)):
|
|
389
829
|
with open(file, "rb") as f:
|
|
@@ -401,7 +841,7 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
401
841
|
file_contents,
|
|
402
842
|
connections=connections,
|
|
403
843
|
)
|
|
404
|
-
logger.debug("Request xml: {
|
|
844
|
+
logger.debug(f"Request xml: {redact_xml(xml_request[:1000])} ")
|
|
405
845
|
|
|
406
846
|
# Send the publishing request to server
|
|
407
847
|
try:
|
|
@@ -413,16 +853,38 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
413
853
|
|
|
414
854
|
if as_job:
|
|
415
855
|
new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
416
|
-
logger.info("Published {
|
|
856
|
+
logger.info(f"Published {workbook_item.name} (JOB_ID: {new_job.id}")
|
|
417
857
|
return new_job
|
|
418
858
|
else:
|
|
419
859
|
new_workbook = WorkbookItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
420
|
-
logger.info("Published {
|
|
860
|
+
logger.info(f"Published {workbook_item.name} (ID: {new_workbook.id})")
|
|
421
861
|
return new_workbook
|
|
422
862
|
|
|
423
863
|
# Populate workbook item's revisions
|
|
424
864
|
@api(version="2.3")
|
|
425
865
|
def populate_revisions(self, workbook_item: WorkbookItem) -> None:
|
|
866
|
+
"""
|
|
867
|
+
Populates (or gets) a list of revisions for a workbook.
|
|
868
|
+
|
|
869
|
+
You must first call this method to populate revisions before you can
|
|
870
|
+
iterate through the revisions.
|
|
871
|
+
|
|
872
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_workbooks_and_views.htm#get_workbook_revisions
|
|
873
|
+
|
|
874
|
+
Parameters
|
|
875
|
+
----------
|
|
876
|
+
workbook_item : WorkbookItem
|
|
877
|
+
The workbook item to populate revisions for.
|
|
878
|
+
|
|
879
|
+
Returns
|
|
880
|
+
-------
|
|
881
|
+
None
|
|
882
|
+
|
|
883
|
+
Raises
|
|
884
|
+
------
|
|
885
|
+
MissingRequiredFieldError
|
|
886
|
+
If the workbook item is missing an ID.
|
|
887
|
+
"""
|
|
426
888
|
if not workbook_item.id:
|
|
427
889
|
error = "Workbook item missing ID. Workbook must be retrieved from server first."
|
|
428
890
|
raise MissingRequiredFieldError(error)
|
|
@@ -431,12 +893,12 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
431
893
|
return self._get_workbook_revisions(workbook_item)
|
|
432
894
|
|
|
433
895
|
workbook_item._set_revisions(revisions_fetcher)
|
|
434
|
-
logger.info("Populated revisions for workbook (ID: {
|
|
896
|
+
logger.info(f"Populated revisions for workbook (ID: {workbook_item.id})")
|
|
435
897
|
|
|
436
898
|
def _get_workbook_revisions(
|
|
437
899
|
self, workbook_item: WorkbookItem, req_options: Optional["RequestOptions"] = None
|
|
438
|
-
) ->
|
|
439
|
-
url = "{
|
|
900
|
+
) -> list[RevisionItem]:
|
|
901
|
+
url = f"{self.baseurl}/{workbook_item.id}/revisions"
|
|
440
902
|
server_response = self.get_request(url, req_options)
|
|
441
903
|
revisions = RevisionItem.from_response(server_response.content, self.parent_srv.namespace, workbook_item)
|
|
442
904
|
return revisions
|
|
@@ -450,13 +912,47 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
450
912
|
filepath: Optional[PathOrFileW] = None,
|
|
451
913
|
include_extract: bool = True,
|
|
452
914
|
) -> PathOrFileW:
|
|
915
|
+
"""
|
|
916
|
+
Downloads a workbook revision to the specified directory (optional).
|
|
917
|
+
|
|
918
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_workbooks_and_views.htm#download_workbook_revision
|
|
919
|
+
|
|
920
|
+
Parameters
|
|
921
|
+
----------
|
|
922
|
+
workbook_id : str
|
|
923
|
+
The workbook ID.
|
|
924
|
+
|
|
925
|
+
revision_number : str | None
|
|
926
|
+
The revision number of the workbook. If None, the latest revision is
|
|
927
|
+
downloaded.
|
|
928
|
+
|
|
929
|
+
filepath : Path or File object, optional
|
|
930
|
+
Downloads the file to the location you specify. If no location is
|
|
931
|
+
specified, the file is downloaded to the current working directory.
|
|
932
|
+
The default is Filepath=None.
|
|
933
|
+
|
|
934
|
+
include_extract : bool, default True
|
|
935
|
+
Set to False to exclude the extract from the download. The default
|
|
936
|
+
is True.
|
|
937
|
+
|
|
938
|
+
Returns
|
|
939
|
+
-------
|
|
940
|
+
Path or File object
|
|
941
|
+
The path to the downloaded workbook or the file object.
|
|
942
|
+
|
|
943
|
+
Raises
|
|
944
|
+
------
|
|
945
|
+
ValueError
|
|
946
|
+
If the workbook ID is not defined.
|
|
947
|
+
"""
|
|
948
|
+
|
|
453
949
|
if not workbook_id:
|
|
454
950
|
error = "Workbook ID undefined."
|
|
455
951
|
raise ValueError(error)
|
|
456
952
|
if revision_number is None:
|
|
457
|
-
url = "{
|
|
953
|
+
url = f"{self.baseurl}/{workbook_id}/content"
|
|
458
954
|
else:
|
|
459
|
-
url = "{
|
|
955
|
+
url = f"{self.baseurl}/{workbook_id}/revisions/{revision_number}/content"
|
|
460
956
|
|
|
461
957
|
if not include_extract:
|
|
462
958
|
url += "?includeExtract=False"
|
|
@@ -478,23 +974,194 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
478
974
|
f.write(chunk)
|
|
479
975
|
return_path = os.path.abspath(download_path)
|
|
480
976
|
|
|
481
|
-
logger.info(
|
|
482
|
-
"Downloaded workbook revision {0} to {1} (ID: {2})".format(revision_number, return_path, workbook_id)
|
|
483
|
-
)
|
|
977
|
+
logger.info(f"Downloaded workbook revision {revision_number} to {return_path} (ID: {workbook_id})")
|
|
484
978
|
return return_path
|
|
485
979
|
|
|
486
980
|
@api(version="2.3")
|
|
487
981
|
def delete_revision(self, workbook_id: str, revision_number: str) -> None:
|
|
982
|
+
"""
|
|
983
|
+
Deletes a specific revision from a workbook on Tableau Server.
|
|
984
|
+
|
|
985
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_revisions.htm#remove_workbook_revision
|
|
986
|
+
|
|
987
|
+
Parameters
|
|
988
|
+
----------
|
|
989
|
+
workbook_id : str
|
|
990
|
+
The workbook ID.
|
|
991
|
+
|
|
992
|
+
revision_number : str
|
|
993
|
+
The revision number of the workbook to delete.
|
|
994
|
+
|
|
995
|
+
Returns
|
|
996
|
+
-------
|
|
997
|
+
None
|
|
998
|
+
|
|
999
|
+
Raises
|
|
1000
|
+
------
|
|
1001
|
+
ValueError
|
|
1002
|
+
If the workbook ID or revision number is not defined.
|
|
1003
|
+
"""
|
|
488
1004
|
if workbook_id is None or revision_number is None:
|
|
489
1005
|
raise ValueError
|
|
490
1006
|
url = "/".join([self.baseurl, workbook_id, "revisions", revision_number])
|
|
491
1007
|
|
|
492
1008
|
self.delete_request(url)
|
|
493
|
-
logger.info("Deleted single workbook revision (ID: {
|
|
1009
|
+
logger.info(f"Deleted single workbook revision (ID: {workbook_id}) (Revision: {revision_number})")
|
|
494
1010
|
|
|
495
1011
|
# a convenience method
|
|
496
1012
|
@api(version="2.8")
|
|
497
1013
|
def schedule_extract_refresh(
|
|
498
1014
|
self, schedule_id: str, item: WorkbookItem
|
|
499
|
-
) ->
|
|
1015
|
+
) -> list["AddResponse"]: # actually should return a task
|
|
1016
|
+
"""
|
|
1017
|
+
Adds a workbook to a schedule for extract refresh.
|
|
1018
|
+
|
|
1019
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_jobs_tasks_and_schedules.htm#add_workbook_to_schedule
|
|
1020
|
+
|
|
1021
|
+
Parameters
|
|
1022
|
+
----------
|
|
1023
|
+
schedule_id : str
|
|
1024
|
+
The schedule ID.
|
|
1025
|
+
|
|
1026
|
+
item : WorkbookItem
|
|
1027
|
+
The workbook item to add to the schedule.
|
|
1028
|
+
|
|
1029
|
+
Returns
|
|
1030
|
+
-------
|
|
1031
|
+
list[AddResponse]
|
|
1032
|
+
The response from the server.
|
|
1033
|
+
"""
|
|
500
1034
|
return self.parent_srv.schedules.add_to_schedule(schedule_id, workbook=item)
|
|
1035
|
+
|
|
1036
|
+
@api(version="1.0")
|
|
1037
|
+
def add_tags(self, item: Union[WorkbookItem, str], tags: Union[Iterable[str], str]) -> set[str]:
|
|
1038
|
+
"""
|
|
1039
|
+
Adds tags to a workbook. One or more tags may be added at a time. If a
|
|
1040
|
+
tag already exists on the workbook, it will not be duplicated.
|
|
1041
|
+
|
|
1042
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_workbooks_and_views.htm#add_tags_to_workbook
|
|
1043
|
+
|
|
1044
|
+
Parameters
|
|
1045
|
+
----------
|
|
1046
|
+
item : WorkbookItem | str
|
|
1047
|
+
The workbook item or workbook ID to add tags to.
|
|
1048
|
+
|
|
1049
|
+
tags : Iterable[str] | str
|
|
1050
|
+
The tag or tags to add to the workbook. Tags can be a single tag or
|
|
1051
|
+
a list of tags.
|
|
1052
|
+
|
|
1053
|
+
Returns
|
|
1054
|
+
-------
|
|
1055
|
+
set[str]
|
|
1056
|
+
The set of tags added to the workbook.
|
|
1057
|
+
"""
|
|
1058
|
+
return super().add_tags(item, tags)
|
|
1059
|
+
|
|
1060
|
+
@api(version="1.0")
|
|
1061
|
+
def delete_tags(self, item: Union[WorkbookItem, str], tags: Union[Iterable[str], str]) -> None:
|
|
1062
|
+
"""
|
|
1063
|
+
Deletes tags from a workbook. One or more tags may be deleted at a time.
|
|
1064
|
+
|
|
1065
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_workbooks_and_views.htm#delete_tag_from_workbook
|
|
1066
|
+
|
|
1067
|
+
Parameters
|
|
1068
|
+
----------
|
|
1069
|
+
item : WorkbookItem | str
|
|
1070
|
+
The workbook item or workbook ID to delete tags from.
|
|
1071
|
+
|
|
1072
|
+
tags : Iterable[str] | str
|
|
1073
|
+
The tag or tags to delete from the workbook. Tags can be a single
|
|
1074
|
+
tag or a list of tags.
|
|
1075
|
+
|
|
1076
|
+
Returns
|
|
1077
|
+
-------
|
|
1078
|
+
None
|
|
1079
|
+
"""
|
|
1080
|
+
return super().delete_tags(item, tags)
|
|
1081
|
+
|
|
1082
|
+
@api(version="1.0")
|
|
1083
|
+
def update_tags(self, item: WorkbookItem) -> None:
|
|
1084
|
+
"""
|
|
1085
|
+
Updates the tags on a workbook. This method is used to update the tags
|
|
1086
|
+
on the server to match the tags on the workbook item. This method is a
|
|
1087
|
+
convenience method that calls add_tags and delete_tags to update the
|
|
1088
|
+
tags on the server.
|
|
1089
|
+
|
|
1090
|
+
Parameters
|
|
1091
|
+
----------
|
|
1092
|
+
item : WorkbookItem
|
|
1093
|
+
The workbook item to update the tags for. The tags on the workbook
|
|
1094
|
+
item will be used to update the tags on the server.
|
|
1095
|
+
|
|
1096
|
+
Returns
|
|
1097
|
+
-------
|
|
1098
|
+
None
|
|
1099
|
+
"""
|
|
1100
|
+
return super().update_tags(item)
|
|
1101
|
+
|
|
1102
|
+
def filter(self, *invalid, page_size: Optional[int] = None, **kwargs) -> QuerySet[WorkbookItem]:
|
|
1103
|
+
"""
|
|
1104
|
+
Queries the Tableau Server for items using the specified filters. Page
|
|
1105
|
+
size can be specified to limit the number of items returned in a single
|
|
1106
|
+
request. If not specified, the default page size is 100. Page size can
|
|
1107
|
+
be an integer between 1 and 1000.
|
|
1108
|
+
|
|
1109
|
+
No positional arguments are allowed. All filters must be specified as
|
|
1110
|
+
keyword arguments. If you use the equality operator, you can specify it
|
|
1111
|
+
through <field_name>=<value>. If you want to use a different operator,
|
|
1112
|
+
you can specify it through <field_name>__<operator>=<value>. Field
|
|
1113
|
+
names can either be in snake_case or camelCase.
|
|
1114
|
+
|
|
1115
|
+
This endpoint supports the following fields and operators:
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
created_at=...
|
|
1119
|
+
created_at__gt=...
|
|
1120
|
+
created_at__gte=...
|
|
1121
|
+
created_at__lt=...
|
|
1122
|
+
created_at__lte=...
|
|
1123
|
+
content_url=...
|
|
1124
|
+
content_url__in=...
|
|
1125
|
+
display_tabs=...
|
|
1126
|
+
favorites_total=...
|
|
1127
|
+
favorites_total__gt=...
|
|
1128
|
+
favorites_total__gte=...
|
|
1129
|
+
favorites_total__lt=...
|
|
1130
|
+
favorites_total__lte=...
|
|
1131
|
+
has_alerts=...
|
|
1132
|
+
has_extracts=...
|
|
1133
|
+
name=...
|
|
1134
|
+
name__in=...
|
|
1135
|
+
owner_domain=...
|
|
1136
|
+
owner_domain__in=...
|
|
1137
|
+
owner_email=...
|
|
1138
|
+
owner_email__in=...
|
|
1139
|
+
owner_name=...
|
|
1140
|
+
owner_name__in=...
|
|
1141
|
+
project_name=...
|
|
1142
|
+
project_name__in=...
|
|
1143
|
+
sheet_count=...
|
|
1144
|
+
sheet_count__gt=...
|
|
1145
|
+
sheet_count__gte=...
|
|
1146
|
+
sheet_count__lt=...
|
|
1147
|
+
sheet_count__lte=...
|
|
1148
|
+
size=...
|
|
1149
|
+
size__gt=...
|
|
1150
|
+
size__gte=...
|
|
1151
|
+
size__lt=...
|
|
1152
|
+
size__lte=...
|
|
1153
|
+
subscriptions_total=...
|
|
1154
|
+
subscriptions_total__gt=...
|
|
1155
|
+
subscriptions_total__gte=...
|
|
1156
|
+
subscriptions_total__lt=...
|
|
1157
|
+
subscriptions_total__lte=...
|
|
1158
|
+
tags=...
|
|
1159
|
+
tags__in=...
|
|
1160
|
+
updated_at=...
|
|
1161
|
+
updated_at__gt=...
|
|
1162
|
+
updated_at__gte=...
|
|
1163
|
+
updated_at__lt=...
|
|
1164
|
+
updated_at__lte=...
|
|
1165
|
+
"""
|
|
1166
|
+
|
|
1167
|
+
return super().filter(*invalid, page_size=page_size, **kwargs)
|