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
@@ -7,7 +7,7 @@ from tableauserverclient.models import DataAlertItem, PaginationItem, UserItem
7
7
 
8
8
  from tableauserverclient.helpers.logging import logger
9
9
 
10
- from typing import List, Optional, TYPE_CHECKING, Tuple, Union
10
+ from typing import Optional, TYPE_CHECKING, Union
11
11
 
12
12
 
13
13
  if TYPE_CHECKING:
@@ -17,14 +17,14 @@ if TYPE_CHECKING:
17
17
 
18
18
  class DataAlerts(Endpoint):
19
19
  def __init__(self, parent_srv: "Server") -> None:
20
- super(DataAlerts, self).__init__(parent_srv)
20
+ super().__init__(parent_srv)
21
21
 
22
22
  @property
23
23
  def baseurl(self) -> str:
24
- return "{0}/sites/{1}/dataAlerts".format(self.parent_srv.baseurl, self.parent_srv.site_id)
24
+ return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/dataAlerts"
25
25
 
26
26
  @api(version="3.2")
27
- def get(self, req_options: Optional["RequestOptions"] = None) -> Tuple[List[DataAlertItem], PaginationItem]:
27
+ def get(self, req_options: Optional["RequestOptions"] = None) -> tuple[list[DataAlertItem], PaginationItem]:
28
28
  logger.info("Querying all dataAlerts on site")
29
29
  url = self.baseurl
30
30
  server_response = self.get_request(url, req_options)
@@ -38,8 +38,8 @@ class DataAlerts(Endpoint):
38
38
  if not dataAlert_id:
39
39
  error = "dataAlert ID undefined."
40
40
  raise ValueError(error)
41
- logger.info("Querying single dataAlert (ID: {0})".format(dataAlert_id))
42
- url = "{0}/{1}".format(self.baseurl, dataAlert_id)
41
+ logger.info(f"Querying single dataAlert (ID: {dataAlert_id})")
42
+ url = f"{self.baseurl}/{dataAlert_id}"
43
43
  server_response = self.get_request(url)
44
44
  return DataAlertItem.from_response(server_response.content, self.parent_srv.namespace)[0]
45
45
 
@@ -55,9 +55,9 @@ class DataAlerts(Endpoint):
55
55
  error = "Dataalert ID undefined."
56
56
  raise ValueError(error)
57
57
  # DELETE /api/api-version/sites/site-id/dataAlerts/data-alert-id/users/user-id
58
- url = "{0}/{1}".format(self.baseurl, dataAlert_id)
58
+ url = f"{self.baseurl}/{dataAlert_id}"
59
59
  self.delete_request(url)
60
- logger.info("Deleted single dataAlert (ID: {0})".format(dataAlert_id))
60
+ logger.info(f"Deleted single dataAlert (ID: {dataAlert_id})")
61
61
 
62
62
  @api(version="3.2")
63
63
  def delete_user_from_alert(self, dataAlert: Union[DataAlertItem, str], user: Union[UserItem, str]) -> None:
@@ -80,9 +80,9 @@ class DataAlerts(Endpoint):
80
80
  error = "User ID undefined."
81
81
  raise ValueError(error)
82
82
  # DELETE /api/api-version/sites/site-id/dataAlerts/data-alert-id/users/user-id
83
- url = "{0}/{1}/users/{2}".format(self.baseurl, dataAlert_id, user_id)
83
+ url = f"{self.baseurl}/{dataAlert_id}/users/{user_id}"
84
84
  self.delete_request(url)
85
- logger.info("Deleted User (ID {0}) from dataAlert (ID: {1})".format(user_id, dataAlert_id))
85
+ logger.info(f"Deleted User (ID {user_id}) from dataAlert (ID: {dataAlert_id})")
86
86
 
87
87
  @api(version="3.2")
88
88
  def add_user_to_alert(self, dataAlert_item: DataAlertItem, user: Union[UserItem, str]) -> UserItem:
@@ -98,10 +98,10 @@ class DataAlerts(Endpoint):
98
98
  if not user_id:
99
99
  error = "User ID undefined."
100
100
  raise ValueError(error)
101
- url = "{0}/{1}/users".format(self.baseurl, dataAlert_item.id)
101
+ url = f"{self.baseurl}/{dataAlert_item.id}/users"
102
102
  update_req = RequestFactory.DataAlert.add_user_to_alert(dataAlert_item, user_id)
103
103
  server_response = self.post_request(url, update_req)
