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
@@ -8,12 +8,9 @@ from xml.etree.ElementTree import ParseError
8
8
  from typing import (
9
9
  Any,
10
10
  Callable,
11
- Dict,
12
11
  Generic,
13
- List,
14
12
  Optional,
15
13
  TYPE_CHECKING,
16
- Tuple,
17
14
  TypeVar,
18
15
  Union,
19
16
  )
@@ -22,6 +19,7 @@ from tableauserverclient.models.pagination_item import PaginationItem
22
19
  from tableauserverclient.server.request_options import RequestOptions
23
20
 
24
21
  from tableauserverclient.server.endpoint.exceptions import (
22
+ FailedSignInError,
25
23
  ServerResponseError,
26
24
  InternalServerError,
27
25
  NonXMLResponseError,
@@ -56,7 +54,7 @@ class Endpoint:
56
54
  async_response = None
57
55
 
58
56
  @staticmethod
59
- def set_parameters(http_options, auth_token, content, content_type, parameters) -> Dict[str, Any]:
57
+ def set_parameters(http_options, auth_token, content, content_type, parameters) -> dict[str, Any]:
60
58
  parameters = parameters or {}
61
59
  parameters.update(http_options)
62
60
  if "headers" not in parameters:
@@ -82,7 +80,7 @@ class Endpoint:
82
80
  else:
83
81
  # only set the TSC user agent if not already populated
84
82
  _client_version: Optional[str] = get_versions()["version"]
85
- parameters["headers"][USER_AGENT_HEADER] = "Tableau Server Client/{}".format(_client_version)
83
+ parameters["headers"][USER_AGENT_HEADER] = f"Tableau Server Client/{_client_version}"
86
84
 
87
85
  # result: parameters["headers"]["User-Agent"] is set
88
86
  # return explicitly for testing only
@@ -90,12 +88,12 @@ class Endpoint:
90
88
 
91
89
  def _blocking_request(self, method, url, parameters={}) -> Optional[Union["Response", Exception]]:
92
90
  response = None
93
- logger.debug("[{}] Begin blocking request to {}".format(datetime.timestamp(), url))
91
+ logger.debug(f"[{datetime.timestamp()}] Begin blocking request to {url}")
94
92
  try:
95
93
  response = method(url, **parameters)
96
- logger.debug("[{}] Call finished".format(datetime.timestamp()))
94
+ logger.debug(f"[{datetime.timestamp()}] Call finished")
97
95
  except Exception as e:
98
- logger.debug("Error making request to server: {}".format(e))
96
+ logger.debug(f"Error making request to server: {e}")
99
97
  raise e
100
98
  return response
101
99
 
@@ -111,13 +109,13 @@ class Endpoint:
111
109
  content: Optional[bytes] = None,
112
110
  auth_token: Optional[str] = None,
113
111
  content_type: Optional[str] = None,
114
- parameters: Optional[Dict[str, Any]] = None,
112
+ parameters: Optional[dict[str, Any]] = None,
115
113
  ) -> "Response":
116
114
  parameters = Endpoint.set_parameters(
117
115
  self.parent_srv.http_options, auth_token, content, content_type, parameters
118
116
  )
119
117
 
120
- logger.debug("request method {}, url: {}".format(method.__name__, url))
118
+ logger.debug(f"request method {method.__name__}, url: {url}")
121
119
  if content:
122
120
  redacted = helpers.strings.redact_xml(content[:200])
123
121
  # this needs to be under a trace or something, it's a LOT
@@ -129,21 +127,21 @@ class Endpoint:
129
127
  server_response: Optional[Union["Response", Exception]] = self.send_request_while_show_progress_threaded(
130
128
  method, url, parameters, request_timeout
131
129
  )
132
- logger.debug("[{}] Async request returned: received {}".format(datetime.timestamp(), server_response))
130
+ logger.debug(f"[{datetime.timestamp()}] Async request returned: received {server_response}")
133
131
  # is this blocking retry really necessary? I guess if it was just the threading messing it up?
134
132
  if server_response is None:
135
133
  logger.debug(server_response)
