tableauserverclient 0.37__py3-none-any.whl → 0.39__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/bin/_version.py → _version.py +3 -3
- bin/__init__.py +3 -0
- bin/_version.py +21 -0
- {tableauserverclient/helpers → helpers}/strings.py +25 -1
- {tableauserverclient/models → models}/__init__.py +15 -1
- models/collection_item.py +52 -0
- {tableauserverclient/models → models}/connection_item.py +16 -2
- {tableauserverclient/models → models}/custom_view_item.py +8 -0
- {tableauserverclient/models → models}/data_freshness_policy_item.py +3 -3
- {tableauserverclient/models → models}/datasource_item.py +113 -3
- models/extensions_item.py +186 -0
- models/extract_item.py +82 -0
- {tableauserverclient/models → models}/favorites_item.py +21 -8
- {tableauserverclient/models → models}/flow_item.py +3 -3
- {tableauserverclient/models → models}/group_item.py +18 -1
- {tableauserverclient/models → models}/groupset_item.py +14 -0
- {tableauserverclient/models → models}/interval_item.py +42 -1
- models/location_item.py +53 -0
- models/oidc_item.py +82 -0
- {tableauserverclient/models → models}/permissions_item.py +2 -0
- {tableauserverclient/models → models}/project_item.py +141 -29
- {tableauserverclient/models → models}/property_decorators.py +2 -2
- {tableauserverclient/models → models}/reference_item.py +12 -6
- {tableauserverclient/models → models}/schedule_item.py +67 -1
- {tableauserverclient/models → models}/site_item.py +54 -0
- {tableauserverclient/models → models}/table_item.py +7 -3
- {tableauserverclient/models → models}/tableau_auth.py +13 -6
- {tableauserverclient/models → models}/tableau_types.py +13 -1
- {tableauserverclient/models → models}/user_item.py +111 -4
- {tableauserverclient/models → models}/view_item.py +79 -5
- {tableauserverclient/models → models}/workbook_item.py +153 -3
- {tableauserverclient/server → server}/endpoint/__init__.py +4 -0
- {tableauserverclient/server → server}/endpoint/databases_endpoint.py +101 -18
- {tableauserverclient/server → server}/endpoint/datasources_endpoint.py +155 -25
- {tableauserverclient/server → server}/endpoint/dqw_endpoint.py +16 -6
- {tableauserverclient/server → server}/endpoint/endpoint.py +39 -0
- server/endpoint/extensions_endpoint.py +79 -0
- {tableauserverclient/server → server}/endpoint/flow_task_endpoint.py +1 -1
- {tableauserverclient/server → server}/endpoint/flows_endpoint.py +5 -4
- server/endpoint/oidc_endpoint.py +157 -0
- {tableauserverclient/server → server}/endpoint/projects_endpoint.py +12 -0
- server/endpoint/schedules_endpoint.py +328 -0
- {tableauserverclient/server → server}/endpoint/sites_endpoint.py +18 -1
- {tableauserverclient/server → server}/endpoint/tables_endpoint.py +140 -17
- {tableauserverclient/server → server}/endpoint/users_endpoint.py +296 -10
- {tableauserverclient/server → server}/endpoint/views_endpoint.py +23 -0
- {tableauserverclient/server → server}/endpoint/workbooks_endpoint.py +124 -9
- {tableauserverclient/server → server}/query.py +36 -0
- {tableauserverclient/server → server}/request_factory.py +286 -2
- {tableauserverclient/server → server}/request_options.py +139 -3
- {tableauserverclient/server → server}/server.py +46 -0
- {tableauserverclient-0.37.dist-info → tableauserverclient-0.39.dist-info}/METADATA +5 -26
- tableauserverclient-0.39.dist-info/RECORD +107 -0
- {tableauserverclient-0.37.dist-info → tableauserverclient-0.39.dist-info}/WHEEL +1 -1
- tableauserverclient-0.39.dist-info/top_level.txt +4 -0
- tableauserverclient/__init__.py +0 -141
- tableauserverclient/config.py +0 -27
- tableauserverclient/datetime_helpers.py +0 -45
- tableauserverclient/exponential_backoff.py +0 -30
- tableauserverclient/filesys_helpers.py +0 -63
- tableauserverclient/namespace.py +0 -37
- tableauserverclient/py.typed +0 -0
- tableauserverclient/server/endpoint/schedules_endpoint.py +0 -151
- tableauserverclient-0.37.dist-info/RECORD +0 -106
- tableauserverclient-0.37.dist-info/licenses/LICENSE.versioneer +0 -7
- tableauserverclient-0.37.dist-info/top_level.txt +0 -1
- {tableauserverclient/helpers → helpers}/__init__.py +0 -0
- {tableauserverclient/helpers → helpers}/headers.py +0 -0
- {tableauserverclient/helpers → helpers}/logging.py +0 -0
- {tableauserverclient/models → models}/column_item.py +0 -0
- {tableauserverclient/models → models}/connection_credentials.py +0 -0
- {tableauserverclient/models → models}/data_acceleration_report_item.py +0 -0
- {tableauserverclient/models → models}/data_alert_item.py +0 -0
- {tableauserverclient/models → models}/database_item.py +0 -0
- {tableauserverclient/models → models}/dqw_item.py +0 -0
- {tableauserverclient/models → models}/exceptions.py +0 -0
- {tableauserverclient/models → models}/fileupload_item.py +0 -0
- {tableauserverclient/models → models}/flow_run_item.py +0 -0
- {tableauserverclient/models → models}/job_item.py +0 -0
- {tableauserverclient/models → models}/linked_tasks_item.py +0 -0
- {tableauserverclient/models → models}/metric_item.py +0 -0
- {tableauserverclient/models → models}/pagination_item.py +0 -0
- {tableauserverclient/models → models}/revision_item.py +0 -0
- {tableauserverclient/models → models}/server_info_item.py +0 -0
- {tableauserverclient/models → models}/subscription_item.py +0 -0
- {tableauserverclient/models → models}/tag_item.py +0 -0
- {tableauserverclient/models → models}/target.py +0 -0
- {tableauserverclient/models → models}/task_item.py +0 -0
- {tableauserverclient/models → models}/virtual_connection_item.py +0 -0
- {tableauserverclient/models → models}/webhook_item.py +0 -0
- {tableauserverclient/server → server}/__init__.py +0 -0
- {tableauserverclient/server → server}/endpoint/auth_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/custom_views_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/data_acceleration_report_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/data_alert_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/default_permissions_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/exceptions.py +0 -0
- {tableauserverclient/server → server}/endpoint/favorites_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/fileuploads_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/flow_runs_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/groups_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/groupsets_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/jobs_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/linked_tasks_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/metadata_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/metrics_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/permissions_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/resource_tagger.py +0 -0
- {tableauserverclient/server → server}/endpoint/server_info_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/subscriptions_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/tasks_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/virtual_connections_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/webhooks_endpoint.py +0 -0
- {tableauserverclient/server → server}/exceptions.py +0 -0
- {tableauserverclient/server → server}/filter.py +0 -0
- {tableauserverclient/server → server}/pager.py +0 -0
- {tableauserverclient/server → server}/sort.py +0 -0
- {tableauserverclient-0.37.dist-info → tableauserverclient-0.39.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import Union
|
|
2
|
+
from typing import Optional, Union, TYPE_CHECKING
|
|
3
3
|
from collections.abc import Iterable
|
|
4
4
|
|
|
5
|
+
from tableauserverclient.models.permissions_item import PermissionsRule
|
|
5
6
|
from tableauserverclient.server.endpoint.dqw_endpoint import _DataQualityWarningEndpoint
|
|
6
7
|
from tableauserverclient.server.endpoint.endpoint import api, Endpoint
|
|
7
8
|
from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError
|
|
@@ -12,6 +13,10 @@ from tableauserverclient.models import TableItem, ColumnItem, PaginationItem
|
|
|
12
13
|
from tableauserverclient.server.pager import Pager
|
|
13
14
|
|
|
14
15
|
from tableauserverclient.helpers.logging import logger
|
|
16
|
+
from tableauserverclient.server.request_options import RequestOptions
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from tableauserverclient.models import DQWItem, PermissionsRule
|
|
15
20
|
|
|
16
21
|
|
|
17
22
|
class Tables(Endpoint, TaggingMixin[TableItem]):
|
|
@@ -22,11 +27,29 @@ class Tables(Endpoint, TaggingMixin[TableItem]):
|
|
|
22
27
|
self._data_quality_warnings = _DataQualityWarningEndpoint(self.parent_srv, "table")
|
|
23
28
|
|
|
24
29
|
@property
|
|
25
|
-
def baseurl(self):
|
|
30
|
+
def baseurl(self) -> str:
|
|
26
31
|
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/tables"
|
|
27
32
|
|
|
28
33
|
@api(version="3.5")
|
|
29
|
-
def get(self, req_options=None):
|
|
34
|
+
def get(self, req_options: Optional[RequestOptions] = None) -> tuple[list[TableItem], PaginationItem]:
|
|
35
|
+
"""
|
|
36
|
+
Get information about all tables on the site. Endpoint is paginated, and
|
|
37
|
+
will return a default of 100 items per page. Use the `req_options`
|
|
38
|
+
parameter to customize the request.
|
|
39
|
+
|
|
40
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_metadata.htm#query_tables
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
req_options : RequestOptions, optional
|
|
45
|
+
Options to customize the request. If not provided, defaults to None.
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
tuple[list[TableItem], PaginationItem]
|
|
50
|
+
A tuple containing a list of TableItem objects and a PaginationItem
|
|
51
|
+
object.
|
|
52
|
+
"""
|
|
30
53
|
logger.info("Querying all tables on site")
|
|
31
54
|
url = self.baseurl
|
|
32
55
|
server_response = self.get_request(url, req_options)
|
|
@@ -36,7 +59,27 @@ class Tables(Endpoint, TaggingMixin[TableItem]):
|
|
|
36
59
|
|
|
37
60
|
# Get 1 table
|
|
38
61
|
@api(version="3.5")
|
|
39
|
-
def get_by_id(self, table_id):
|
|
62
|
+
def get_by_id(self, table_id: str) -> TableItem:
|
|
63
|
+
"""
|
|
64
|
+
Get information about a single table on the site.
|
|
65
|
+
|
|
66
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_metadata.htm#query_table
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
table_id : str
|
|
71
|
+
The ID of the table to retrieve.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
TableItem
|
|
76
|
+
A TableItem object representing the table.
|
|
77
|
+
|
|
78
|
+
Raises
|
|
79
|
+
------
|
|
80
|
+
ValueError
|
|
81
|
+
If the table ID is not provided.
|
|
82
|
+
"""
|
|
40
83
|
if not table_id:
|
|
41
84
|
error = "table ID undefined."
|
|
42
85
|
raise ValueError(error)
|
|
@@ -46,7 +89,24 @@ class Tables(Endpoint, TaggingMixin[TableItem]):
|
|
|
46
89
|
return TableItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
47
90
|
|
|
48
91
|
@api(version="3.5")
|
|
49
|
-
def delete(self, table_id):
|
|
92
|
+
def delete(self, table_id: str) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Delete a single table from the server.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
table_id : str
|
|
99
|
+
The ID of the table to delete.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
None
|
|
104
|
+
|
|
105
|
+
Raises
|
|
106
|
+
------
|
|
107
|
+
ValueError
|
|
108
|
+
If the table ID is not provided.
|
|
109
|
+
"""
|
|
50
110
|
if not table_id:
|
|
51
111
|
error = "Database ID undefined."
|
|
52
112
|
raise ValueError(error)
|
|
@@ -55,7 +115,27 @@ class Tables(Endpoint, TaggingMixin[TableItem]):
|
|
|
55
115
|
logger.info(f"Deleted single table (ID: {table_id})")
|
|
56
116
|
|
|
57
117
|
@api(version="3.5")
|
|
58
|
-
def update(self, table_item):
|
|
118
|
+
def update(self, table_item: TableItem) -> TableItem:
|
|
119
|
+
"""
|
|
120
|
+
Update a table on the server.
|
|
121
|
+
|
|
122
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_metadata.htm#update_table
|
|
123
|
+
|
|
124
|
+
Parameters
|
|
125
|
+
----------
|
|
126
|
+
table_item : TableItem
|
|
127
|
+
The TableItem object to update.
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
TableItem
|
|
132
|
+
The updated TableItem object.
|
|
133
|
+
|
|
134
|
+
Raises
|
|
135
|
+
------
|
|
136
|
+
MissingRequiredFieldError
|
|
137
|
+
If the table item is missing an ID.
|
|
138
|
+
"""
|
|
59
139
|
if not table_item.id:
|
|
60
140
|
error = "table item missing ID."
|
|
61
141
|
raise MissingRequiredFieldError(error)
|
|
@@ -69,21 +149,46 @@ class Tables(Endpoint, TaggingMixin[TableItem]):
|
|
|
69
149
|
|
|
70
150
|
# Get all columns of the table
|
|
71
151
|
@api(version="3.5")
|
|
72
|
-
def populate_columns(self, table_item, req_options=None):
|
|
152
|
+
def populate_columns(self, table_item: TableItem, req_options: Optional[RequestOptions] = None) -> None:
|
|
153
|
+
"""
|
|
154
|
+
Populate the columns of a table item. Sets a fetcher function to
|
|
155
|
+
retrieve the columns when needed.
|
|
156
|
+
|
|
157
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_metadata.htm#query_columns
|
|
158
|
+
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
table_item : TableItem
|
|
162
|
+
The TableItem object to populate columns for.
|
|
163
|
+
|
|
164
|
+
req_options : RequestOptions, optional
|
|
165
|
+
Options to customize the request. If not provided, defaults to None.
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
None
|
|
170
|
+
|
|
171
|
+
Raises
|
|
172
|
+
------
|
|
173
|
+
MissingRequiredFieldError
|
|
174
|
+
If the table item is missing an ID.
|
|
175
|
+
"""
|
|
73
176
|
if not table_item.id:
|
|
74
177
|
error = "Table item missing ID. table must be retrieved from server first."
|
|
75
178
|
raise MissingRequiredFieldError(error)
|
|
76
179
|
|
|
77
180
|
def column_fetcher():
|
|
78
181
|
return Pager(
|
|
79
|
-
lambda options: self._get_columns_for_table(table_item, options),
|
|
182
|
+
lambda options: self._get_columns_for_table(table_item, options), # type: ignore
|
|
80
183
|
req_options,
|
|
81
184
|
)
|
|
82
185
|
|
|
83
186
|
table_item._set_columns(column_fetcher)
|
|
84
187
|
logger.info(f"Populated columns for table (ID: {table_item.id}")
|
|
85
188
|
|
|
86
|
-
def _get_columns_for_table(
|
|
189
|
+
def _get_columns_for_table(
|
|
190
|
+
self, table_item: TableItem, req_options: Optional[RequestOptions] = None
|
|
191
|
+
) -> tuple[list[ColumnItem], PaginationItem]:
|
|
87
192
|
url = f"{self.baseurl}/{table_item.id}/columns"
|
|
88
193
|
server_response = self.get_request(url, req_options)
|
|
89
194
|
columns = ColumnItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
@@ -91,7 +196,25 @@ class Tables(Endpoint, TaggingMixin[TableItem]):
|
|
|
91
196
|
return columns, pagination_item
|
|
92
197
|
|
|
93
198
|
@api(version="3.5")
|
|
94
|
-
def update_column(self, table_item, column_item):
|
|
199
|
+
def update_column(self, table_item: TableItem, column_item: ColumnItem) -> ColumnItem:
|
|
200
|
+
"""
|
|
201
|
+
Update the description of a column in a table.
|
|
202
|
+
|
|
203
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_metadata.htm#update_column
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
table_item : TableItem
|
|
208
|
+
The TableItem object representing the table.
|
|
209
|
+
|
|
210
|
+
column_item : ColumnItem
|
|
211
|
+
The ColumnItem object representing the column to update.
|
|
212
|
+
|
|
213
|
+
Returns
|
|
214
|
+
-------
|
|
215
|
+
ColumnItem
|
|
216
|
+
The updated ColumnItem object.
|
|
217
|
+
"""
|
|
95
218
|
url = f"{self.baseurl}/{table_item.id}/columns/{column_item.id}"
|
|
96
219
|
update_req = RequestFactory.Column.update_req(column_item)
|
|
97
220
|
server_response = self.put_request(url, update_req)
|
|
@@ -101,31 +224,31 @@ class Tables(Endpoint, TaggingMixin[TableItem]):
|
|
|
101
224
|
return column
|
|
102
225
|
|
|
103
226
|
@api(version="3.5")
|
|
104
|
-
def populate_permissions(self, item):
|
|
227
|
+
def populate_permissions(self, item: TableItem) -> None:
|
|
105
228
|
self._permissions.populate(item)
|
|
106
229
|
|
|
107
230
|
@api(version="3.5")
|
|
108
|
-
def update_permissions(self, item, rules):
|
|
231
|
+
def update_permissions(self, item: TableItem, rules: list[PermissionsRule]) -> list[PermissionsRule]:
|
|
109
232
|
return self._permissions.update(item, rules)
|
|
110
233
|
|
|
111
234
|
@api(version="3.5")
|
|
112
|
-
def delete_permission(self, item, rules):
|
|
235
|
+
def delete_permission(self, item: TableItem, rules: list[PermissionsRule]) -> None:
|
|
113
236
|
return self._permissions.delete(item, rules)
|
|
114
237
|
|
|
115
238
|
@api(version="3.5")
|
|
116
|
-
def populate_dqw(self, item):
|
|
239
|
+
def populate_dqw(self, item: TableItem) -> None:
|
|
117
240
|
self._data_quality_warnings.populate(item)
|
|
118
241
|
|
|
119
242
|
@api(version="3.5")
|
|
120
|
-
def update_dqw(self, item, warning):
|
|
243
|
+
def update_dqw(self, item: TableItem, warning: "DQWItem") -> list["DQWItem"]:
|
|
121
244
|
return self._data_quality_warnings.update(item, warning)
|
|
122
245
|
|
|
123
246
|
@api(version="3.5")
|
|
124
|
-
def add_dqw(self, item, warning):
|
|
247
|
+
def add_dqw(self, item: TableItem, warning: "DQWItem") -> list["DQWItem"]:
|
|
125
248
|
return self._data_quality_warnings.add(item, warning)
|
|
126
249
|
|
|
127
250
|
@api(version="3.5")
|
|
128
|
-
def delete_dqw(self, item):
|
|
251
|
+
def delete_dqw(self, item: TableItem) -> None:
|
|
129
252
|
self._data_quality_warnings.clear(item)
|
|
130
253
|
|
|
131
254
|
@api(version="3.9")
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
1
2
|
import copy
|
|
3
|
+
import csv
|
|
4
|
+
import io
|
|
5
|
+
import itertools
|
|
2
6
|
import logging
|
|
3
7
|
from typing import Optional
|
|
8
|
+
import warnings
|
|
4
9
|
|
|
5
10
|
from tableauserverclient.server.query import QuerySet
|
|
6
11
|
|
|
7
|
-
from .endpoint import QuerysetEndpoint, api
|
|
8
|
-
from .exceptions import MissingRequiredFieldError, ServerResponseError
|
|
12
|
+
from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api
|
|
13
|
+
from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError, ServerResponseError
|
|
9
14
|
from tableauserverclient.server import RequestFactory, RequestOptions
|
|
10
|
-
from tableauserverclient.models import UserItem, WorkbookItem, PaginationItem, GroupItem
|
|
11
|
-
from
|
|
15
|
+
from tableauserverclient.models import UserItem, WorkbookItem, PaginationItem, GroupItem, JobItem
|
|
16
|
+
from tableauserverclient.server.pager import Pager
|
|
12
17
|
|
|
13
18
|
from tableauserverclient.helpers.logging import logger
|
|
14
19
|
|
|
@@ -87,7 +92,7 @@ class Users(QuerysetEndpoint[UserItem]):
|
|
|
87
92
|
|
|
88
93
|
if req_options is None:
|
|
89
94
|
req_options = RequestOptions()
|
|
90
|
-
req_options.
|
|
95
|
+
req_options.all_fields = True
|
|
91
96
|
|
|
92
97
|
url = self.baseurl
|
|
93
98
|
server_response = self.get_request(url, req_options)
|
|
@@ -344,7 +349,34 @@ class Users(QuerysetEndpoint[UserItem]):
|
|
|
344
349
|
|
|
345
350
|
# Add new users to site. This does not actually perform a bulk action, it's syntactic sugar
|
|
346
351
|
@api(version="2.0")
|
|
347
|
-
def add_all(self, users: list[UserItem]):
|
|
352
|
+
def add_all(self, users: list[UserItem]) -> tuple[list[UserItem], list[UserItem]]:
|
|
353
|
+
"""
|
|
354
|
+
Syntactic sugar for calling users.add multiple times. This method has
|
|
355
|
+
been deprecated in favor of using the bulk_add which accomplishes the
|
|
356
|
+
same task in one API call.
|
|
357
|
+
|
|
358
|
+
.. deprecated:: v0.41.0
|
|
359
|
+
`add_all` will be removed as its functionality is replicated via
|
|
360
|
+
the `bulk_add` method.
|
|
361
|
+
|
|
362
|
+
Parameters
|
|
363
|
+
----------
|
|
364
|
+
users: list[UserItem]
|
|
365
|
+
A list of UserItem objects to add to the site. Each UserItem object
|
|
366
|
+
will be passed to the `add` method individually.
|
|
367
|
+
|
|
368
|
+
Returns
|
|
369
|
+
-------
|
|
370
|
+
tuple[list[UserItem], list[UserItem]]
|
|
371
|
+
The first element of the tuple is a list of UserItem objects that
|
|
372
|
+
were successfully added to the site. The second element is a list
|
|
373
|
+
of UserItem objects that failed to be added to the site.
|
|
374
|
+
|
|
375
|
+
Warnings
|
|
376
|
+
--------
|
|
377
|
+
This method is deprecated. Use the `bulk_add` method instead.
|
|
378
|
+
"""
|
|
379
|
+
warnings.warn("This method is deprecated, use bulk_add method instead.", DeprecationWarning)
|
|
348
380
|
created = []
|
|
349
381
|
failed = []
|
|
350
382
|
for user in users:
|
|
@@ -357,8 +389,143 @@ class Users(QuerysetEndpoint[UserItem]):
|
|
|
357
389
|
|
|
358
390
|
# helping the user by parsing a file they could have used to add users through the UI
|
|
359
391
|
# line format: Username [required], password, display name, license, admin, publish
|
|
392
|
+
@api(version="3.15")
|
|
393
|
+
def bulk_add(self, users: Iterable[UserItem]) -> JobItem:
|
|
394
|
+
"""
|
|
395
|
+
When adding users in bulk, the server will return a job item that can be used to track the progress of the
|
|
396
|
+
operation. This method will return the job item that was created when the users were added.
|
|
397
|
+
|
|
398
|
+
For each user, name is required, and other fields are optional. If connected to activte directory and
|
|
399
|
+
the user name is not unique across domains, then the domain attribute must be populated on
|
|
400
|
+
the UserItem.
|
|
401
|
+
|
|
402
|
+
The user's display name is read from the fullname attribute.
|
|
403
|
+
|
|
404
|
+
Email is optional, but if provided, it must be a valid email address.
|
|
405
|
+
|
|
406
|
+
If auth_setting is not provided, and idp_configuration_id is None, then
|
|
407
|
+
default is ServerDefault.
|
|
408
|
+
|
|
409
|
+
If site_role is not provided, the default is Unlicensed.
|
|
410
|
+
|
|
411
|
+
Password is optional, and only used if the server is using local
|
|
412
|
+
authentication. If using any other authentication method, the password
|
|
413
|
+
should not be provided.
|
|
414
|
+
|
|
415
|
+
Details about administrator level and publishing capability are
|
|
416
|
+
inferred from the site_role.
|
|
417
|
+
|
|
418
|
+
If the user belongs to a different IDP configuration, the UserItem's
|
|
419
|
+
idp_configuration_id attribute must be set to the IDP configuration ID
|
|
420
|
+
that the user belongs to.
|
|
421
|
+
|
|
422
|
+
Parameters
|
|
423
|
+
----------
|
|
424
|
+
users: Iterable[UserItem]
|
|
425
|
+
An iterable of UserItem objects to add to the site. See above for
|
|
426
|
+
what fields are required and optional.
|
|
427
|
+
|
|
428
|
+
Returns
|
|
429
|
+
-------
|
|
430
|
+
JobItem
|
|
431
|
+
The job that is started for adding the users in bulk.
|
|
432
|
+
|
|
433
|
+
Examples
|
|
434
|
+
--------
|
|
435
|
+
>>> import tableauserverclient as TSC
|
|
436
|
+
>>> server = TSC.Server('http://localhost')
|
|
437
|
+
>>> # Login to the server
|
|
438
|
+
|
|
439
|
+
>>> # Create a list of UserItem objects to add to the site
|
|
440
|
+
>>> users = [
|
|
441
|
+
>>> TSC.UserItem(name="user1", site_role="Unlicensed"),
|
|
442
|
+
>>> TSC.UserItem(name="user2", site_role="Explorer"),
|
|
443
|
+
>>> TSC.UserItem(name="user3", site_role="Creator"),
|
|
444
|
+
>>> ]
|
|
445
|
+
|
|
446
|
+
>>> # Set the domain name for the users
|
|
447
|
+
>>> for user in users:
|
|
448
|
+
>>> user.domain_name = "example.com"
|
|
449
|
+
|
|
450
|
+
>>> # Add the users to the site
|
|
451
|
+
>>> job = server.users.bulk_add(users)
|
|
452
|
+
|
|
453
|
+
"""
|
|
454
|
+
url = f"{self.baseurl}/import"
|
|
455
|
+
# Allow for iterators to be passed into the function
|
|
456
|
+
csv_users, xml_users = itertools.tee(users, 2)
|
|
457
|
+
csv_content = create_users_csv(csv_users)
|
|
458
|
+
|
|
459
|
+
xml_request, content_type = RequestFactory.User.import_from_csv_req(csv_content, xml_users)
|
|
460
|
+
server_response = self.post_request(url, xml_request, content_type)
|
|
461
|
+
return JobItem.from_response(server_response.content, self.parent_srv.namespace).pop()
|
|
462
|
+
|
|
463
|
+
@api(version="3.15")
|
|
464
|
+
def bulk_remove(self, users: Iterable[UserItem]) -> None:
|
|
465
|
+
"""
|
|
466
|
+
Remove multiple users from the site. The users are identified by their
|
|
467
|
+
domain and name. The users are removed in bulk, so the server will not
|
|
468
|
+
return a job item to track the progress of the operation nor a response
|
|
469
|
+
for each user that was removed.
|
|
470
|
+
|
|
471
|
+
Parameters
|
|
472
|
+
----------
|
|
473
|
+
users: Iterable[UserItem]
|
|
474
|
+
An iterable of UserItem objects to remove from the site. Each
|
|
475
|
+
UserItem object should have the domain and name attributes set.
|
|
476
|
+
|
|
477
|
+
Returns
|
|
478
|
+
-------
|
|
479
|
+
None
|
|
480
|
+
|
|
481
|
+
Examples
|
|
482
|
+
--------
|
|
483
|
+
>>> import tableauserverclient as TSC
|
|
484
|
+
>>> server = TSC.Server('http://localhost')
|
|
485
|
+
>>> # Login to the server
|
|
486
|
+
|
|
487
|
+
>>> # Find the users to remove
|
|
488
|
+
>>> example_users = server.users.filter(domain_name="example.com")
|
|
489
|
+
>>> server.users.bulk_remove(example_users)
|
|
490
|
+
"""
|
|
491
|
+
url = f"{self.baseurl}/delete"
|
|
492
|
+
csv_content = remove_users_csv(users)
|
|
493
|
+
request, content_type = RequestFactory.User.delete_csv_req(csv_content)
|
|
494
|
+
server_response = self.post_request(url, request, content_type)
|
|
495
|
+
return None
|
|
496
|
+
|
|
360
497
|
@api(version="2.0")
|
|
361
498
|
def create_from_file(self, filepath: str) -> tuple[list[UserItem], list[tuple[UserItem, ServerResponseError]]]:
|
|
499
|
+
"""
|
|
500
|
+
Syntactic sugar for calling users.add multiple times. This method has
|
|
501
|
+
been deprecated in favor of using the bulk_add which accomplishes the
|
|
502
|
+
same task in one API call.
|
|
503
|
+
|
|
504
|
+
.. deprecated:: v0.41.0
|
|
505
|
+
`add_all` will be removed as its functionality is replicated via
|
|
506
|
+
the `bulk_add` method.
|
|
507
|
+
|
|
508
|
+
Parameters
|
|
509
|
+
----------
|
|
510
|
+
filepath: str
|
|
511
|
+
The path to the CSV file containing the users to add to the site.
|
|
512
|
+
The file is read in line by line and each line is passed to the
|
|
513
|
+
`add` method.
|
|
514
|
+
|
|
515
|
+
Returns
|
|
516
|
+
-------
|
|
517
|
+
tuple[list[UserItem], list[tuple[UserItem, ServerResponseError]]]
|
|
518
|
+
The first element of the tuple is a list of UserItem objects that
|
|
519
|
+
were successfully added to the site. The second element is a list
|
|
520
|
+
of tuples where the first element is the UserItem object that failed
|
|
521
|
+
to be added to the site and the second element is the ServerResponseError
|
|
522
|
+
that was raised when attempting to add the user.
|
|
523
|
+
|
|
524
|
+
Warnings
|
|
525
|
+
--------
|
|
526
|
+
This method is deprecated. Use the `bulk_add` method instead.
|
|
527
|
+
"""
|
|
528
|
+
warnings.warn("This method is deprecated, use bulk_add instead", DeprecationWarning)
|
|
362
529
|
created = []
|
|
363
530
|
failed = []
|
|
364
531
|
if not filepath.find("csv"):
|
|
@@ -381,10 +548,15 @@ class Users(QuerysetEndpoint[UserItem]):
|
|
|
381
548
|
|
|
382
549
|
# Get workbooks for user
|
|
383
550
|
@api(version="2.0")
|
|
384
|
-
def populate_workbooks(
|
|
551
|
+
def populate_workbooks(
|
|
552
|
+
self, user_item: UserItem, req_options: Optional[RequestOptions] = None, owned_only: bool = False
|
|
553
|
+
) -> None:
|
|
385
554
|
"""
|
|
386
555
|
Returns information about the workbooks that the specified user owns
|
|
387
|
-
|
|
556
|
+
or has Read (view) permissions for. If owned_only is set to True,
|
|
557
|
+
only the workbooks that the user owns are returned. If owned_only is
|
|
558
|
+
set to False, all workbooks that the user has Read (view) permissions
|
|
559
|
+
for are returned.
|
|
388
560
|
|
|
389
561
|
This method retrieves the workbook information for the specified user.
|
|
390
562
|
The REST API is designed to return only the information you ask for
|
|
@@ -402,6 +574,10 @@ class Users(QuerysetEndpoint[UserItem]):
|
|
|
402
574
|
req_options : Optional[RequestOptions]
|
|
403
575
|
Optional request options to filter and sort the results.
|
|
404
576
|
|
|
577
|
+
owned_only : bool, default=False
|
|
578
|
+
If True, only the workbooks that the user owns are returned.
|
|
579
|
+
If False, all workbooks that the user has Read (view) permissions
|
|
580
|
+
|
|
405
581
|
Returns
|
|
406
582
|
-------
|
|
407
583
|
None
|
|
@@ -423,14 +599,22 @@ class Users(QuerysetEndpoint[UserItem]):
|
|
|
423
599
|
raise MissingRequiredFieldError(error)
|
|
424
600
|
|
|
425
601
|
def wb_pager():
|
|
426
|
-
|
|
602
|
+
def func(req_options):
|
|
603
|
+
return self._get_wbs_for_user(user_item, req_options, owned_only=owned_only)
|
|
604
|
+
|
|
605
|
+
return Pager(func, req_options)
|
|
427
606
|
|
|
428
607
|
user_item._set_workbooks(wb_pager)
|
|
429
608
|
|
|
430
609
|
def _get_wbs_for_user(
|
|
431
|
-
self,
|
|
610
|
+
self,
|
|
611
|
+
user_item: UserItem,
|
|
612
|
+
req_options: Optional[RequestOptions] = None,
|
|
613
|
+
owned_only: bool = False,
|
|
432
614
|
) -> tuple[list[WorkbookItem], PaginationItem]:
|
|
433
615
|
url = f"{self.baseurl}/{user_item.id}/workbooks"
|
|
616
|
+
if owned_only:
|
|
617
|
+
url += "?ownedBy=true"
|
|
434
618
|
server_response = self.get_request(url, req_options)
|
|
435
619
|
logger.info(f"Populated workbooks for user (ID: {user_item.id})")
|
|
436
620
|
workbook_item = WorkbookItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
@@ -552,3 +736,105 @@ class Users(QuerysetEndpoint[UserItem]):
|
|
|
552
736
|
"""
|
|
553
737
|
|
|
554
738
|
return super().filter(*invalid, page_size=page_size, **kwargs)
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
def create_users_csv(users: Iterable[UserItem]) -> bytes:
|
|
742
|
+
"""
|
|
743
|
+
Create a CSV byte string from an Iterable of UserItem objects. The CSV will
|
|
744
|
+
have the following columns, and no header row:
|
|
745
|
+
|
|
746
|
+
- Username
|
|
747
|
+
- Password
|
|
748
|
+
- Display Name
|
|
749
|
+
- License
|
|
750
|
+
- Admin Level
|
|
751
|
+
- Publish capability
|
|
752
|
+
- Email
|
|
753
|
+
|
|
754
|
+
Parameters
|
|
755
|
+
----------
|
|
756
|
+
users: Iterable[UserItem]
|
|
757
|
+
An iterable of UserItem objects to create the CSV from.
|
|
758
|
+
|
|
759
|
+
Returns
|
|
760
|
+
-------
|
|
761
|
+
bytes
|
|
762
|
+
A byte string containing the CSV data.
|
|
763
|
+
"""
|
|
764
|
+
with io.StringIO() as output:
|
|
765
|
+
writer = csv.writer(output, quoting=csv.QUOTE_MINIMAL)
|
|
766
|
+
for user in users:
|
|
767
|
+
site_role = user.site_role or "Unlicensed"
|
|
768
|
+
if site_role == "ServerAdministrator":
|
|
769
|
+
license = "Creator"
|
|
770
|
+
admin_level = "System"
|
|
771
|
+
elif site_role.startswith("SiteAdministrator"):
|
|
772
|
+
admin_level = "Site"
|
|
773
|
+
license = site_role.replace("SiteAdministrator", "")
|
|
774
|
+
else:
|
|
775
|
+
license = site_role
|
|
776
|
+
admin_level = ""
|
|
777
|
+
|
|
778
|
+
if any(x in site_role for x in ("Creator", "Admin", "Publish")):
|
|
779
|
+
publish = 1
|
|
780
|
+
else:
|
|
781
|
+
publish = 0
|
|
782
|
+
|
|
783
|
+
writer.writerow(
|
|
784
|
+
(
|
|
785
|
+
f"{user.domain_name}\\{user.name}" if user.domain_name else user.name,
|
|
786
|
+
getattr(user, "password", ""),
|
|
787
|
+
user.fullname,
|
|
788
|
+
license,
|
|
789
|
+
admin_level,
|
|
790
|
+
publish,
|
|
791
|
+
user.email,
|
|
792
|
+
)
|
|
793
|
+
)
|
|
794
|
+
output.seek(0)
|
|
795
|
+
result = output.read().encode("utf-8")
|
|
796
|
+
return result
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def remove_users_csv(users: Iterable[UserItem]) -> bytes:
|
|
800
|
+
"""
|
|
801
|
+
Create a CSV byte string from an Iterable of UserItem objects. This function
|
|
802
|
+
only consumes the domain and name attributes of the UserItem objects. The
|
|
803
|
+
CSV will have space for the following columns, though only the first column
|
|
804
|
+
will be populated, and no header row:
|
|
805
|
+
|
|
806
|
+
- Username
|
|
807
|
+
- Password
|
|
808
|
+
- Display Name
|
|
809
|
+
- License
|
|
810
|
+
- Admin Level
|
|
811
|
+
- Publish capability
|
|
812
|
+
- Email
|
|
813
|
+
|
|
814
|
+
Parameters
|
|
815
|
+
----------
|
|
816
|
+
users: Iterable[UserItem]
|
|
817
|
+
An iterable of UserItem objects to create the CSV from.
|
|
818
|
+
|
|
819
|
+
Returns
|
|
820
|
+
-------
|
|
821
|
+
bytes
|
|
822
|
+
A byte string containing the CSV data.
|
|
823
|
+
"""
|
|
824
|
+
with io.StringIO() as output:
|
|
825
|
+
writer = csv.writer(output, quoting=csv.QUOTE_MINIMAL)
|
|
826
|
+
for user in users:
|
|
827
|
+
writer.writerow(
|
|
828
|
+
(
|
|
829
|
+
f"{user.domain_name}\\{user.name}" if user.domain_name else user.name,
|
|
830
|
+
None,
|
|
831
|
+
None,
|
|
832
|
+
None,
|
|
833
|
+
None,
|
|
834
|
+
None,
|
|
835
|
+
None,
|
|
836
|
+
)
|
|
837
|
+
)
|
|
838
|
+
output.seek(0)
|
|
839
|
+
result = output.read().encode("utf-8")
|
|
840
|
+
return result
|
|
@@ -371,6 +371,29 @@ class Views(QuerysetEndpoint[ViewItem], TaggingMixin[ViewItem]):
|
|
|
371
371
|
# Returning view item to stay consistent with datasource/view update functions
|
|
372
372
|
return view_item
|
|
373
373
|
|
|
374
|
+
@api(version="3.27")
|
|
375
|
+
def delete(self, view: ViewItem | str) -> None:
|
|
376
|
+
"""
|
|
377
|
+
Deletes a view in a workbook. If you delete the only view in a workbook,
|
|
378
|
+
the workbook is deleted. Can be used to remove hidden views when
|
|
379
|
+
republishing or migrating to a different environment.
|
|
380
|
+
|
|
381
|
+
REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_workbooks_and_views.htm#delete_view
|
|
382
|
+
|
|
383
|
+
Parameters
|
|
384
|
+
----------
|
|
385
|
+
view: ViewItem | str
|
|
386
|
+
The ViewItem or the luid for the view to be deleted.
|
|
387
|
+
|
|
388
|
+
Returns
|
|
389
|
+
-------
|
|
390
|
+
None
|
|
391
|
+
"""
|
|
392
|
+
id_ = getattr(view, "id", view)
|
|
393
|
+
self.delete_request(f"{self.baseurl}/{id_}")
|
|
394
|
+
logger.info(f"View({id_}) deleted.")
|
|
395
|
+
return None
|
|
396
|
+
|
|
374
397
|
@api(version="1.0")
|
|
375
398
|
def add_tags(self, item: Union[ViewItem, str], tags: Union[Iterable[str], str]) -> set[str]:
|
|
376
399
|
"""
|