104
- logger.info("Added user (ID {0}) to dataAlert item (ID: {1})".format(user_id, dataAlert_item.id))
104
+ logger.info(f"Added user (ID {user_id}) to dataAlert item (ID: {dataAlert_item.id})")
105
105
  added_user = UserItem.from_response(server_response.content, self.parent_srv.namespace)[0]
106
106
  return added_user
107
107
 
@@ -111,9 +111,9 @@ class DataAlerts(Endpoint):
111
111
  error = "Dataalert item missing ID."
112
112
  raise MissingRequiredFieldError(error)
113
113
 
114
- url = "{0}/{1}".format(self.baseurl, dataAlert_item.id)
114
+ url = f"{self.baseurl}/{dataAlert_item.id}"
115
115
  update_req = RequestFactory.DataAlert.update_req(dataAlert_item)
116
116
  server_response = self.put_request(url, update_req)
117
- logger.info("Updated dataAlert item (ID: {0})".format(dataAlert_item.id))
117
+ logger.info(f"Updated dataAlert item (ID: {dataAlert_item.id})")
118
118
  updated_dataAlert = DataAlertItem.from_response(server_response.content, self.parent_srv.namespace)[0]
119
119
  return updated_dataAlert
@@ -1,5 +1,6 @@
1
1
  import logging
2
- from typing import Union, Iterable, Set
2
+ from typing import Union
3
+ from collections.abc import Iterable
3
4
 
4
5
  from tableauserverclient.server.endpoint.default_permissions_endpoint import _DefaultPermissionsEndpoint
5
6
  from tableauserverclient.server.endpoint.dqw_endpoint import _DataQualityWarningEndpoint
@@ -15,7 +16,7 @@ from tableauserverclient.helpers.logging import logger
15
16
 
16
17
  class Databases(Endpoint, TaggingMixin):
17
18
  def __init__(self, parent_srv):
18
- super(Databases, self).__init__(parent_srv)
19
+ super().__init__(parent_srv)
19
20
 
20
21
  self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
21
22
  self._default_permissions = _DefaultPermissionsEndpoint(parent_srv, lambda: self.baseurl)
@@ -23,7 +24,7 @@ class Databases(Endpoint, TaggingMixin):
23
24
 
24
25
  @property
25
26
  def baseurl(self):
26
- return "{0}/sites/{1}/databases".format(self.parent_srv.baseurl, self.parent_srv.site_id)
27
+ return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/databases"
27
28
 
28
29
  @api(version="3.5")
29
30
  def get(self, req_options=None):
@@ -40,8 +41,8 @@ class Databases(Endpoint, TaggingMixin):
40
41
  if not database_id:
41
42
  error = "database ID undefined."
42
43
  raise ValueError(error)
43
- logger.info("Querying single database (ID: {0})".format(database_id))
44
- url = "{0}/{1}".format(self.baseurl, database_id)
44
+ logger.info(f"Querying single database (ID: {database_id})")
45
+ url = f"{self.baseurl}/{database_id}"
45
46
  server_response = self.get_request(url)
46
47
  return DatabaseItem.from_response(server_response.content, self.parent_srv.namespace)[0]
47
48
 
@@ -50,9 +51,9 @@ class Databases(Endpoint, TaggingMixin):
50
51
  if not database_id:
51
52
  error = "Database ID undefined."
52
53
  raise ValueError(error)
53
- url = "{0}/{1}".format(self.baseurl, database_id)
54
+ url = f"{self.baseurl}/{database_id}"
54
55
  self.delete_request(url)
55
- logger.info("Deleted single database (ID: {0})".format(database_id))
56
+ logger.info(f"Deleted single database (ID: {database_id})")
56
57
 
57
58
  @api(version="3.5")
58
59
  def update(self, database_item):
@@ -60,10 +61,10 @@ class Databases(Endpoint, TaggingMixin):
60
61
  error = "Database item missing ID."
61
62
  raise MissingRequiredFieldError(error)
62
63
 
63
- url = "{0}/{1}".format(self.baseurl, database_item.id)
64
+ url = f"{self.baseurl}/{database_item.id}"
64
65
  update_req = RequestFactory.Database.update_req(database_item)
65
66
  server_response = self.put_request(url, update_req)
66
- logger.info("Updated database item (ID: {0})".format(database_item.id))
67
+ logger.info(f"Updated database item (ID: {database_item.id})")
67
68
  updated_database = DatabaseItem.from_response(server_response.content, self.parent_srv.namespace)[0]
68
69
  return updated_database
69
70
 
@@ -78,10 +79,10 @@ class Databases(Endpoint, TaggingMixin):
78
79
  return self._get_tables_for_database(database_item)
79
80
 