136
- logger.debug("[{}] Async request failed: retrying".format(datetime.timestamp()))
134
+ logger.debug(f"[{datetime.timestamp()}] Async request failed: retrying")
137
135
  server_response = self._blocking_request(method, url, parameters)
138
136
  if server_response is None:
139
- logger.debug("[{}] Request failed".format(datetime.timestamp()))
137
+ logger.debug(f"[{datetime.timestamp()}] Request failed")
140
138
  raise RuntimeError
141
139
  if isinstance(server_response, Exception):
142
140
  raise server_response
143
141
  self._check_status(server_response, url)
144
142
 
145
143
  loggable_response = self.log_response_safely(server_response)
146
- logger.debug("Server response from {0}".format(url))
144
+ logger.debug(f"Server response from {url}")
147
145
  # uncomment the following to log full responses in debug mode
148
146
  # BE CAREFUL WHEN SHARING THESE RESULTS - MAY CONTAIN YOUR SENSITIVE DATA
149
147
  # logger.debug(loggable_response)
@@ -154,16 +152,16 @@ class Endpoint:
154
152
  return server_response
155
153
 
156
154
  def _check_status(self, server_response: "Response", url: Optional[str] = None):
157
- logger.debug("Response status: {}".format(server_response))
155
+ logger.debug(f"Response status: {server_response}")
158
156
  if not hasattr(server_response, "status_code"):
159
- raise EnvironmentError("Response is not a http response?")
157
+ raise OSError("Response is not a http response?")
160
158
  if server_response.status_code >= 500:
161
159
  raise InternalServerError(server_response, url)
162
160
  elif server_response.status_code not in Success_codes:
163
161
  try:
164
162
  if server_response.status_code == 401:
165
163
  # TODO: catch this in server.py and attempt to sign in again, in case it's a session expiry
166
- raise NotSignedInError(server_response.content, url)
164
+ raise FailedSignInError.from_response(server_response.content, self.parent_srv.namespace, url)
167
165
 
168
166
  raise ServerResponseError.from_response(server_response.content, self.parent_srv.namespace, url)
169
167
  except ParseError:
@@ -183,9 +181,9 @@ class Endpoint:
183
181
  # content-type is an octet-stream accomplishes the same goal without eagerly loading content.
184
182
  # This check is to determine if the response is a text response (xml or otherwise)
185
183
  # so that we do not attempt to log bytes and other binary data.
186
- loggable_response = "Content type `{}`".format(content_type)
184
+ loggable_response = f"Content type `{content_type}`"
187
185
  if content_type == "application/octet-stream":
188
- loggable_response = "A stream of type {} [Truncated File Contents]".format(content_type)
186
+ loggable_response = f"A stream of type {content_type} [Truncated File Contents]"
189
187
  elif server_response.encoding and len(server_response.content) > 0:
190
188
  loggable_response = helpers.strings.redact_xml(server_response.content.decode(server_response.encoding))
191
189
  return loggable_response
