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