80
81
  database_item._set_tables(column_fetcher)
81
- logger.info("Populated tables for database (ID: {0}".format(database_item.id))
82
+ logger.info(f"Populated tables for database (ID: {database_item.id}")
82
83
 
83
84
  def _get_tables_for_database(self, database_item):
84
- url = "{0}/{1}/tables".format(self.baseurl, database_item.id)
85
+ url = f"{self.baseurl}/{database_item.id}/tables"
85
86
  server_response = self.get_request(url)
86
87
  tables = TableItem.from_response(server_response.content, self.parent_srv.namespace)
87
88
  return tables
@@ -127,7 +128,7 @@ class Databases(Endpoint, TaggingMixin):
127
128
  self._data_quality_warnings.clear(item)
128
129
 
129
130
  @api(version="3.9")
130
- def add_tags(self, item: Union[DatabaseItem, str], tags: Iterable[str]) -> Set[str]:
131
+ def add_tags(self, item: Union[DatabaseItem, str], tags: Iterable[str]) -> set[str]:
131
132
  return super().add_tags(item, tags)
132
133
 
133
134
  @api(version="3.9")
@@ -6,7 +6,8 @@ import os
6
6
 
7
7
  from contextlib import closing
8
8
  from pathlib import Path
9
- from typing import Iterable, List, Mapping, Optional, Sequence, Set, Tuple, TYPE_CHECKING, Union
9
+ from typing import Optional, TYPE_CHECKING, Union
10
+ from collections.abc import Iterable, Mapping, Sequence
10
11
 
11
12
  from tableauserverclient.helpers.headers import fix_filename
12
13
  from tableauserverclient.server.query import QuerySet
@@ -22,7 +23,7 @@ from tableauserverclient.server.endpoint.exceptions import InternalServerError,
22
23
  from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
23
24
  from tableauserverclient.server.endpoint.resource_tagger import TaggingMixin
24
25
 
25
- from tableauserverclient.config import ALLOWED_FILE_EXTENSIONS, FILESIZE_LIMIT_MB, BYTES_PER_MB, config
26
+ from tableauserverclient.config import ALLOWED_FILE_EXTENSIONS, BYTES_PER_MB, config
26
27
  from tableauserverclient.filesys_helpers import (
27
28
  make_download_path,
28
29
  get_file_type,
@@ -57,7 +58,7 @@ PathOrFileW = Union[FilePath, FileObjectW]
57
58
 
58
59
  class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]):
59
60
  def __init__(self, parent_srv: "Server") -> None:
60
- super(Datasources, self).__init__(parent_srv)
61
+ super().__init__(parent_srv)
61
62
  self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
62
63
  self._data_quality_warnings = _DataQualityWarningEndpoint(self.parent_srv, "datasource")
63
64
 
@@ -65,11 +66,11 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
65
66
 
66
67
  @property
67
68
  def baseurl(self) -> str:
68
- return "{0}/sites/{1}/datasources".format(self.parent_srv.baseurl, self.parent_srv.site_id)
69
+ return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/datasources"
69
70
 
70
71
  # Get all datasources
71
72
  @api(version="2.0")
72
- def get(self, req_options: Optional[RequestOptions] = None) -> Tuple[List[DatasourceItem], PaginationItem]:
73
+ def get(self, req_options: Optional[RequestOptions] = None) -> tuple[list[DatasourceItem], PaginationItem]:
73
74
  logger.info("Querying all datasources on site")
74
75
  url = self.baseurl
75
76
  server_response = self.get_request(url, req_options)
@@ -83,8 +84,8 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
83
84
  if not datasource_id:
84
85
  error = "Datasource ID undefined."
85
86
  raise ValueError(error)
86
- logger.info("Querying single datasource (ID: {0})".format(datasource_id))
87
- url = "{0}/{1}".format(self.baseurl, datasource_id)
87
+ logger.info(f"Querying single datasource (ID: {datasource_id})")
88
+ url = f"{self.baseurl}/{datasource_id}"
88
89
  server_response = self.get_request(url)
89
90
  return DatasourceItem.from_response(server_response.content, self.parent_srv.namespace)[0]
90
91
 
@@ -99,10 +100,10 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
99
100
  return self._get_datasource_connections(datasource_item)
100
101
 
101
102
  datasource_item._set_connections(connections_fetcher)
102
- logger.info("Populated connections for datasource (ID: {0})".format(datasource_item.id))
103
+ logger.info(f"Populated connections for datasource (ID: {datasource_item.id})")
103
104
 
104
105
  def _get_datasource_connections(self, datasource_item, req_options=None):
