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.
Files changed (94) hide show
  1. tableauserverclient/__init__.py +28 -22
  2. tableauserverclient/_version.py +3 -3
  3. tableauserverclient/config.py +5 -3
  4. tableauserverclient/models/column_item.py +1 -1
  5. tableauserverclient/models/connection_credentials.py +1 -1
  6. tableauserverclient/models/connection_item.py +6 -6
  7. tableauserverclient/models/custom_view_item.py +29 -6
  8. tableauserverclient/models/data_acceleration_report_item.py +2 -2
  9. tableauserverclient/models/data_alert_item.py +5 -5
  10. tableauserverclient/models/data_freshness_policy_item.py +6 -6
  11. tableauserverclient/models/database_item.py +3 -3
  12. tableauserverclient/models/datasource_item.py +10 -10
  13. tableauserverclient/models/dqw_item.py +1 -1
  14. tableauserverclient/models/favorites_item.py +5 -6
  15. tableauserverclient/models/fileupload_item.py +1 -1
  16. tableauserverclient/models/flow_item.py +6 -6
  17. tableauserverclient/models/flow_run_item.py +3 -3
  18. tableauserverclient/models/group_item.py +4 -4
  19. tableauserverclient/models/groupset_item.py +4 -4
  20. tableauserverclient/models/interval_item.py +9 -9
  21. tableauserverclient/models/job_item.py +8 -8
  22. tableauserverclient/models/linked_tasks_item.py +5 -5
  23. tableauserverclient/models/metric_item.py +5 -5
  24. tableauserverclient/models/pagination_item.py +1 -1
  25. tableauserverclient/models/permissions_item.py +12 -10
  26. tableauserverclient/models/project_item.py +35 -19
  27. tableauserverclient/models/property_decorators.py +12 -11
  28. tableauserverclient/models/reference_item.py +2 -2
  29. tableauserverclient/models/revision_item.py +3 -3
  30. tableauserverclient/models/schedule_item.py +2 -2
  31. tableauserverclient/models/server_info_item.py +26 -6
  32. tableauserverclient/models/site_item.py +69 -3
  33. tableauserverclient/models/subscription_item.py +3 -3
  34. tableauserverclient/models/table_item.py +1 -1
  35. tableauserverclient/models/tableau_auth.py +115 -5
  36. tableauserverclient/models/tableau_types.py +2 -2
  37. tableauserverclient/models/tag_item.py +3 -4
  38. tableauserverclient/models/task_item.py +4 -4
  39. tableauserverclient/models/user_item.py +47 -17
  40. tableauserverclient/models/view_item.py +11 -10
  41. tableauserverclient/models/virtual_connection_item.py +6 -5
  42. tableauserverclient/models/webhook_item.py +6 -6
  43. tableauserverclient/models/workbook_item.py +90 -12
  44. tableauserverclient/namespace.py +1 -1
  45. tableauserverclient/server/__init__.py +2 -1
  46. tableauserverclient/server/endpoint/auth_endpoint.py +65 -8
  47. tableauserverclient/server/endpoint/custom_views_endpoint.py +62 -18
  48. tableauserverclient/server/endpoint/data_acceleration_report_endpoint.py +2 -2
  49. tableauserverclient/server/endpoint/data_alert_endpoint.py +14 -14
  50. tableauserverclient/server/endpoint/databases_endpoint.py +13 -12
  51. tableauserverclient/server/endpoint/datasources_endpoint.py +49 -54
  52. tableauserverclient/server/endpoint/default_permissions_endpoint.py +19 -18
  53. tableauserverclient/server/endpoint/dqw_endpoint.py +9 -9
  54. tableauserverclient/server/endpoint/endpoint.py +19 -21
  55. tableauserverclient/server/endpoint/exceptions.py +23 -7
  56. tableauserverclient/server/endpoint/favorites_endpoint.py +31 -31
  57. tableauserverclient/server/endpoint/fileuploads_endpoint.py +9 -11
  58. tableauserverclient/server/endpoint/flow_runs_endpoint.py +15 -13
  59. tableauserverclient/server/endpoint/flow_task_endpoint.py +2 -2
  60. tableauserverclient/server/endpoint/flows_endpoint.py +30 -29
  61. tableauserverclient/server/endpoint/groups_endpoint.py +18 -17
  62. tableauserverclient/server/endpoint/groupsets_endpoint.py +2 -2
  63. tableauserverclient/server/endpoint/jobs_endpoint.py +7 -7
  64. tableauserverclient/server/endpoint/linked_tasks_endpoint.py +2 -2
  65. tableauserverclient/server/endpoint/metadata_endpoint.py +2 -2
  66. tableauserverclient/server/endpoint/metrics_endpoint.py +10 -10
  67. tableauserverclient/server/endpoint/permissions_endpoint.py +13 -15
  68. tableauserverclient/server/endpoint/projects_endpoint.py +81 -30
  69. tableauserverclient/server/endpoint/resource_tagger.py +14 -13
  70. tableauserverclient/server/endpoint/schedules_endpoint.py +17 -18
  71. tableauserverclient/server/endpoint/server_info_endpoint.py +40 -5
  72. tableauserverclient/server/endpoint/sites_endpoint.py +282 -17
  73. tableauserverclient/server/endpoint/subscriptions_endpoint.py +10 -10
  74. tableauserverclient/server/endpoint/tables_endpoint.py +15 -14
  75. tableauserverclient/server/endpoint/tasks_endpoint.py +8 -8
  76. tableauserverclient/server/endpoint/users_endpoint.py +366 -19
  77. tableauserverclient/server/endpoint/views_endpoint.py +19 -18
  78. tableauserverclient/server/endpoint/virtual_connections_endpoint.py +6 -5
  79. tableauserverclient/server/endpoint/webhooks_endpoint.py +11 -11
  80. tableauserverclient/server/endpoint/workbooks_endpoint.py +647 -61
  81. tableauserverclient/server/filter.py +2 -2
  82. tableauserverclient/server/pager.py +5 -6
  83. tableauserverclient/server/query.py +68 -19
  84. tableauserverclient/server/request_factory.py +37 -36
  85. tableauserverclient/server/request_options.py +123 -145
  86. tableauserverclient/server/server.py +65 -9
  87. tableauserverclient/server/sort.py +2 -2
  88. {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/METADATA +6 -6
  89. tableauserverclient-0.34.dist-info/RECORD +106 -0
  90. {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/WHEEL +1 -1
  91. tableauserverclient-0.33.dist-info/RECORD +0 -106
  92. {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/LICENSE +0 -0
  93. {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/LICENSE.versioneer +0 -0
  94. {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 List, Optional, Tuple
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 "{0}/sites/{1}/users".format(self.parent_srv.baseurl, self.parent_srv.site_id)
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) -> Tuple[List[UserItem], PaginationItem]:
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: {0})".format(user_id))
43
- url = "{0}/{1}".format(self.baseurl, user_id)
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 = "{0}/{1}".format(self.baseurl, user_item.id)
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: {0})".format(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 = "{0}/{1}".format(self.baseurl, user_id)
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: {0})".format(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 {}".format(user_item.name))
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: {0})".format(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: List[UserItem]):
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) -> Tuple[List[UserItem], List[Tuple[UserItem, ServerResponseError]]]:
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
- ) -> Tuple[List[WorkbookItem], PaginationItem]:
137
- url = "{0}/{1}/workbooks".format(self.baseurl, user_item.id)
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: {0})".format(user_item.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
- ) -> Tuple[List[GroupItem], PaginationItem]:
165
- url = "{0}/{1}/groups".format(self.baseurl, user_item.id)
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: {0})".format(user_item.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 Iterable, Iterator, List, Optional, Set, Tuple, TYPE_CHECKING, Union
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(Views, self).__init__(parent_srv)
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 "{0}/sites/{1}".format(self.parent_srv.baseurl, self.parent_srv.site_id)
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 "{0}/views".format(self.siteurl)
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
- ) -> Tuple[List[ViewItem], PaginationItem]:
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: {0})".format(view_id))
59
- url = "{0}/{1}".format(self.baseurl, view_id)
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: {0})".format(view_item.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 = "{0}/workbooks/{1}/views/{2}/previewImage".format(self.siteurl, view_item.workbook_id, view_item.id)
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: {0})".format(view_item.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 = "{0}/{1}/image".format(self.baseurl, view_item.id)
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: {0})".format(view_item.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 = "{0}/{1}/pdf".format(self.baseurl, view_item.id)
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: {0})".format(view_item.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 = "{0}/{1}/data".format(self.baseurl, view_item.id)
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: {0})".format(view_item.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 = "{0}/{1}/crosstab/excel".format(self.baseurl, view_item.id)
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]) -> Set[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 Iterable, List, Optional, Set, TYPE_CHECKING, Tuple, Union
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) -> Tuple[List[VirtualConnectionItem], PaginationItem]:
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
- ) -> Tuple[List[ConnectionItem], PaginationItem]:
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
- ) -> Tuple[List[RevisionItem], PaginationItem]:
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
- ) -> Set[str]:
163
+ ) -> set[str]:
163
164
  return super().add_tags(virtual_connection, tags)
164
165
 
165
166
  @api(version="3.23")