tableauserverclient 0.33__py3-none-any.whl → 0.34__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tableauserverclient/__init__.py +28 -22
- tableauserverclient/_version.py +3 -3
- tableauserverclient/config.py +5 -3
- tableauserverclient/models/column_item.py +1 -1
- tableauserverclient/models/connection_credentials.py +1 -1
- tableauserverclient/models/connection_item.py +6 -6
- tableauserverclient/models/custom_view_item.py +29 -6
- tableauserverclient/models/data_acceleration_report_item.py +2 -2
- tableauserverclient/models/data_alert_item.py +5 -5
- tableauserverclient/models/data_freshness_policy_item.py +6 -6
- tableauserverclient/models/database_item.py +3 -3
- tableauserverclient/models/datasource_item.py +10 -10
- tableauserverclient/models/dqw_item.py +1 -1
- tableauserverclient/models/favorites_item.py +5 -6
- tableauserverclient/models/fileupload_item.py +1 -1
- tableauserverclient/models/flow_item.py +6 -6
- tableauserverclient/models/flow_run_item.py +3 -3
- tableauserverclient/models/group_item.py +4 -4
- tableauserverclient/models/groupset_item.py +4 -4
- tableauserverclient/models/interval_item.py +9 -9
- tableauserverclient/models/job_item.py +8 -8
- tableauserverclient/models/linked_tasks_item.py +5 -5
- tableauserverclient/models/metric_item.py +5 -5
- tableauserverclient/models/pagination_item.py +1 -1
- tableauserverclient/models/permissions_item.py +12 -10
- tableauserverclient/models/project_item.py +35 -19
- tableauserverclient/models/property_decorators.py +12 -11
- tableauserverclient/models/reference_item.py +2 -2
- tableauserverclient/models/revision_item.py +3 -3
- tableauserverclient/models/schedule_item.py +2 -2
- tableauserverclient/models/server_info_item.py +26 -6
- tableauserverclient/models/site_item.py +69 -3
- tableauserverclient/models/subscription_item.py +3 -3
- tableauserverclient/models/table_item.py +1 -1
- tableauserverclient/models/tableau_auth.py +115 -5
- tableauserverclient/models/tableau_types.py +2 -2
- tableauserverclient/models/tag_item.py +3 -4
- tableauserverclient/models/task_item.py +4 -4
- tableauserverclient/models/user_item.py +47 -17
- tableauserverclient/models/view_item.py +11 -10
- tableauserverclient/models/virtual_connection_item.py +6 -5
- tableauserverclient/models/webhook_item.py +6 -6
- tableauserverclient/models/workbook_item.py +90 -12
- tableauserverclient/namespace.py +1 -1
- tableauserverclient/server/__init__.py +2 -1
- tableauserverclient/server/endpoint/auth_endpoint.py +65 -8
- tableauserverclient/server/endpoint/custom_views_endpoint.py +62 -18
- tableauserverclient/server/endpoint/data_acceleration_report_endpoint.py +2 -2
- tableauserverclient/server/endpoint/data_alert_endpoint.py +14 -14
- tableauserverclient/server/endpoint/databases_endpoint.py +13 -12
- tableauserverclient/server/endpoint/datasources_endpoint.py +49 -54
- tableauserverclient/server/endpoint/default_permissions_endpoint.py +19 -18
- tableauserverclient/server/endpoint/dqw_endpoint.py +9 -9
- tableauserverclient/server/endpoint/endpoint.py +19 -21
- tableauserverclient/server/endpoint/exceptions.py +23 -7
- tableauserverclient/server/endpoint/favorites_endpoint.py +31 -31
- tableauserverclient/server/endpoint/fileuploads_endpoint.py +9 -11
- tableauserverclient/server/endpoint/flow_runs_endpoint.py +15 -13
- tableauserverclient/server/endpoint/flow_task_endpoint.py +2 -2
- tableauserverclient/server/endpoint/flows_endpoint.py +30 -29
- tableauserverclient/server/endpoint/groups_endpoint.py +18 -17
- tableauserverclient/server/endpoint/groupsets_endpoint.py +2 -2
- tableauserverclient/server/endpoint/jobs_endpoint.py +7 -7
- tableauserverclient/server/endpoint/linked_tasks_endpoint.py +2 -2
- tableauserverclient/server/endpoint/metadata_endpoint.py +2 -2
- tableauserverclient/server/endpoint/metrics_endpoint.py +10 -10
- tableauserverclient/server/endpoint/permissions_endpoint.py +13 -15
- tableauserverclient/server/endpoint/projects_endpoint.py +81 -30
- tableauserverclient/server/endpoint/resource_tagger.py +14 -13
- tableauserverclient/server/endpoint/schedules_endpoint.py +17 -18
- tableauserverclient/server/endpoint/server_info_endpoint.py +40 -5
- tableauserverclient/server/endpoint/sites_endpoint.py +282 -17
- tableauserverclient/server/endpoint/subscriptions_endpoint.py +10 -10
- tableauserverclient/server/endpoint/tables_endpoint.py +15 -14
- tableauserverclient/server/endpoint/tasks_endpoint.py +8 -8
- tableauserverclient/server/endpoint/users_endpoint.py +366 -19
- tableauserverclient/server/endpoint/views_endpoint.py +19 -18
- tableauserverclient/server/endpoint/virtual_connections_endpoint.py +6 -5
- tableauserverclient/server/endpoint/webhooks_endpoint.py +11 -11
- tableauserverclient/server/endpoint/workbooks_endpoint.py +647 -61
- tableauserverclient/server/filter.py +2 -2
- tableauserverclient/server/pager.py +5 -6
- tableauserverclient/server/query.py +68 -19
- tableauserverclient/server/request_factory.py +37 -36
- tableauserverclient/server/request_options.py +123 -145
- tableauserverclient/server/server.py +65 -9
- tableauserverclient/server/sort.py +2 -2
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/METADATA +6 -6
- tableauserverclient-0.34.dist-info/RECORD +106 -0
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/WHEEL +1 -1
- tableauserverclient-0.33.dist-info/RECORD +0 -106
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/LICENSE +0 -0
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/LICENSE.versioneer +0 -0
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
import logging
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Optional
|
|
4
4
|
|
|
5
5
|
from tableauserverclient.server.query import QuerySet
|
|
6
6
|
|
|
@@ -14,13 +14,75 @@ from tableauserverclient.helpers.logging import logger
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class Users(QuerysetEndpoint[UserItem]):
|
|
17
|
+
"""
|
|
18
|
+
The user resources for Tableau Server are defined in the UserItem class.
|
|
19
|
+
The class corresponds to the user resources you can access using the
|
|
20
|
+
Tableau Server REST API. The user methods are based upon the endpoints for
|
|
21
|
+
users in the REST API and operate on the UserItem class. Only server and
|
|
22
|
+
site administrators can access the user resources.
|
|
23
|
+
"""
|
|
24
|
+
|
|
17
25
|
@property
|
|
18
26
|
def baseurl(self) -> str:
|
|
19
|
-
return "{
|
|
27
|
+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/users"
|
|
20
28
|
|
|
21
29
|
# Gets all users
|
|
22
30
|
@api(version="2.0")
|
|
23
|
-
def get(self, req_options: Optional[RequestOptions] = None) ->
|
|
31
|
+
def get(self, req_options: Optional[RequestOptions] = None) -> tuple[list[UserItem], PaginationItem]:
|
|
32
|
+
"""
|
|
33
|
+
Query all users on the site. Request is paginated and returns a subset of users.
|
|
34
|
+
By default, the request returns the first 100 users on the site.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
req_options : Optional[RequestOptions]
|
|
39
|
+
Optional request options to filter and sort the results.
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
tuple[list[UserItem], PaginationItem]
|
|
44
|
+
Returns a tuple with a list of UserItem objects and a PaginationItem object.
|
|
45
|
+
|
|
46
|
+
Raises
|
|
47
|
+
------
|
|
48
|
+
ServerResponseError
|
|
49
|
+
code: 400006
|
|
50
|
+
summary: Invalid page number
|
|
51
|
+
detail: The page number is not an integer, is less than one, or is
|
|
52
|
+
greater than the final page number for users at the requested
|
|
53
|
+
page size.
|
|
54
|
+
|
|
55
|
+
ServerResponseError
|
|
56
|
+
code: 400007
|
|
57
|
+
summary: Invalid page size
|
|
58
|
+
detail: The page size parameter is not an integer, is less than one.
|
|
59
|
+
|
|
60
|
+
ServerResponseError
|
|
61
|
+
code: 403014
|
|
62
|
+
summary: Page size limit exceeded
|
|
63
|
+
detail: The specified page size is larger than the maximum page size
|
|
64
|
+
|
|
65
|
+
ServerResponseError
|
|
66
|
+
code: 404000
|
|
67
|
+
summary: Site not found
|
|
68
|
+
detail: The site ID in the URI doesn't correspond to an existing site.
|
|
69
|
+
|
|
70
|
+
ServerResponseError
|
|
71
|
+
code: 405000
|
|
72
|
+
summary: Invalid request method
|
|
73
|
+
detail: Request type was not GET.
|
|
74
|
+
|
|
75
|
+
Examples
|
|
76
|
+
--------
|
|
77
|
+
>>> import tableauserverclient as TSC
|
|
78
|
+
>>> tableau_auth = TSC.TableauAuth('USERNAME', 'PASSWORD')
|
|
79
|
+
>>> server = TSC.Server('https://SERVERURL')
|
|
80
|
+
|
|
81
|
+
>>> with server.auth.sign_in(tableau_auth):
|
|
82
|
+
>>> users_page, pagination_item = server.users.get()
|
|
83
|
+
>>> print("\nThere are {} user on site: ".format(pagination_item.total_available))
|
|
84
|
+
>>> print([user.name for user in users_page])
|
|
85
|
+
"""
|
|
24
86
|
logger.info("Querying all users on site")
|
|
25
87
|
|
|
26
88
|
if req_options is None:
|
|
@@ -36,55 +98,253 @@ class Users(QuerysetEndpoint[UserItem]):
|
|
|
36
98
|
# Gets 1 user by id
|
|
37
99
|
@api(version="2.0")
|
|
38
100
|
def get_by_id(self, user_id: str) -> UserItem:
|
|
101
|
+
"""
|
|
102
|
+
Query a single user by ID.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
user_id : str
|
|
107
|
+
The ID of the user to query.
|
|
108
|
+
|
|
109
|
+
Returns
|
|
110
|
+
-------
|
|
111
|
+
UserItem
|
|
112
|
+
The user item that was queried.
|
|
113
|
+
|
|
114
|
+
Raises
|
|
115
|
+
------
|
|
116
|
+
ValueError
|
|
117
|
+
If the user ID is not specified.
|
|
118
|
+
|
|
119
|
+
ServerResponseError
|
|
120
|
+
code: 404000
|
|
121
|
+
summary: Site not found
|
|
122
|
+
detail: The site ID in the URI doesn't correspond to an existing site.
|
|
123
|
+
|
|
124
|
+
ServerResponseError
|
|
125
|
+
code: 403133
|
|
126
|
+
summary: Query user permissions forbidden
|
|
127
|
+
detail: The user does not have permissions to query user information
|
|
128
|
+
for other users
|
|
129
|
+
|
|
130
|
+
ServerResponseError
|
|
131
|
+
code: 404002
|
|
132
|
+
summary: User not found
|
|
133
|
+
detail: The user ID in the URI doesn't correspond to an existing user.
|
|
134
|
+
|
|
135
|
+
ServerResponseError
|
|
136
|
+
code: 405000
|
|
137
|
+
summary: Invalid request method
|
|
138
|
+
detail: Request type was not GET.
|
|
139
|
+
|
|
140
|
+
Examples
|
|
141
|
+
--------
|
|
142
|
+
>>> user1 = server.users.get_by_id('9f9e9d9c-8b8a-8f8e-7d7c-7b7a6f6d6e6d')
|
|
143
|
+
"""
|
|
39
144
|
if not user_id:
|
|
40
145
|
error = "User ID undefined."
|
|
41
146
|
raise ValueError(error)
|
|
42
|
-
logger.info("Querying single user (ID: {
|
|
43
|
-
url = "{
|
|
147
|
+
logger.info(f"Querying single user (ID: {user_id})")
|
|
148
|
+
url = f"{self.baseurl}/{user_id}"
|
|
44
149
|
server_response = self.get_request(url)
|
|
45
150
|
return UserItem.from_response(server_response.content, self.parent_srv.namespace).pop()
|
|
46
151
|
|
|
47
152
|
# Update user
|
|
48
153
|
@api(version="2.0")
|
|
49
154
|
def update(self, user_item: UserItem, password: Optional[str] = None) -> UserItem:
|
|
155
|
+
"""
|
|
156
|
+
Modifies information about the specified user.
|
|
157
|
+
|
|
158
|
+
If Tableau Server is configured to use local authentication, you can
|
|
159
|
+
update the user's name, email address, password, or site role.
|
|
160
|
+
|
|
161
|
+
If Tableau Server is configured to use Active Directory
|
|
162
|
+
authentication, you can change the user's display name (full name),
|
|
163
|
+
email address, and site role. However, if you synchronize the user with
|
|
164
|
+
Active Directory, the display name and email address will be
|
|
165
|
+
overwritten with the information that's in Active Directory.
|
|
166
|
+
|
|
167
|
+
For Tableau Cloud, you can update the site role for a user, but you
|
|
168
|
+
cannot update or change a user's password, user name (email address),
|
|
169
|
+
or full name.
|
|
170
|
+
|
|
171
|
+
Parameters
|
|
172
|
+
----------
|
|
173
|
+
user_item : UserItem
|
|
174
|
+
The user item to update.
|
|
175
|
+
|
|
176
|
+
password : Optional[str]
|
|
177
|
+
The new password for the user.
|
|
178
|
+
|
|
179
|
+
Returns
|
|
180
|
+
-------
|
|
181
|
+
UserItem
|
|
182
|
+
The user item that was updated.
|
|
183
|
+
|
|
184
|
+
Raises
|
|
185
|
+
------
|
|
186
|
+
MissingRequiredFieldError
|
|
187
|
+
If the user item is missing an ID.
|
|
188
|
+
|
|
189
|
+
Examples
|
|
190
|
+
--------
|
|
191
|
+
>>> user = server.users.get_by_id('9f9e9d9c-8b8a-8f8e-7d7c-7b7a6f6d6e6d')
|
|
192
|
+
>>> user.fullname = 'New Full Name'
|
|
193
|
+
>>> updated_user = server.users.update(user)
|
|
194
|
+
|
|
195
|
+
"""
|
|
50
196
|
if not user_item.id:
|
|
51
197
|
error = "User item missing ID."
|
|
52
198
|
raise MissingRequiredFieldError(error)
|
|
53
199
|
|
|
54
|
-
url = "{
|
|
200
|
+
url = f"{self.baseurl}/{user_item.id}"
|
|
55
201
|
update_req = RequestFactory.User.update_req(user_item, password)
|
|
56
202
|
server_response = self.put_request(url, update_req)
|
|
57
|
-
logger.info("Updated user item (ID: {
|
|
203
|
+
logger.info(f"Updated user item (ID: {user_item.id})")
|
|
58
204
|
updated_item = copy.copy(user_item)
|
|
59
205
|
return updated_item._parse_common_tags(server_response.content, self.parent_srv.namespace)
|
|
60
206
|
|
|
61
207
|
# Delete 1 user by id
|
|
62
208
|
@api(version="2.0")
|
|
63
209
|
def remove(self, user_id: str, map_assets_to: Optional[str] = None) -> None:
|
|
210
|
+
"""
|
|
211
|
+
Removes a user from the site. You can also specify a user to map the
|
|
212
|
+
assets to when you remove the user.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
user_id : str
|
|
217
|
+
The ID of the user to remove.
|
|
218
|
+
|
|
219
|
+
map_assets_to : Optional[str]
|
|
220
|
+
The ID of the user to map the assets to when you remove the user.
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
None
|
|
225
|
+
|
|
226
|
+
Raises
|
|
227
|
+
------
|
|
228
|
+
ValueError
|
|
229
|
+
If the user ID is not specified.
|
|
230
|
+
|
|
231
|
+
Examples
|
|
232
|
+
--------
|
|
233
|
+
>>> server.users.remove('9f9e9d9c-8b8a-8f8e-7d7c-7b7a6f6d6e6d')
|
|
234
|
+
"""
|
|
64
235
|
if not user_id:
|
|
65
236
|
error = "User ID undefined."
|
|
66
237
|
raise ValueError(error)
|
|
67
|
-
url = "{
|
|
238
|
+
url = f"{self.baseurl}/{user_id}"
|
|
68
239
|
if map_assets_to is not None:
|
|
69
240
|
url += f"?mapAssetsTo={map_assets_to}"
|
|
70
241
|
self.delete_request(url)
|
|
71
|
-
logger.info("Removed single user (ID: {
|
|
242
|
+
logger.info(f"Removed single user (ID: {user_id})")
|
|
72
243
|
|
|
73
244
|
# Add new user to site
|
|
74
245
|
@api(version="2.0")
|
|
75
246
|
def add(self, user_item: UserItem) -> UserItem:
|
|
247
|
+
"""
|
|
248
|
+
Adds the user to the site.
|
|
249
|
+
|
|
250
|
+
To add a new user to the site you need to first create a new user_item
|
|
251
|
+
(from UserItem class). When you create a new user, you specify the name
|
|
252
|
+
of the user and their site role. For Tableau Cloud, you also specify
|
|
253
|
+
the auth_setting attribute in your request. When you add user to
|
|
254
|
+
Tableau Cloud, the name of the user must be the email address that is
|
|
255
|
+
used to sign in to Tableau Cloud. After you add a user, Tableau Cloud
|
|
256
|
+
sends the user an email invitation. The user can click the link in the
|
|
257
|
+
invitation to sign in and update their full name and password.
|
|
258
|
+
|
|
259
|
+
Parameters
|
|
260
|
+
----------
|
|
261
|
+
user_item : UserItem
|
|
262
|
+
The user item to add to the site.
|
|
263
|
+
|
|
264
|
+
Returns
|
|
265
|
+
-------
|
|
266
|
+
UserItem
|
|
267
|
+
The user item that was added to the site with attributes from the
|
|
268
|
+
site populated.
|
|
269
|
+
|
|
270
|
+
Raises
|
|
271
|
+
------
|
|
272
|
+
ValueError
|
|
273
|
+
If the user item is missing a name
|
|
274
|
+
|
|
275
|
+
ValueError
|
|
276
|
+
If the user item is missing a site role
|
|
277
|
+
|
|
278
|
+
ServerResponseError
|
|
279
|
+
code: 400000
|
|
280
|
+
summary: Bad Request
|
|
281
|
+
detail: The content of the request body is missing or incomplete, or
|
|
282
|
+
contains malformed XML.
|
|
283
|
+
|
|
284
|
+
ServerResponseError
|
|
285
|
+
code: 400003
|
|
286
|
+
summary: Bad Request
|
|
287
|
+
detail: The user authentication setting ServerDefault is not
|
|
288
|
+
supported for you site. Try again using TableauIDWithMFA instead.
|
|
289
|
+
|
|
290
|
+
ServerResponseError
|
|
291
|
+
code: 400013
|
|
292
|
+
summary: Invalid site role
|
|
293
|
+
detail: The value of the siteRole attribute must be Explorer,
|
|
294
|
+
ExplorerCanPublish, SiteAdministratorCreator,
|
|
295
|
+
SiteAdministratorExplorer, Unlicensed, or Viewer.
|
|
296
|
+
|
|
297
|
+
ServerResponseError
|
|
298
|
+
code: 404000
|
|
299
|
+
summary: Site not found
|
|
300
|
+
detail: The site ID in the URI doesn't correspond to an existing site.
|
|
301
|
+
|
|
302
|
+
ServerResponseError
|
|
303
|
+
code: 404002
|
|
304
|
+
summary: User not found
|
|
305
|
+
detail: The server is configured to use Active Directory for
|
|
306
|
+
authentication, and the username specified in the request body
|
|
307
|
+
doesn't match an existing user in Active Directory.
|
|
308
|
+
|
|
309
|
+
ServerResponseError
|
|
310
|
+
code: 405000
|
|
311
|
+
summary: Invalid request method
|
|
312
|
+
detail: Request type was not POST.
|
|
313
|
+
|
|
314
|
+
ServerResponseError
|
|
315
|
+
code: 409000
|
|
316
|
+
summary: User conflict
|
|
317
|
+
detail: The specified user already exists on the site.
|
|
318
|
+
|
|
319
|
+
ServerResponseError
|
|
320
|
+
code: 409005
|
|
321
|
+
summary: Guest user conflict
|
|
322
|
+
detail: The Tableau Server API doesn't allow adding a user with the
|
|
323
|
+
guest role to a site.
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
Examples
|
|
327
|
+
--------
|
|
328
|
+
>>> import tableauserverclient as TSC
|
|
329
|
+
>>> server = TSC.Server('https://SERVERURL')
|
|
330
|
+
>>> # Login to the server
|
|
331
|
+
|
|
332
|
+
>>> new_user = TSC.UserItem(name='new_user', site_role=TSC.UserItem.Role.Unlicensed)
|
|
333
|
+
>>> new_user = server.users.add(new_user)
|
|
334
|
+
|
|
335
|
+
"""
|
|
76
336
|
url = self.baseurl
|
|
77
|
-
logger.info("Add user {
|
|
337
|
+
logger.info(f"Add user {user_item.name}")
|
|
78
338
|
add_req = RequestFactory.User.add_req(user_item)
|
|
79
339
|
server_response = self.post_request(url, add_req)
|
|
80
340
|
logger.info(server_response)
|
|
81
341
|
new_user = UserItem.from_response(server_response.content, self.parent_srv.namespace).pop()
|
|
82
|
-
logger.info("Added new user (ID: {
|
|
342
|
+
logger.info(f"Added new user (ID: {new_user.id})")
|
|
83
343
|
return new_user
|
|
84
344
|
|
|
85
345
|
# Add new users to site. This does not actually perform a bulk action, it's syntactic sugar
|
|
86
346
|
@api(version="2.0")
|
|
87
|
-
def add_all(self, users:
|
|
347
|
+
def add_all(self, users: list[UserItem]):
|
|
88
348
|
created = []
|
|
89
349
|
failed = []
|
|
90
350
|
for user in users:
|
|
@@ -98,7 +358,7 @@ class Users(QuerysetEndpoint[UserItem]):
|
|
|
98
358
|
# helping the user by parsing a file they could have used to add users through the UI
|
|
99
359
|
# line format: Username [required], password, display name, license, admin, publish
|
|
100
360
|
@api(version="2.0")
|
|
101
|
-
def create_from_file(self, filepath: str) ->
|
|
361
|
+
def create_from_file(self, filepath: str) -> tuple[list[UserItem], list[tuple[UserItem, ServerResponseError]]]:
|
|
102
362
|
created = []
|
|
103
363
|
failed = []
|
|
104
364
|
if not filepath.find("csv"):
|
|
@@ -122,6 +382,42 @@ class Users(QuerysetEndpoint[UserItem]):
|
|
|
122
382
|
# Get workbooks for user
|
|
123
383
|
@api(version="2.0")
|
|
124
384
|
def populate_workbooks(self, user_item: UserItem, req_options: Optional[RequestOptions] = None) -> None:
|
|
385
|
+
"""
|
|
386
|
+
Returns information about the workbooks that the specified user owns
|
|
387
|
+
and has Read (view) permissions for.
|
|
388
|
+
|
|
389
|
+
This method retrieves the workbook information for the specified user.
|
|
390
|
+
The REST API is designed to return only the information you ask for
|
|
391
|
+
explicitly. When you query for all the users, the workbook information
|
|
392
|
+
for each user is not included. Use this method to retrieve information
|
|
393
|
+
about the workbooks that the user owns or has Read (view) permissions.
|
|
394
|
+
The method adds the list of workbooks to the user item object
|
|
395
|
+
(user_item.workbooks).
|
|
396
|
+
|
|
397
|
+
Parameters
|
|
398
|
+
----------
|
|
399
|
+
user_item : UserItem
|
|
400
|
+
The user item to populate workbooks for.
|
|
401
|
+
|
|
402
|
+
req_options : Optional[RequestOptions]
|
|
403
|
+
Optional request options to filter and sort the results.
|
|
404
|
+
|
|
405
|
+
Returns
|
|
406
|
+
-------
|
|
407
|
+
None
|
|
408
|
+
|
|
409
|
+
Raises
|
|
410
|
+
------
|
|
411
|
+
MissingRequiredFieldError
|
|
412
|
+
If the user item is missing an ID.
|
|
413
|
+
|
|
414
|
+
Examples
|
|
415
|
+
--------
|
|
416
|
+
>>> user = server.users.get_by_id('9f9e9d9c-8b8a-8f8e-7d7c-7b7a6f6d6e6d')
|
|
417
|
+
>>> server.users.populate_workbooks(user)
|
|
418
|
+
>>> for wb in user.workbooks:
|
|
419
|
+
>>> print(wb.name)
|
|
420
|
+
"""
|
|
125
421
|
if not user_item.id:
|
|
126
422
|
error = "User item missing ID."
|
|
127
423
|
raise MissingRequiredFieldError(error)
|
|
@@ -133,20 +429,71 @@ class Users(QuerysetEndpoint[UserItem]):
|
|
|
133
429
|
|
|
134
430
|
def _get_wbs_for_user(
|
|
135
431
|
self, user_item: UserItem, req_options: Optional[RequestOptions] = None
|
|
136
|
-
) ->
|
|
137
|
-
url = "{
|
|
432
|
+
) -> tuple[list[WorkbookItem], PaginationItem]:
|
|
433
|
+
url = f"{self.baseurl}/{user_item.id}/workbooks"
|
|
138
434
|
server_response = self.get_request(url, req_options)
|
|
139
|
-
logger.info("Populated workbooks for user (ID: {
|
|
435
|
+
logger.info(f"Populated workbooks for user (ID: {user_item.id})")
|
|
140
436
|
workbook_item = WorkbookItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
141
437
|
pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
142
438
|
return workbook_item, pagination_item
|
|
143
439
|
|
|
144
440
|
def populate_favorites(self, user_item: UserItem) -> None:
|
|
441
|
+
"""
|
|
442
|
+
Populate the favorites for the user.
|
|
443
|
+
|
|
444
|
+
Parameters
|
|
445
|
+
----------
|
|
446
|
+
user_item : UserItem
|
|
447
|
+
The user item to populate favorites for.
|
|
448
|
+
|
|
449
|
+
Returns
|
|
450
|
+
-------
|
|
451
|
+
None
|
|
452
|
+
|
|
453
|
+
Examples
|
|
454
|
+
--------
|
|
455
|
+
>>> import tableauserverclient as TSC
|
|
456
|
+
>>> server = TSC.Server('https://SERVERURL')
|
|
457
|
+
>>> # Login to the server
|
|
458
|
+
|
|
459
|
+
>>> user = server.users.get_by_id('9f9e9d9c-8b8a-8f8e-7d7c-7b7a6f6d6e6d')
|
|
460
|
+
>>> server.users.populate_favorites(user)
|
|
461
|
+
>>> for obj_type, items in user.favorites.items():
|
|
462
|
+
>>> print(f"Favorites for {obj_type}:")
|
|
463
|
+
>>> for item in items:
|
|
464
|
+
>>> print(item.name)
|
|
465
|
+
"""
|
|
145
466
|
self.parent_srv.favorites.get(user_item)
|
|
146
467
|
|
|
147
468
|
# Get groups for user
|
|
148
469
|
@api(version="3.7")
|
|
149
470
|
def populate_groups(self, user_item: UserItem, req_options: Optional[RequestOptions] = None) -> None:
|
|
471
|
+
"""
|
|
472
|
+
Populate the groups for the user.
|
|
473
|
+
|
|
474
|
+
Parameters
|
|
475
|
+
----------
|
|
476
|
+
user_item : UserItem
|
|
477
|
+
The user item to populate groups for.
|
|
478
|
+
|
|
479
|
+
req_options : Optional[RequestOptions]
|
|
480
|
+
Optional request options to filter and sort the results.
|
|
481
|
+
|
|
482
|
+
Returns
|
|
483
|
+
-------
|
|
484
|
+
None
|
|
485
|
+
|
|
486
|
+
Raises
|
|
487
|
+
------
|
|
488
|
+
MissingRequiredFieldError
|
|
489
|
+
If the user item is missing an ID.
|
|
490
|
+
|
|
491
|
+
Examples
|
|
492
|
+
--------
|
|
493
|
+
>>> server.users.populate_groups(user)
|
|
494
|
+
>>> for group in user.groups:
|
|
495
|
+
>>> print(group.name)
|
|
496
|
+
"""
|
|
150
497
|
if not user_item.id:
|
|
151
498
|
error = "User item missing ID."
|
|
152
499
|
raise MissingRequiredFieldError(error)
|
|
@@ -161,10 +508,10 @@ class Users(QuerysetEndpoint[UserItem]):
|
|
|
161
508
|
|
|
162
509
|
def _get_groups_for_user(
|
|
163
510
|
self, user_item: UserItem, req_options: Optional[RequestOptions] = None
|
|
164
|
-
) ->
|
|
165
|
-
url = "{
|
|
511
|
+
) -> tuple[list[GroupItem], PaginationItem]:
|
|
512
|
+
url = f"{self.baseurl}/{user_item.id}/groups"
|
|
166
513
|
server_response = self.get_request(url, req_options)
|
|
167
|
-
logger.info("Populated groups for user (ID: {
|
|
514
|
+
logger.info(f"Populated groups for user (ID: {user_item.id})")
|
|
168
515
|
group_item = GroupItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
169
516
|
pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
170
517
|
return group_item, pagination_item
|
|
@@ -11,7 +11,8 @@ from tableauserverclient.models import ViewItem, PaginationItem
|
|
|
11
11
|
|
|
12
12
|
from tableauserverclient.helpers.logging import logger
|
|
13
13
|
|
|
14
|
-
from typing import
|
|
14
|
+
from typing import Optional, TYPE_CHECKING, Union
|
|
15
|
+
from collections.abc import Iterable, Iterator
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
17
18
|
from tableauserverclient.server.request_options import (
|
|
@@ -25,22 +26,22 @@ if TYPE_CHECKING:
|
|
|
25
26
|
|
|
26
27
|
class Views(QuerysetEndpoint[ViewItem], TaggingMixin[ViewItem]):
|
|
27
28
|
def __init__(self, parent_srv):
|
|
28
|
-
super(
|
|
29
|
+
super().__init__(parent_srv)
|
|
29
30
|
self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
|
|
30
31
|
|
|
31
32
|
# Used because populate_preview_image functionaliy requires workbook endpoint
|
|
32
33
|
@property
|
|
33
34
|
def siteurl(self) -> str:
|
|
34
|
-
return "{
|
|
35
|
+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}"
|
|
35
36
|
|
|
36
37
|
@property
|
|
37
38
|
def baseurl(self) -> str:
|
|
38
|
-
return "{
|
|
39
|
+
return f"{self.siteurl}/views"
|
|
39
40
|
|
|
40
41
|
@api(version="2.2")
|
|
41
42
|
def get(
|
|
42
43
|
self, req_options: Optional["RequestOptions"] = None, usage: bool = False
|
|
43
|
-
) ->
|
|
44
|
+
) -> tuple[list[ViewItem], PaginationItem]:
|
|
44
45
|
logger.info("Querying all views on site")
|
|
45
46
|
url = self.baseurl
|
|
46
47
|
if usage:
|
|
@@ -55,8 +56,8 @@ class Views(QuerysetEndpoint[ViewItem], TaggingMixin[ViewItem]):
|
|
|
55
56
|
if not view_id:
|
|
56
57
|
error = "View item missing ID."
|
|
57
58
|
raise MissingRequiredFieldError(error)
|
|
58
|
-
logger.info("Querying single view (ID: {
|
|
59
|
-
url = "{
|
|
59
|
+
logger.info(f"Querying single view (ID: {view_id})")
|
|
60
|
+
url = f"{self.baseurl}/{view_id}"
|
|
60
61
|
if usage:
|
|
61
62
|
url += "?includeUsageStatistics=true"
|
|
62
63
|
server_response = self.get_request(url)
|
|
@@ -72,10 +73,10 @@ class Views(QuerysetEndpoint[ViewItem], TaggingMixin[ViewItem]):
|
|
|
72
73
|
return self._get_preview_for_view(view_item)
|
|
73
74
|
|
|
74
75
|
view_item._set_preview_image(image_fetcher)
|
|
75
|
-
logger.info("Populated preview image for view (ID: {
|
|
76
|
+
logger.info(f"Populated preview image for view (ID: {view_item.id})")
|
|
76
77
|
|
|
77
78
|
def _get_preview_for_view(self, view_item: ViewItem) -> bytes:
|
|
78
|
-
url = "{
|
|
79
|
+
url = f"{self.siteurl}/workbooks/{view_item.workbook_id}/views/{view_item.id}/previewImage"
|
|
79
80
|
server_response = self.get_request(url)
|
|
80
81
|
image = server_response.content
|
|
81
82
|
return image
|
|
@@ -90,10 +91,10 @@ class Views(QuerysetEndpoint[ViewItem], TaggingMixin[ViewItem]):
|
|
|
90
91
|
return self._get_view_image(view_item, req_options)
|
|
91
92
|
|
|
92
93
|
view_item._set_image(image_fetcher)
|
|
93
|
-
logger.info("Populated image for view (ID: {
|
|
94
|
+
logger.info(f"Populated image for view (ID: {view_item.id})")
|
|
94
95
|
|
|
95
96
|
def _get_view_image(self, view_item: ViewItem, req_options: Optional["ImageRequestOptions"]) -> bytes:
|
|
96
|
-
url = "{
|
|
97
|
+
url = f"{self.baseurl}/{view_item.id}/image"
|
|
97
98
|
server_response = self.get_request(url, req_options)
|
|
98
99
|
image = server_response.content
|
|
99
100
|
return image
|
|
@@ -108,10 +109,10 @@ class Views(QuerysetEndpoint[ViewItem], TaggingMixin[ViewItem]):
|
|
|
108
109
|
return self._get_view_pdf(view_item, req_options)
|
|
109
110
|
|
|
110
111
|
view_item._set_pdf(pdf_fetcher)
|
|
111
|
-
logger.info("Populated pdf for view (ID: {
|
|
112
|
+
logger.info(f"Populated pdf for view (ID: {view_item.id})")
|
|
112
113
|
|
|
113
114
|
def _get_view_pdf(self, view_item: ViewItem, req_options: Optional["PDFRequestOptions"]) -> bytes:
|
|
114
|
-
url = "{
|
|
115
|
+
url = f"{self.baseurl}/{view_item.id}/pdf"
|
|
115
116
|
server_response = self.get_request(url, req_options)
|
|
116
117
|
pdf = server_response.content
|
|
117
118
|
return pdf
|
|
@@ -126,10 +127,10 @@ class Views(QuerysetEndpoint[ViewItem], TaggingMixin[ViewItem]):
|
|
|
126
127
|
return self._get_view_csv(view_item, req_options)
|
|
127
128
|
|
|
128
129
|
view_item._set_csv(csv_fetcher)
|
|
129
|
-
logger.info("Populated csv for view (ID: {
|
|
130
|
+
logger.info(f"Populated csv for view (ID: {view_item.id})")
|
|
130
131
|
|
|
131
132
|
def _get_view_csv(self, view_item: ViewItem, req_options: Optional["CSVRequestOptions"]) -> Iterator[bytes]:
|
|
132
|
-
url = "{
|
|
133
|
+
url = f"{self.baseurl}/{view_item.id}/data"
|
|
133
134
|
|
|
134
135
|
with closing(self.get_request(url, request_object=req_options, parameters={"stream": True})) as server_response:
|
|
135
136
|
yield from server_response.iter_content(1024)
|
|
@@ -144,10 +145,10 @@ class Views(QuerysetEndpoint[ViewItem], TaggingMixin[ViewItem]):
|
|
|
144
145
|
return self._get_view_excel(view_item, req_options)
|
|
145
146
|
|
|
146
147
|
view_item._set_excel(excel_fetcher)
|
|
147
|
-
logger.info("Populated excel for view (ID: {
|
|
148
|
+
logger.info(f"Populated excel for view (ID: {view_item.id})")
|
|
148
149
|
|
|
149
150
|
def _get_view_excel(self, view_item: ViewItem, req_options: Optional["ExcelRequestOptions"]) -> Iterator[bytes]:
|
|
150
|
-
url = "{
|
|
151
|
+
url = f"{self.baseurl}/{view_item.id}/crosstab/excel"
|
|
151
152
|
|
|
152
153
|
with closing(self.get_request(url, request_object=req_options, parameters={"stream": True})) as server_response:
|
|
153
154
|
yield from server_response.iter_content(1024)
|
|
@@ -176,7 +177,7 @@ class Views(QuerysetEndpoint[ViewItem], TaggingMixin[ViewItem]):
|
|
|
176
177
|
return view_item
|
|
177
178
|
|
|
178
179
|
@api(version="1.0")
|
|
179
|
-
def add_tags(self, item: Union[ViewItem, str], tags: Union[Iterable[str], str]) ->
|
|
180
|
+
def add_tags(self, item: Union[ViewItem, str], tags: Union[Iterable[str], str]) -> set[str]:
|
|
180
181
|
return super().add_tags(item, tags)
|
|
181
182
|
|
|
182
183
|
@api(version="1.0")
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from functools import partial
|
|
2
2
|
import json
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Optional, TYPE_CHECKING, Union
|
|
5
|
+
from collections.abc import Iterable
|
|
5
6
|
|
|
6
7
|
from tableauserverclient.models.connection_item import ConnectionItem
|
|
7
8
|
from tableauserverclient.models.pagination_item import PaginationItem
|
|
@@ -28,7 +29,7 @@ class VirtualConnections(QuerysetEndpoint[VirtualConnectionItem], TaggingMixin):
|
|
|
28
29
|
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/virtualConnections"
|
|
29
30
|
|
|
30
31
|
@api(version="3.18")
|
|
31
|
-
def get(self, req_options: Optional[RequestOptions] = None) ->
|
|
32
|
+
def get(self, req_options: Optional[RequestOptions] = None) -> tuple[list[VirtualConnectionItem], PaginationItem]:
|
|
32
33
|
server_response = self.get_request(self.baseurl, req_options)
|
|
33
34
|
pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
34
35
|
virtual_connections = VirtualConnectionItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
@@ -44,7 +45,7 @@ class VirtualConnections(QuerysetEndpoint[VirtualConnectionItem], TaggingMixin):
|
|
|
44
45
|
|
|
45
46
|
def _get_virtual_database_connections(
|
|
46
47
|
self, virtual_connection: VirtualConnectionItem, req_options: Optional[RequestOptions] = None
|
|
47
|
-
) ->
|
|
48
|
+
) -> tuple[list[ConnectionItem], PaginationItem]:
|
|
48
49
|
server_response = self.get_request(f"{self.baseurl}/{virtual_connection.id}/connections", req_options)
|
|
49
50
|
connections = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
50
51
|
pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
@@ -83,7 +84,7 @@ class VirtualConnections(QuerysetEndpoint[VirtualConnectionItem], TaggingMixin):
|
|
|
83
84
|
@api(version="3.23")
|
|
84
85
|
def get_revisions(
|
|
85
86
|
self, virtual_connection: VirtualConnectionItem, req_options: Optional[RequestOptions] = None
|
|
86
|
-
) ->
|
|
87
|
+
) -> tuple[list[RevisionItem], PaginationItem]:
|
|
87
88
|
server_response = self.get_request(f"{self.baseurl}/{virtual_connection.id}/revisions", req_options)
|
|
88
89
|
pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
89
90
|
revisions = RevisionItem.from_response(server_response.content, self.parent_srv.namespace, virtual_connection)
|
|
@@ -159,7 +160,7 @@ class VirtualConnections(QuerysetEndpoint[VirtualConnectionItem], TaggingMixin):
|
|
|
159
160
|
@api(version="3.23")
|
|
160
161
|
def add_tags(
|
|
161
162
|
self, virtual_connection: Union[VirtualConnectionItem, str], tags: Union[Iterable[str], str]
|
|
162
|
-
) ->
|
|
163
|
+
) -> set[str]:
|
|
163
164
|
return super().add_tags(virtual_connection, tags)
|
|
164
165
|
|
|
165
166
|
@api(version="3.23")
|