105
- url = "{0}/{1}/connections".format(self.baseurl, datasource_item.id)
106
+ url = f"{self.baseurl}/{datasource_item.id}/connections"
106
107
  server_response = self.get_request(url, req_options)
107
108
  connections = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)
108
109
  return connections
@@ -113,9 +114,9 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
113
114
  if not datasource_id:
114
115
  error = "Datasource ID undefined."
115
116
  raise ValueError(error)
116
- url = "{0}/{1}".format(self.baseurl, datasource_id)
117
+ url = f"{self.baseurl}/{datasource_id}"
117
118
  self.delete_request(url)
118
- logger.info("Deleted single datasource (ID: {0})".format(datasource_id))
119
+ logger.info(f"Deleted single datasource (ID: {datasource_id})")
119
120
 
120
121
  # Download 1 datasource by id
121
122
  @api(version="2.0")
@@ -152,11 +153,11 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
152
153
  self.update_tags(datasource_item)
153
154
 
154
155
  # Update the datasource itself
155
- url = "{0}/{1}".format(self.baseurl, datasource_item.id)
156
+ url = f"{self.baseurl}/{datasource_item.id}"
156
157
 
157
158
  update_req = RequestFactory.Datasource.update_req(datasource_item)
158
159
  server_response = self.put_request(url, update_req)
159
- logger.info("Updated datasource item (ID: {0})".format(datasource_item.id))
160
+ logger.info(f"Updated datasource item (ID: {datasource_item.id})")
160
161
  updated_datasource = copy.copy(datasource_item)
161
162
  return updated_datasource._parse_common_elements(server_response.content, self.parent_srv.namespace)
162
163
 
@@ -165,7 +166,7 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
165
166
  def update_connection(
166
167
  self, datasource_item: DatasourceItem, connection_item: ConnectionItem
167
168
  ) -> Optional[ConnectionItem]:
168
- url = "{0}/{1}/connections/{2}".format(self.baseurl, datasource_item.id, connection_item.id)
169
+ url = f"{self.baseurl}/{datasource_item.id}/connections/{connection_item.id}"
169
170
 
170
171
  update_req = RequestFactory.Connection.update_req(connection_item)
171
172
  server_response = self.put_request(url, update_req)
@@ -174,18 +175,16 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
174
175
  return None
175
176
 
176
177
  if len(connections) > 1:
177
- logger.debug("Multiple connections returned ({0})".format(len(connections)))
178
+ logger.debug(f"Multiple connections returned ({len(connections)})")
178
179
  connection = list(filter(lambda x: x.id == connection_item.id, connections))[0]
179
180
 
180
- logger.info(
181
- "Updated datasource item (ID: {0} & connection item {1}".format(datasource_item.id, connection_item.id)
182
- )
181
+ logger.info(f"Updated datasource item (ID: {datasource_item.id} & connection item {connection_item.id}")
183
182
  return connection
184
183
 
185
184
  @api(version="2.8")
186
185
  def refresh(self, datasource_item: DatasourceItem) -> JobItem:
187
186
  id_ = getattr(datasource_item, "id", datasource_item)
188
- url = "{0}/{1}/refresh".format(self.baseurl, id_)
187
+ url = f"{self.baseurl}/{id_}/refresh"
189
188
  empty_req = RequestFactory.Empty.empty_req()
190
189
  server_response = self.post_request(url, empty_req)
191
190
  new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
@@ -194,7 +193,7 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
194
193
  @api(version="3.5")
195
194
  def create_extract(self, datasource_item: DatasourceItem, encrypt: bool = False) -> JobItem:
196
195
  id_ = getattr(datasource_item, "id", datasource_item)
197
- url = "{0}/{1}/createExtract?encrypt={2}".format(self.baseurl, id_, encrypt)
196
+ url = f"{self.baseurl}/{id_}/createExtract?encrypt={encrypt}"
198
197
  empty_req = RequestFactory.Empty.empty_req()
199
198
  server_response = self.post_request(url, empty_req)
200
199
  new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
@@ -203,7 +202,7 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
203
202
  @api(version="3.5")
204
203
  def delete_extract(self, datasource_item: DatasourceItem) -> None:
205
204
  id_ = getattr(datasource_item, "id", datasource_item)
206
- url = "{0}/{1}/deleteExtract".format(self.baseurl, id_)
205
+ url = f"{self.baseurl}/{id_}/deleteExtract"
207
206
  empty_req = RequestFactory.Empty.empty_req()
208
207
  self.post_request(url, empty_req)
209
208
 
@@ -223,12 +222,12 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
223
222
  if isinstance(file, (os.PathLike, str)):
224
223
  if not os.path.isfile(file):
225
224
  error = "File path does not lead to an existing file."
226
- raise IOError(error)
225
+ raise OSError(error)
227
226
 
228
227
  filename = os.path.basename(file)
229
228
  file_extension = os.path.splitext(filename)[1][1:]
230
229
  file_size = os.path.getsize(file)
231
- logger.debug("Publishing file `{}`, size `{}`".format(filename, file_size))
230
+ logger.debug(f"Publishing file `{filename}`, size `{file_size}`")
232
231
  # If name is not defined, grab the name from the file to publish
233
232
  if not datasource_item.name:
234
233
  datasource_item.name = os.path.splitext(filename)[0]
@@ -247,10 +246,10 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
247
246
  elif file_type == "xml":
248
247
  file_extension = "tds"
249
248
  else:
250
- error = "Unsupported file type {}".format(file_type)
249
+ error = f"Unsupported file type {file_type}"
251
250
  raise ValueError(error)
252
251
 
253
- filename = "{}.{}".format(datasource_item.name, file_extension)
252
+ filename = f"{datasource_item.name}.{file_extension}"
254
253
  file_size = get_file_object_size(file)
255
254
 
256
255
  else:
@@ -261,27 +260,27 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
261
260
  raise ValueError(error)
262
261
 
263
262
  # Construct the url with the defined mode
264
- url = "{0}?datasourceType={1}".format(self.baseurl, file_extension)
263
+ url = f"{self.baseurl}?datasourceType={file_extension}"
265
264
  if mode == self.parent_srv.PublishMode.Overwrite or mode == self.parent_srv.PublishMode.Append:
266
- url += "&{0}=true".format(mode.lower())
265
+ url += f"&{mode.lower()}=true"
267
266
 
268
267
  if as_job:
269
- url += "&{0}=true".format("asJob")
268
+ url += "&{}=true".format("asJob")
270
269
 
271
270
  # Determine if chunking is required (64MB is the limit for single upload method)
272
- if file_size >= FILESIZE_LIMIT_MB * BYTES_PER_MB:
271
+ if file_size >= config.FILESIZE_LIMIT_MB * BYTES_PER_MB:
273
272
  logger.info(
274
273
  "Publishing {} to server with chunking method (datasource over {}MB, chunk size {}MB)".format(
275
- filename, FILESIZE_LIMIT_MB, config.CHUNK_SIZE_MB
274
+ filename, config.FILESIZE_LIMIT_MB, config.CHUNK_SIZE_MB
276
275
  )
277
276
  )
278
277
  upload_session_id = self.parent_srv.fileuploads.upload(file)
279
- url = "{0}&uploadSessionId={1}".format(url, upload_session_id)
278
+ url = f"{url}&uploadSessionId={upload_session_id}"
280
279
  xml_request, content_type = RequestFactory.Datasource.publish_req_chunked(
281
280
  datasource_item, connection_credentials, connections
282
281
  )
283
282
  else:
284
- logger.info("Publishing {0} to server".format(filename))
283
+ logger.info(f"Publishing {filename} to server")
285
284
 
286
285
  if isinstance(file, (Path, str)):
287
286
  with open(file, "rb") as f:
@@ -309,11 +308,11 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
309
308
 
310
309
  if as_job:
311
310
  new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
312
- logger.info("Published {0} (JOB_ID: {1}".format(filename, new_job.id))
311
+ logger.info(f"Published {filename} (JOB_ID: {new_job.id}")
313
312
  return new_job
314
313
  else:
315
314
  new_datasource = DatasourceItem.from_response(server_response.content, self.parent_srv.namespace)[0]
316
- logger.info("Published {0} (ID: {1})".format(filename, new_datasource.id))
315
+ logger.info(f"Published {filename} (ID: {new_datasource.id})")
317
316
  return new_datasource
318
317
 
319
318
  @api(version="3.13")
@@ -327,23 +326,23 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
327
326
  ) -> JobItem:
328
327
  if isinstance(datasource_or_connection_item, DatasourceItem):
329
328
  datasource_id = datasource_or_connection_item.id
330
- url = "{0}/{1}/data".format(self.baseurl, datasource_id)
329
+ url = f"{self.baseurl}/{datasource_id}/data"
331
330
  elif isinstance(datasource_or_connection_item, ConnectionItem):
332
331
  datasource_id = datasource_or_connection_item.datasource_id
333
332
  connection_id = datasource_or_connection_item.id
334
- url = "{0}/{1}/connections/{2}/data".format(self.baseurl, datasource_id, connection_id)
333
+ url = f"{self.baseurl}/{datasource_id}/connections/{connection_id}/data"
335
334
  else:
336
335
  assert isinstance(datasource_or_connection_item, str)
337
- url = "{0}/{1}/data".format(self.baseurl, datasource_or_connection_item)
336
+ url = f"{self.baseurl}/{datasource_or_connection_item}/data"
338
337
 
339
338
  if payload is not None:
340
339
  if not os.path.isfile(payload):
341
340
  error = "File path does not lead to an existing file."
342
- raise IOError(error)
341
+ raise OSError(error)
343
342
 
344
- logger.info("Uploading {0} to server with chunking method for Update job".format(payload))
343
+ logger.info(f"Uploading {payload} to server with chunking method for Update job")
345
344
  upload_session_id = self.parent_srv.fileuploads.upload(payload)
346
- url = "{0}?uploadSessionId={1}".format(url, upload_session_id)
345
+ url = f"{url}?uploadSessionId={upload_session_id}"
347
346
 
348
347
  json_request = json.dumps({"actions": actions})
349
348
  parameters = {"headers": {"requestid": request_id}}
@@ -356,7 +355,7 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
356
355
  self._permissions.populate(item)
357
356
 
358
357
  @api(version="2.0")
359
- def update_permissions(self, item: DatasourceItem, permission_item: List["PermissionsRule"]) -> None:
358
+ def update_permissions(self, item: DatasourceItem, permission_item: list["PermissionsRule"]) -> None:
360
359
  self._permissions.update(item, permission_item)
361
360
 
362
361
  @api(version="2.0")
@@ -390,12 +389,12 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
390
389
  return self._get_datasource_revisions(datasource_item)
391
390
 
392
391
  datasource_item._set_revisions(revisions_fetcher)
393
- logger.info("Populated revisions for datasource (ID: {0})".format(datasource_item.id))
392
+ logger.info(f"Populated revisions for datasource (ID: {datasource_item.id})")
394
393
 
395
394
  def _get_datasource_revisions(
396
395
  self, datasource_item: DatasourceItem, req_options: Optional["RequestOptions"] = None
397
- ) -> List[RevisionItem]:
398
- url = "{0}/{1}/revisions".format(self.baseurl, datasource_item.id)
396
+ ) -> list[RevisionItem]:
397
+ url = f"{self.baseurl}/{datasource_item.id}/revisions"
399
398
  server_response = self.get_request(url, req_options)
400
399
  revisions = RevisionItem.from_response(server_response.content, self.parent_srv.namespace, datasource_item)
401
400
  return revisions
@@ -413,9 +412,9 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
413
412
  error = "Datasource ID undefined."
414
413
  raise ValueError(error)
415
414
  if revision_number is None:
416
- url = "{0}/{1}/content".format(self.baseurl, datasource_id)
415
+ url = f"{self.baseurl}/{datasource_id}/content"
417
416
  else:
418
- url = "{0}/{1}/revisions/{2}/content".format(self.baseurl, datasource_id, revision_number)
417
+ url = f"{self.baseurl}/{datasource_id}/revisions/{revision_number}/content"
419
418
 
420
419
  if not include_extract:
421
420
  url += "?includeExtract=False"
@@ -437,9 +436,7 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
437
436
  f.write(chunk)
438
437
  return_path = os.path.abspath(download_path)
439
438
 
440
- logger.info(
441
- "Downloaded datasource revision {0} to {1} (ID: {2})".format(revision_number, return_path, datasource_id)
442
- )
439
+ logger.info(f"Downloaded datasource revision {revision_number} to {return_path} (ID: {datasource_id})")
443
440
  return return_path
444
441
 
445
442
  @api(version="2.3")
@@ -449,19 +446,17 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
449
446
  url = "/".join([self.baseurl, datasource_id, "revisions", revision_number])
450
447
 
451
448
  self.delete_request(url)
452
- logger.info(
453
- "Deleted single datasource revision (ID: {0}) (Revision: {1})".format(datasource_id, revision_number)
454
- )
449
+ logger.info(f"Deleted single datasource revision (ID: {datasource_id}) (Revision: {revision_number})")
455
450
 
456
451
  # a convenience method
457
452
  @api(version="2.8")
458
453
  def schedule_extract_refresh(
459
454
  self, schedule_id: str, item: DatasourceItem
460
- ) -> List["AddResponse"]: # actually should return a task
455
+ ) -> list["AddResponse"]: # actually should return a task
461
456
  return self.parent_srv.schedules.add_to_schedule(schedule_id, datasource=item)
462
457
 
463
458
  @api(version="1.0")
464
- def add_tags(self, item: Union[DatasourceItem, str], tags: Union[Iterable[str], str]) -> Set[str]:
459
+ def add_tags(self, item: Union[DatasourceItem, str], tags: Union[Iterable[str], str]) -> set[str]:
465
460
  return super().add_tags(item, tags)
466
461
 
467
462
  @api(version="1.0")
@@ -4,7 +4,8 @@ from .endpoint import Endpoint
4
4
  from .exceptions import MissingRequiredFieldError
5
5
  from tableauserverclient.server import RequestFactory
6
6
  from tableauserverclient.models import DatabaseItem, PermissionsRule, ProjectItem, plural_type, Resource
7
- from typing import TYPE_CHECKING, Callable, List, Optional, Sequence, Union
7
+ from typing import TYPE_CHECKING, Callable, Optional, Union
8
+ from collections.abc import Sequence
8
9
 
9
10
  if TYPE_CHECKING:
10
11
  from ..server import Server
@@ -25,7 +26,7 @@ class _DefaultPermissionsEndpoint(Endpoint):
25
26
  """
26
27
 
27
28
  def __init__(self, parent_srv: "Server", owner_baseurl: Callable[[], str]) -> None:
28
- super(_DefaultPermissionsEndpoint, self).__init__(parent_srv)
29
+ super().__init__(parent_srv)
29
30
 
30
31
  # owner_baseurl is the baseurl of the parent, a project or database.
31
32
  # It MUST be a lambda since we don't know the full site URL until we sign in.
@@ -33,23 +34,25 @@ class _DefaultPermissionsEndpoint(Endpoint):
33
34
  self.owner_baseurl = owner_baseurl
34
35
 
35
36
  def __str__(self):
36
- return "<DefaultPermissionsEndpoint {} [Flow, Datasource, Workbook, Lens]>".format(self.owner_baseurl())
37
+ return f"<DefaultPermissionsEndpoint {self.owner_baseurl()} [Flow, Datasource, Workbook, Lens]>"
37
38
 
38
39
  __repr__ = __str__
39
40
 
40
41
  def update_default_permissions(
41
- self, resource: BaseItem, permissions: Sequence[PermissionsRule], content_type: Resource
42
- ) -> List[PermissionsRule]:
43
- url = "{0}/{1}/default-permissions/{2}".format(self.owner_baseurl(), resource.id, plural_type(content_type))
42
+ self, resource: BaseItem, permissions: Sequence[PermissionsRule], content_type: Union[Resource, str]
43
+ ) -> list[PermissionsRule]:
44
+ url = f"{self.owner_baseurl()}/{resource.id}/default-permissions/{plural_type(content_type)}"
44
45
  update_req = RequestFactory.Permission.add_req(permissions)
45
46
  response = self.put_request(url, update_req)
46
47
  permissions = PermissionsRule.from_response(response.content, self.parent_srv.namespace)
47
- logger.info("Updated default {} permissions for resource {}".format(content_type, resource.id))
48
+ logger.info(f"Updated default {content_type} permissions for resource {resource.id}")
48
49
  logger.info(permissions)
49
50
 
50
51
  return permissions
51
52
 
52
- def delete_default_permission(self, resource: BaseItem, rule: PermissionsRule, content_type: Resource) -> None:
53
+ def delete_default_permission(
54
+ self, resource: BaseItem, rule: PermissionsRule, content_type: Union[Resource, str]
55
+ ) -> None:
53
56
  for capability, mode in rule.capabilities.items():
54
57
  # Made readability better but line is too long, will make this look better
55
58
  url = (
@@ -65,29 +68,27 @@ class _DefaultPermissionsEndpoint(Endpoint):
65
68
  )
66
69
  )
67
70
 
68
- logger.debug("Removing {0} permission for capability {1}".format(mode, capability))
71
+ logger.debug(f"Removing {mode} permission for capability {capability}")
69
72
 
70
73
  self.delete_request(url)
71
74
 
72
- logger.info(
73
- "Deleted permission for {0} {1} item {2}".format(rule.grantee.tag_name, rule.grantee.id, resource.id)
74
- )
75
+ logger.info(f"Deleted permission for {rule.grantee.tag_name} {rule.grantee.id} item {resource.id}")
75
76
 
76
- def populate_default_permissions(self, item: BaseItem, content_type: Resource) -> None:
77
+ def populate_default_permissions(self, item: BaseItem, content_type: Union[Resource, str]) -> None:
77
78
  if not item.id:
78
79
  error = "Server item is missing ID. Item must be retrieved from server first."
79
80
  raise MissingRequiredFieldError(error)
80
81
 
81
- def permission_fetcher() -> List[PermissionsRule]:
82
+ def permission_fetcher() -> list[PermissionsRule]:
82
83
  return self._get_default_permissions(item, content_type)
83
84
 
84
85
  item._set_default_permissions(permission_fetcher, content_type)
85
- logger.info("Populated default {0} permissions for item (ID: {1})".format(content_type, item.id))
86
+ logger.info(f"Populated default {content_type} permissions for item (ID: {item.id})")
86
87
 
87
88
  def _get_default_permissions(
88
- self, item: BaseItem, content_type: Resource, req_options: Optional["RequestOptions"] = None
89
- ) -> List[PermissionsRule]:
90
- url = "{0}/{1}/default-permissions/{2}".format(self.owner_baseurl(), item.id, plural_type(content_type))
89
+ self, item: BaseItem, content_type: Union[Resource, str], req_options: Optional["RequestOptions"] = None
90
+ ) -> list[PermissionsRule]:
91
+ url = f"{self.owner_baseurl()}/{item.id}/default-permissions/{plural_type(content_type)}"
91
92
  server_response = self.get_request(url, req_options)
92
93
  permissions = PermissionsRule.from_response(server_response.content, self.parent_srv.namespace)
93
94
  logger.info({"content_type": content_type, "permissions": permissions})
@@ -10,35 +10,35 @@ from tableauserverclient.helpers.logging import logger
10
10
 
11
11
  class _DataQualityWarningEndpoint(Endpoint):
12
12
  def __init__(self, parent_srv, resource_type):
13
- super(_DataQualityWarningEndpoint, self).__init__(parent_srv)
13
+ super().__init__(parent_srv)
14
14
  self.resource_type = resource_type
15
15
 
16
16
  @property
17
17
  def baseurl(self):
18
- return "{0}/sites/{1}/dataQualityWarnings/{2}".format(
18
+ return "{}/sites/{}/dataQualityWarnings/{}".format(
19
19
  self.parent_srv.baseurl, self.parent_srv.site_id, self.resource_type
20
20
  )
21
21
 
22
22
  def add(self, resource, warning):
23
- url = "{baseurl}/{content_luid}".format(baseurl=self.baseurl, content_luid=resource.id)
23
+ url = f"{self.baseurl}/{resource.id}"
24
24
  add_req = RequestFactory.DQW.add_req(warning)
25
25
  response = self.post_request(url, add_req)
26
26
  warnings = DQWItem.from_response(response.content, self.parent_srv.namespace)
27
- logger.info("Added dqw for resource {0}".format(resource.id))
27
+ logger.info(f"Added dqw for resource {resource.id}")
28
28
 
29
29
  return warnings
30
30
 
31
31
  def update(self, resource, warning):
32
- url = "{baseurl}/{content_luid}".format(baseurl=self.baseurl, content_luid=resource.id)
32
+ url = f"{self.baseurl}/{resource.id}"
33
33
  add_req = RequestFactory.DQW.update_req(warning)
34
34
  response = self.put_request(url, add_req)
35
35
  warnings = DQWItem.from_response(response.content, self.parent_srv.namespace)
36
- logger.info("Added dqw for resource {0}".format(resource.id))
36
+ logger.info(f"Added dqw for resource {resource.id}")
37
37
 
38
38
  return warnings
39
39
 
40
40
  def clear(self, resource):
41
- url = "{baseurl}/{content_luid}".format(baseurl=self.baseurl, content_luid=resource.id)
41
+ url = f"{self.baseurl}/{resource.id}"
42
42
  return self.delete_request(url)
43
43
 
44
44
  def populate(self, item):
@@ -50,10 +50,10 @@ class _DataQualityWarningEndpoint(Endpoint):
50
50
  return self._get_data_quality_warnings(item)
51
51
 
52
52
  item._set_data_quality_warnings(dqw_fetcher)
53
- logger.info("Populated permissions for item (ID: {0})".format(item.id))
53
+ logger.info(f"Populated permissions for item (ID: {item.id})")
54
54
 
55
55
  def _get_data_quality_warnings(self, item, req_options=None):
56
- url = "{baseurl}/{content_luid}".format(baseurl=self.baseurl, content_luid=item.id)
56
+ url = f"{self.baseurl}/{item.id}"
57
57
  server_response = self.get_request(url, req_options)
58
58
  dqws = DQWItem.from_response(server_response.content, self.parent_srv.namespace)
59
59