@@ -313,7 +311,7 @@ def parameter_added_in(**params: str) -> Callable[[Callable[Concatenate[E, P], R
313
311
  for p in params_to_check:
314
312
  min_ver = Version(str(params[p]))
315
313
  if server_ver < min_ver:
316
- error = "{!r} not available in {}, it will be ignored. Added in {}".format(p, server_ver, min_ver)
314
+ error = f"{p!r} not available in {server_ver}, it will be ignored. Added in {min_ver}"
317
315
  warnings.warn(error)
318
316
  return func(self, *args, **kwargs)
319
317
 
@@ -353,5 +351,5 @@ class QuerysetEndpoint(Endpoint, Generic[T]):
353
351
  return queryset
354
352
 
355
353
  @abc.abstractmethod
356
- def get(self, request_options: Optional[RequestOptions] = None) -> Tuple[List[T], PaginationItem]:
354
+ def get(self, request_options: Optional[RequestOptions] = None) -> tuple[list[T], PaginationItem]:
357
355
  raise NotImplementedError(f".get has not been implemented for {self.__class__.__qualname__}")
@@ -1,24 +1,31 @@
1
1
  from defusedxml.ElementTree import fromstring
2
- from typing import Optional
2
+ from typing import Mapping, Optional, TypeVar
3
+
4
+
5
+ def split_pascal_case(s: str) -> str:
6
+ return "".join([f" {c}" if c.isupper() else c for c in s]).strip()
3
7
 
4
8
 
5
9
  class TableauError(Exception):
6
10
  pass
7
11
 
8
12
 
9
- class ServerResponseError(TableauError):
10
- def __init__(self, code, summary, detail, url=None):
13
+ T = TypeVar("T")
14
+
15
+
16
+ class XMLError(TableauError):
17
+ def __init__(self, code: str, summary: str, detail: str, url: Optional[str] = None) -> None:
11
18
  self.code = code
12
19
  self.summary = summary
13
20
  self.detail = detail
14
21
  self.url = url
15
- super(ServerResponseError, self).__init__(str(self))
22
+ super().__init__(str(self))
16
23
 
17
24
  def __str__(self):
18
- return "\n\n\t{0}: {1}\n\t\t{2}".format(self.code, self.summary, self.detail)
25
+ return f"\n\n\t{self.code}: {self.summary}\n\t\t{self.detail}"
19
26
 
20
27
  @classmethod
21
- def from_response(cls, resp, ns, url=None):
28
+ def from_response(cls, resp, ns, url):
22
29
  # Check elements exist before .text
23
30
  parsed_response = fromstring(resp)
24
31
  try:
@@ -33,6 +40,10 @@ class ServerResponseError(TableauError):
33
40
  return error_response
34
41
 
35
42
 
43
+ class ServerResponseError(XMLError):
44
+ pass
45
+
46
+
36
47
  class InternalServerError(TableauError):
37
48
  def __init__(self, server_response, request_url: Optional[str] = None):
38
49
  self.code = server_response.status_code
@@ -40,7 +51,7 @@ class InternalServerError(TableauError):
40
51
  self.url = request_url or "server"
41
52
 
42
53
  def __str__(self):
43
- return "\n\nInternal error {0} at {1}\n{2}".format(self.code, self.url, self.content)
54
+ return f"\n\nInternal error {self.code} at {self.url}\n{self.content}"
44
55
 
45
56
 
46
57
  class MissingRequiredFieldError(TableauError):
@@ -51,6 +62,11 @@ class NotSignedInError(TableauError):
51
62
  pass
52
63
 
53
64
 
65
+ class FailedSignInError(XMLError, NotSignedInError):
66
+ def __str__(self):
67
+ return f"{split_pascal_case(self.__class__.__name__)}: {super().__str__()}"
68
+
69
+
54
70
  class ItemTypeNotAllowed(TableauError):
55
71
  pass
56
72
 
@@ -20,13 +20,13 @@ from typing import Optional
20
20
  class Favorites(Endpoint):
21
21
  @property
22
22
  def baseurl(self) -> str:
23
- return "{0}/sites/{1}/favorites".format(self.parent_srv.baseurl, self.parent_srv.site_id)
23
+ return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/favorites"
24
24
 
25
25
  # Gets all favorites
26
26
  @api(version="2.5")
27
27
  def get(self, user_item: UserItem, req_options: Optional[RequestOptions] = None) -> None:
28
- logger.info("Querying all favorites for user {0}".format(user_item.name))
29
- url = "{0}/{1}".format(self.baseurl, user_item.id)
28
+ logger.info(f"Querying all favorites for user {user_item.name}")
29
+ url = f"{self.baseurl}/{user_item.id}"
30
30
  server_response = self.get_request(url, req_options)
31
31
  user_item._favorites = FavoriteItem.from_response(server_response.content, self.parent_srv.namespace)
32
32
 
@@ -34,53 +34,53 @@ class Favorites(Endpoint):
34
34
 
35
35
  @api(version="3.15")
36
36
  def add_favorite(self, user_item: UserItem, content_type: str, item: TableauItem) -> "Response":
37
- url = "{0}/{1}".format(self.baseurl, user_item.id)
37
+ url = f"{self.baseurl}/{user_item.id}"
38
38
  add_req = RequestFactory.Favorite.add_request(item.id, content_type, item.name)
39
39
  server_response = self.put_request(url, add_req)
40
- logger.info("Favorited {0} for user (ID: {1})".format(item.name, user_item.id))
40
+ logger.info(f"Favorited {item.name} for user (ID: {user_item.id})")
41
41
  return server_response
42
42
 
43
43
  @api(version="2.0")
44
44
  def add_favorite_workbook(self, user_item: UserItem, workbook_item: WorkbookItem) -> None:
45
- url = "{0}/{1}".format(self.baseurl, user_item.id)
45
+ url = f"{self.baseurl}/{user_item.id}"
46
46
  add_req = RequestFactory.Favorite.add_workbook_req(workbook_item.id, workbook_item.name)
47
47
  server_response = self.put_request(url, add_req)
48
- logger.info("Favorited {0} for user (ID: {1})".format(workbook_item.name, user_item.id))
48
+ logger.info(f"Favorited {workbook_item.name} for user (ID: {user_item.id})")
49
49
 
50
50
  @api(version="2.0")
51
51
  def add_favorite_view(self, user_item: UserItem, view_item: ViewItem) -> None:
52
- url = "{0}/{1}".format(self.baseurl, user_item.id)
52
+ url = f"{self.baseurl}/{user_item.id}"
53
53
  add_req = RequestFactory.Favorite.add_view_req(view_item.id, view_item.name)
54
54
  server_response = self.put_request(url, add_req)
55
- logger.info("Favorited {0} for user (ID: {1})".format(view_item.name, user_item.id))
55
+ logger.info(f"Favorited {view_item.name} for user (ID: {user_item.id})")
56
56
 
57
57
  @api(version="2.3")
58
58
  def add_favorite_datasource(self, user_item: UserItem, datasource_item: DatasourceItem) -> None:
59
- url = "{0}/{1}".format(self.baseurl, user_item.id)
59
+ url = f"{self.baseurl}/{user_item.id}"
60
60
  add_req = RequestFactory.Favorite.add_datasource_req(datasource_item.id, datasource_item.name)
61
61
  server_response = self.put_request(url, add_req)
62
- logger.info("Favorited {0} for user (ID: {1})".format(datasource_item.name, user_item.id))
62
+ logger.info(f"Favorited {datasource_item.name} for user (ID: {user_item.id})")
63
63
 
64
64
  @api(version="3.1")
65
65
  def add_favorite_project(self, user_item: UserItem, project_item: ProjectItem) -> None:
66
- url = "{0}/{1}".format(self.baseurl, user_item.id)
66
+ url = f"{self.baseurl}/{user_item.id}"
67
67
  add_req = RequestFactory.Favorite.add_project_req(project_item.id, project_item.name)
68
68
  server_response = self.put_request(url, add_req)
69
- logger.info("Favorited {0} for user (ID: {1})".format(project_item.name, user_item.id))
69
+ logger.info(f"Favorited {project_item.name} for user (ID: {user_item.id})")
70
70
 
71
71
  @api(version="3.3")
72
72
  def add_favorite_flow(self, user_item: UserItem, flow_item: FlowItem) -> None:
73
- url = "{0}/{1}".format(self.baseurl, user_item.id)
73
+ url = f"{self.baseurl}/{user_item.id}"
74
74
  add_req = RequestFactory.Favorite.add_flow_req(flow_item.id, flow_item.name)
75
75
  server_response = self.put_request(url, add_req)
76
- logger.info("Favorited {0} for user (ID: {1})".format(flow_item.name, user_item.id))
76
+ logger.info(f"Favorited {flow_item.name} for user (ID: {user_item.id})")
77
77
 
78
78
  @api(version="3.3")
79
79
  def add_favorite_metric(self, user_item: UserItem, metric_item: MetricItem) -> None:
80
- url = "{0}/{1}".format(self.baseurl, user_item.id)
80
+ url = f"{self.baseurl}/{user_item.id}"
81
81
  add_req = RequestFactory.Favorite.add_request(metric_item.id, Resource.Metric, metric_item.name)
82
82
  server_response = self.put_request(url, add_req)
83
- logger.info("Favorited metric {0} for user (ID: {1})".format(metric_item.name, user_item.id))
83
+ logger.info(f"Favorited metric {metric_item.name} for user (ID: {user_item.id})")
84
84
 
85
85
  # ------- delete from favorites
86
86
  # Response:
@@ -94,42 +94,42 @@ class Favorites(Endpoint):
94
94
 
95
95
  @api(version="3.15")
96
96
  def delete_favorite(self, user_item: UserItem, content_type: Resource, item: TableauItem) -> None:
97
- url = "{0}/{1}/{2}/{3}".format(self.baseurl, user_item.id, content_type, item.id)
98
- logger.info("Removing favorite {0}({1}) for user (ID: {2})".format(content_type, item.id, user_item.id))
97
+ url = f"{self.baseurl}/{user_item.id}/{content_type}/{item.id}"
98
+ logger.info(f"Removing favorite {content_type}({item.id}) for user (ID: {user_item.id})")
99
99
  self.delete_request(url)
100
100
 
101
101
  @api(version="2.0")
102
102
  def delete_favorite_workbook(self, user_item: UserItem, workbook_item: WorkbookItem) -> None:
103
- url = "{0}/{1}/workbooks/{2}".format(self.baseurl, user_item.id, workbook_item.id)
104
- logger.info("Removing favorite workbook {0} for user (ID: {1})".format(workbook_item.id, user_item.id))
103
+ url = f"{self.baseurl}/{user_item.id}/workbooks/{workbook_item.id}"
104
+ logger.info(f"Removing favorite workbook {workbook_item.id} for user (ID: {user_item.id})")
105
105
  self.delete_request(url)
106
106
 
107
107
  @api(version="2.0")
108
108
  def delete_favorite_view(self, user_item: UserItem, view_item: ViewItem) -> None:
109
- url = "{0}/{1}/views/{2}".format(self.baseurl, user_item.id, view_item.id)
110
- logger.info("Removing favorite view {0} for user (ID: {1})".format(view_item.id, user_item.id))
109
+ url = f"{self.baseurl}/{user_item.id}/views/{view_item.id}"
110
+ logger.info(f"Removing favorite view {view_item.id} for user (ID: {user_item.id})")
111
111
  self.delete_request(url)
112
112
 
113
113
  @api(version="2.3")
114
114
  def delete_favorite_datasource(self, user_item: UserItem, datasource_item: DatasourceItem) -> None:
115
- url = "{0}/{1}/datasources/{2}".format(self.baseurl, user_item.id, datasource_item.id)
116
- logger.info("Removing favorite {0} for user (ID: {1})".format(datasource_item.id, user_item.id))
115
+ url = f"{self.baseurl}/{user_item.id}/datasources/{datasource_item.id}"
116
+ logger.info(f"Removing favorite {datasource_item.id} for user (ID: {user_item.id})")
117
117
  self.delete_request(url)
118
118
 
119
119
  @api(version="3.1")
120
120
  def delete_favorite_project(self, user_item: UserItem, project_item: ProjectItem) -> None:
121
- url = "{0}/{1}/projects/{2}".format(self.baseurl, user_item.id, project_item.id)
122
- logger.info("Removing favorite project {0} for user (ID: {1})".format(project_item.id, user_item.id))
121
+ url = f"{self.baseurl}/{user_item.id}/projects/{project_item.id}"
122
+ logger.info(f"Removing favorite project {project_item.id} for user (ID: {user_item.id})")
123
123
  self.delete_request(url)
124
124
 
125
125
  @api(version="3.3")
126
126
  def delete_favorite_flow(self, user_item: UserItem, flow_item: FlowItem) -> None:
127
- url = "{0}/{1}/flows/{2}".format(self.baseurl, user_item.id, flow_item.id)
128
- logger.info("Removing favorite flow {0} for user (ID: {1})".format(flow_item.id, user_item.id))
127
+ url = f"{self.baseurl}/{user_item.id}/flows/{flow_item.id}"
128
+ logger.info(f"Removing favorite flow {flow_item.id} for user (ID: {user_item.id})")
129
129
  self.delete_request(url)
130
130
 
131
131
  @api(version="3.15")
132
132
  def delete_favorite_metric(self, user_item: UserItem, metric_item: MetricItem) -> None:
133
- url = "{0}/{1}/metrics/{2}".format(self.baseurl, user_item.id, metric_item.id)
134
- logger.info("Removing favorite metric {0} for user (ID: {1})".format(metric_item.id, user_item.id))
133
+ url = f"{self.baseurl}/{user_item.id}/metrics/{metric_item.id}"
134
+ logger.info(f"Removing favorite metric {metric_item.id} for user (ID: {user_item.id})")
135
135
  self.delete_request(url)
@@ -9,11 +9,11 @@ from tableauserverclient.server import RequestFactory
9
9
 
10
10
  class Fileuploads(Endpoint):
11
11
  def __init__(self, parent_srv):
12
- super(Fileuploads, self).__init__(parent_srv)
12
+ super().__init__(parent_srv)
13
13
 
14
14
  @property
15
15
  def baseurl(self):
16
- return "{0}/sites/{1}/fileUploads".format(self.parent_srv.baseurl, self.parent_srv.site_id)
16
+ return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/fileUploads"
17
17
 
18
18
  @api(version="2.0")
19
19
  def initiate(self):
@@ -21,14 +21,14 @@ class Fileuploads(Endpoint):
21
21
  server_response = self.post_request(url, "")
22
22
  fileupload_item = FileuploadItem.from_response(server_response.content, self.parent_srv.namespace)
23
23
  upload_id = fileupload_item.upload_session_id
24
- logger.info("Initiated file upload session (ID: {0})".format(upload_id))
24
+ logger.info(f"Initiated file upload session (ID: {upload_id})")
25
25
  return upload_id
26
26
 
27
27
  @api(version="2.0")
28
28
  def append(self, upload_id, data, content_type):
29
- url = "{0}/{1}".format(self.baseurl, upload_id)
29
+ url = f"{self.baseurl}/{upload_id}"
30
30
  server_response = self.put_request(url, data, content_type)
31
- logger.info("Uploading a chunk to session (ID: {0})".format(upload_id))
31
+ logger.info(f"Uploading a chunk to session (ID: {upload_id})")
32
32
  return FileuploadItem.from_response(server_response.content, self.parent_srv.namespace)
33
33
 
34
34
  def _read_chunks(self, file):
@@ -52,12 +52,10 @@ class Fileuploads(Endpoint):
52
52
  def upload(self, file):
53
53
  upload_id = self.initiate()
54
54
  for chunk in self._read_chunks(file):
55
- logger.debug("{} processing chunk...".format(datetime.timestamp()))
55
+ logger.debug(f"{datetime.timestamp()} processing chunk...")
56
56
  request, content_type = RequestFactory.Fileupload.chunk_req(chunk)
57
- logger.debug("{} created chunk request".format(datetime.timestamp()))
57
+ logger.debug(f"{datetime.timestamp()} created chunk request")
58
58
  fileupload_item = self.append(upload_id, request, content_type)
59
- logger.info(
60
- "\t{0} Published {1}MB".format(datetime.timestamp(), (fileupload_item.file_size / BYTES_PER_MB))
61
- )
62
- logger.info("File upload finished (ID: {0})".format(upload_id))
59
+ logger.info(f"\t{datetime.timestamp()} Published {(fileupload_item.file_size / BYTES_PER_MB)}MB")
60
+ logger.info(f"File upload finished (ID: {upload_id})")
63
61
  return upload_id
@@ -1,9 +1,9 @@
1
1
  import logging
2
- from typing import List, Optional, Tuple, TYPE_CHECKING
2
+ from typing import Optional, TYPE_CHECKING, Union
3
3
 
4
4
  from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api
5
5
  from tableauserverclient.server.endpoint.exceptions import FlowRunFailedException, FlowRunCancelledException
6
- from tableauserverclient.models import FlowRunItem, PaginationItem
6
+ from tableauserverclient.models import FlowRunItem
7
7
  from tableauserverclient.exponential_backoff import ExponentialBackoffTimer
8
8
 
9
9
  from tableauserverclient.helpers.logging import logger
@@ -16,22 +16,24 @@ if TYPE_CHECKING:
16
16
 
17
17
  class FlowRuns(QuerysetEndpoint[FlowRunItem]):
18
18
  def __init__(self, parent_srv: "Server") -> None:
19
- super(FlowRuns, self).__init__(parent_srv)
19
+ super().__init__(parent_srv)
20
20
  return None
21
21
 
22
22
  @property
23
23
  def baseurl(self) -> str:
24
- return "{0}/sites/{1}/flows/runs".format(self.parent_srv.baseurl, self.parent_srv.site_id)
24
+ return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/flows/runs"
25
25
 
26
26
  # Get all flows
27
27
  @api(version="3.10")
28
- def get(self, req_options: Optional["RequestOptions"] = None) -> Tuple[List[FlowRunItem], PaginationItem]:
28
+ # QuerysetEndpoint expects a PaginationItem to be returned, but FlowRuns
29
+ # does not return a PaginationItem. Suppressing the mypy error because the
30
+ # changes to the QuerySet class should permit this to function regardless.
31
+ def get(self, req_options: Optional["RequestOptions"] = None) -> list[FlowRunItem]: # type: ignore[override]
29
32
  logger.info("Querying all flow runs on site")
30
33
  url = self.baseurl
31
34
  server_response = self.get_request(url, req_options)
32
- pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
33
35
  all_flow_run_items = FlowRunItem.from_response(server_response.content, self.parent_srv.namespace)
34
- return all_flow_run_items, pagination_item
36
+ return all_flow_run_items
35
37
 
36
38
  # Get 1 flow by id
37
39
  @api(version="3.10")
@@ -39,21 +41,21 @@ class FlowRuns(QuerysetEndpoint[FlowRunItem]):
39
41
  if not flow_run_id:
40
42
  error = "Flow ID undefined."
41
43
  raise ValueError(error)
42
- logger.info("Querying single flow (ID: {0})".format(flow_run_id))
43
- url = "{0}/{1}".format(self.baseurl, flow_run_id)
44
+ logger.info(f"Querying single flow (ID: {flow_run_id})")
45
+ url = f"{self.baseurl}/{flow_run_id}"
44
46
  server_response = self.get_request(url)
45
47
  return FlowRunItem.from_response(server_response.content, self.parent_srv.namespace)[0]
46
48
 
47
49
  # Cancel 1 flow run by id
48
50
  @api(version="3.10")
49
- def cancel(self, flow_run_id: str) -> None:
51
+ def cancel(self, flow_run_id: Union[str, FlowRunItem]) -> None:
50
52
  if not flow_run_id:
51
53
  error = "Flow ID undefined."
52
54
  raise ValueError(error)
53
55
  id_ = getattr(flow_run_id, "id", flow_run_id)
54
- url = "{0}/{1}".format(self.baseurl, id_)
56
+ url = f"{self.baseurl}/{id_}"
55
57
  self.put_request(url)
56
- logger.info("Deleted single flow (ID: {0})".format(id_))
58
+ logger.info(f"Deleted single flow (ID: {id_})")
57
59
 
58
60
  @api(version="3.10")
59
61
  def wait_for_job(self, flow_run_id: str, *, timeout: Optional[int] = None) -> FlowRunItem:
@@ -69,7 +71,7 @@ class FlowRuns(QuerysetEndpoint[FlowRunItem]):
69
71
  flow_run = self.get_by_id(flow_run_id)
70
72
  logger.debug(f"\tFlowRun {flow_run_id} progress={flow_run.progress}")
71
73
 
72
- logger.info("FlowRun {} Completed: Status: {}".format(flow_run_id, flow_run.status))
74
+ logger.info(f"FlowRun {flow_run_id} Completed: Status: {flow_run.status}")
73
75
 
74
76
  if flow_run.status == "Success":
75
77
  return flow_run
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import List, Optional, Tuple, TYPE_CHECKING
2
+ from typing import TYPE_CHECKING
3
3
 
4
4
  from tableauserverclient.server.endpoint.endpoint import Endpoint, api
5
5
  from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
15
15
  class FlowTasks(Endpoint):
16
16
  @property
17
17
  def baseurl(self) -> str:
18
- return "{0}/sites/{1}/tasks/flows".format(self.parent_srv.baseurl, self.parent_srv.site_id)
18
+ return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/tasks/flows"
19
19
 
20
20
  @api(version="3.22")
21
21
  def create(self, flow_item: TaskItem) -> TaskItem: