tableauserverclient 0.32__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 (96) hide show
  1. tableauserverclient/__init__.py +34 -18
  2. tableauserverclient/_version.py +3 -3
  3. tableauserverclient/config.py +20 -6
  4. tableauserverclient/models/__init__.py +12 -0
  5. tableauserverclient/models/column_item.py +1 -1
  6. tableauserverclient/models/connection_credentials.py +1 -1
  7. tableauserverclient/models/connection_item.py +10 -8
  8. tableauserverclient/models/custom_view_item.py +29 -6
  9. tableauserverclient/models/data_acceleration_report_item.py +2 -2
  10. tableauserverclient/models/data_alert_item.py +5 -5
  11. tableauserverclient/models/data_freshness_policy_item.py +6 -6
  12. tableauserverclient/models/database_item.py +8 -2
  13. tableauserverclient/models/datasource_item.py +10 -10
  14. tableauserverclient/models/dqw_item.py +1 -1
  15. tableauserverclient/models/favorites_item.py +5 -6
  16. tableauserverclient/models/fileupload_item.py +1 -1
  17. tableauserverclient/models/flow_item.py +12 -12
  18. tableauserverclient/models/flow_run_item.py +3 -3
  19. tableauserverclient/models/group_item.py +4 -4
  20. tableauserverclient/models/groupset_item.py +53 -0
  21. tableauserverclient/models/interval_item.py +36 -23
  22. tableauserverclient/models/job_item.py +26 -10
  23. tableauserverclient/models/linked_tasks_item.py +102 -0
  24. tableauserverclient/models/metric_item.py +5 -5
  25. tableauserverclient/models/pagination_item.py +1 -1
  26. tableauserverclient/models/permissions_item.py +19 -14
  27. tableauserverclient/models/project_item.py +35 -19
  28. tableauserverclient/models/property_decorators.py +12 -11
  29. tableauserverclient/models/reference_item.py +2 -2
  30. tableauserverclient/models/revision_item.py +3 -3
  31. tableauserverclient/models/schedule_item.py +2 -2
  32. tableauserverclient/models/server_info_item.py +26 -6
  33. tableauserverclient/models/site_item.py +69 -3
  34. tableauserverclient/models/subscription_item.py +3 -3
  35. tableauserverclient/models/table_item.py +1 -1
  36. tableauserverclient/models/tableau_auth.py +115 -5
  37. tableauserverclient/models/tableau_types.py +11 -9
  38. tableauserverclient/models/tag_item.py +3 -4
  39. tableauserverclient/models/task_item.py +4 -4
  40. tableauserverclient/models/user_item.py +47 -17
  41. tableauserverclient/models/view_item.py +11 -10
  42. tableauserverclient/models/virtual_connection_item.py +78 -0
  43. tableauserverclient/models/webhook_item.py +6 -6
  44. tableauserverclient/models/workbook_item.py +90 -12
  45. tableauserverclient/namespace.py +1 -1
  46. tableauserverclient/server/__init__.py +2 -1
  47. tableauserverclient/server/endpoint/__init__.py +8 -0
  48. tableauserverclient/server/endpoint/auth_endpoint.py +68 -11
  49. tableauserverclient/server/endpoint/custom_views_endpoint.py +124 -19
  50. tableauserverclient/server/endpoint/data_acceleration_report_endpoint.py +2 -2
  51. tableauserverclient/server/endpoint/data_alert_endpoint.py +14 -14
  52. tableauserverclient/server/endpoint/databases_endpoint.py +32 -17
  53. tableauserverclient/server/endpoint/datasources_endpoint.py +150 -59
  54. tableauserverclient/server/endpoint/default_permissions_endpoint.py +19 -18
  55. tableauserverclient/server/endpoint/dqw_endpoint.py +9 -9
  56. tableauserverclient/server/endpoint/endpoint.py +47 -31
  57. tableauserverclient/server/endpoint/exceptions.py +23 -7
  58. tableauserverclient/server/endpoint/favorites_endpoint.py +31 -31
  59. tableauserverclient/server/endpoint/fileuploads_endpoint.py +11 -13
  60. tableauserverclient/server/endpoint/flow_runs_endpoint.py +59 -17
  61. tableauserverclient/server/endpoint/flow_task_endpoint.py +2 -2
  62. tableauserverclient/server/endpoint/flows_endpoint.py +73 -35
  63. tableauserverclient/server/endpoint/groups_endpoint.py +96 -27
  64. tableauserverclient/server/endpoint/groupsets_endpoint.py +127 -0
  65. tableauserverclient/server/endpoint/jobs_endpoint.py +79 -12
  66. tableauserverclient/server/endpoint/linked_tasks_endpoint.py +45 -0
  67. tableauserverclient/server/endpoint/metadata_endpoint.py +2 -2
  68. tableauserverclient/server/endpoint/metrics_endpoint.py +10 -10
  69. tableauserverclient/server/endpoint/permissions_endpoint.py +13 -15
  70. tableauserverclient/server/endpoint/projects_endpoint.py +124 -30
  71. tableauserverclient/server/endpoint/resource_tagger.py +139 -6
  72. tableauserverclient/server/endpoint/schedules_endpoint.py +17 -18
  73. tableauserverclient/server/endpoint/server_info_endpoint.py +40 -5
  74. tableauserverclient/server/endpoint/sites_endpoint.py +282 -17
  75. tableauserverclient/server/endpoint/subscriptions_endpoint.py +10 -10
  76. tableauserverclient/server/endpoint/tables_endpoint.py +33 -19
  77. tableauserverclient/server/endpoint/tasks_endpoint.py +8 -8
  78. tableauserverclient/server/endpoint/users_endpoint.py +405 -19
  79. tableauserverclient/server/endpoint/views_endpoint.py +111 -25
  80. tableauserverclient/server/endpoint/virtual_connections_endpoint.py +174 -0
  81. tableauserverclient/server/endpoint/webhooks_endpoint.py +11 -11
  82. tableauserverclient/server/endpoint/workbooks_endpoint.py +735 -68
  83. tableauserverclient/server/filter.py +2 -2
  84. tableauserverclient/server/pager.py +8 -10
  85. tableauserverclient/server/query.py +70 -20
  86. tableauserverclient/server/request_factory.py +213 -41
  87. tableauserverclient/server/request_options.py +125 -145
  88. tableauserverclient/server/server.py +73 -9
  89. tableauserverclient/server/sort.py +2 -2
  90. {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/METADATA +17 -17
  91. tableauserverclient-0.34.dist-info/RECORD +106 -0
  92. {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/WHEEL +1 -1
  93. tableauserverclient-0.32.dist-info/RECORD +0 -100
  94. {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/LICENSE +0 -0
  95. {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/LICENSE.versioneer +0 -0
  96. {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/top_level.txt +0 -0
@@ -1,18 +1,21 @@
1
1
  import logging
2
2
  from contextlib import closing
3
3
 
4
- from .endpoint import QuerysetEndpoint, api
5
- from .exceptions import MissingRequiredFieldError
6
- from .permissions_endpoint import _PermissionsEndpoint
7
- from .resource_tagger import _ResourceTagger
4
+ from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api
5
+ from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError
6
+ from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
7
+ from tableauserverclient.server.endpoint.resource_tagger import TaggingMixin
8
+ from tableauserverclient.server.query import QuerySet
9
+
8
10
  from tableauserverclient.models import ViewItem, PaginationItem
9
11
 
10
12
  from tableauserverclient.helpers.logging import logger
11
13
 
12
- from typing import Iterator, List, Optional, Tuple, TYPE_CHECKING
14
+ from typing import Optional, TYPE_CHECKING, Union
15
+ from collections.abc import Iterable, Iterator
13
16
 
14
17
  if TYPE_CHECKING:
15
- from ..request_options import (
18
+ from tableauserverclient.server.request_options import (
16
19
  RequestOptions,
17
20
  CSVRequestOptions,
18
21
  PDFRequestOptions,
@@ -21,25 +24,24 @@ if TYPE_CHECKING:
21
24
  )
22
25
 
23
26
 
24
- class Views(QuerysetEndpoint[ViewItem]):
27
+ class Views(QuerysetEndpoint[ViewItem], TaggingMixin[ViewItem]):
25
28
  def __init__(self, parent_srv):
26
- super(Views, self).__init__(parent_srv)
27
- self._resource_tagger = _ResourceTagger(parent_srv)
29
+ super().__init__(parent_srv)
28
30
  self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
29
31
 
30
32
  # Used because populate_preview_image functionaliy requires workbook endpoint
31
33
  @property
32
34
  def siteurl(self) -> str:
33
- return "{0}/sites/{1}".format(self.parent_srv.baseurl, self.parent_srv.site_id)
35
+ return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}"
34
36
 
35
37
  @property
36
38
  def baseurl(self) -> str:
37
- return "{0}/views".format(self.siteurl)
39
+ return f"{self.siteurl}/views"
38
40
 
39
41
  @api(version="2.2")
40
42
  def get(
41
43
  self, req_options: Optional["RequestOptions"] = None, usage: bool = False
42
- ) -> Tuple[List[ViewItem], PaginationItem]:
44
+ ) -> tuple[list[ViewItem], PaginationItem]:
43
45
  logger.info("Querying all views on site")
44
46
  url = self.baseurl
45
47
  if usage:
@@ -54,8 +56,8 @@ class Views(QuerysetEndpoint[ViewItem]):
54
56
  if not view_id:
55
57
  error = "View item missing ID."
56
58
  raise MissingRequiredFieldError(error)
57
- logger.info("Querying single view (ID: {0})".format(view_id))
58
- url = "{0}/{1}".format(self.baseurl, view_id)
59
+ logger.info(f"Querying single view (ID: {view_id})")
60
+ url = f"{self.baseurl}/{view_id}"
59
61
  if usage:
60
62
  url += "?includeUsageStatistics=true"
61
63
  server_response = self.get_request(url)
@@ -71,10 +73,10 @@ class Views(QuerysetEndpoint[ViewItem]):
71
73
  return self._get_preview_for_view(view_item)
72
74
 
73
75
  view_item._set_preview_image(image_fetcher)
74
- logger.info("Populated preview image for view (ID: {0})".format(view_item.id))
76
+ logger.info(f"Populated preview image for view (ID: {view_item.id})")
75
77
 
76
78
  def _get_preview_for_view(self, view_item: ViewItem) -> bytes:
77
- url = "{0}/workbooks/{1}/views/{2}/previewImage".format(self.siteurl, view_item.workbook_id, view_item.id)
79
+ url = f"{self.siteurl}/workbooks/{view_item.workbook_id}/views/{view_item.id}/previewImage"
78
80
  server_response = self.get_request(url)
79
81
  image = server_response.content
80
82
  return image
@@ -89,10 +91,10 @@ class Views(QuerysetEndpoint[ViewItem]):
89
91
  return self._get_view_image(view_item, req_options)
90
92
 
91
93
  view_item._set_image(image_fetcher)
92
- logger.info("Populated image for view (ID: {0})".format(view_item.id))
94
+ logger.info(f"Populated image for view (ID: {view_item.id})")
93
95
 
94
96
  def _get_view_image(self, view_item: ViewItem, req_options: Optional["ImageRequestOptions"]) -> bytes:
95
- url = "{0}/{1}/image".format(self.baseurl, view_item.id)
97
+ url = f"{self.baseurl}/{view_item.id}/image"
96
98
  server_response = self.get_request(url, req_options)
97
99
  image = server_response.content
98
100
  return image
@@ -107,10 +109,10 @@ class Views(QuerysetEndpoint[ViewItem]):
107
109
  return self._get_view_pdf(view_item, req_options)
108
110
 
109
111
  view_item._set_pdf(pdf_fetcher)
110
- logger.info("Populated pdf for view (ID: {0})".format(view_item.id))
112
+ logger.info(f"Populated pdf for view (ID: {view_item.id})")
111
113
 
112
114
  def _get_view_pdf(self, view_item: ViewItem, req_options: Optional["PDFRequestOptions"]) -> bytes:
113
- url = "{0}/{1}/pdf".format(self.baseurl, view_item.id)
115
+ url = f"{self.baseurl}/{view_item.id}/pdf"
114
116
  server_response = self.get_request(url, req_options)
115
117
  pdf = server_response.content
116
118
  return pdf
@@ -125,10 +127,10 @@ class Views(QuerysetEndpoint[ViewItem]):
125
127
  return self._get_view_csv(view_item, req_options)
126
128
 
127
129
  view_item._set_csv(csv_fetcher)
128
- logger.info("Populated csv for view (ID: {0})".format(view_item.id))
130
+ logger.info(f"Populated csv for view (ID: {view_item.id})")
129
131
 
130
132
  def _get_view_csv(self, view_item: ViewItem, req_options: Optional["CSVRequestOptions"]) -> Iterator[bytes]:
131
- url = "{0}/{1}/data".format(self.baseurl, view_item.id)
133
+ url = f"{self.baseurl}/{view_item.id}/data"
132
134
 
133
135
  with closing(self.get_request(url, request_object=req_options, parameters={"stream": True})) as server_response:
134
136
  yield from server_response.iter_content(1024)
@@ -143,10 +145,10 @@ class Views(QuerysetEndpoint[ViewItem]):
143
145
  return self._get_view_excel(view_item, req_options)
144
146
 
145
147
  view_item._set_excel(excel_fetcher)
146
- logger.info("Populated excel for view (ID: {0})".format(view_item.id))
148
+ logger.info(f"Populated excel for view (ID: {view_item.id})")
147
149
 
148
150
  def _get_view_excel(self, view_item: ViewItem, req_options: Optional["ExcelRequestOptions"]) -> Iterator[bytes]:
149
- url = "{0}/{1}/crosstab/excel".format(self.baseurl, view_item.id)
151
+ url = f"{self.baseurl}/{view_item.id}/crosstab/excel"
150
152
 
151
153
  with closing(self.get_request(url, request_object=req_options, parameters={"stream": True})) as server_response:
152
154
  yield from server_response.iter_content(1024)
@@ -169,7 +171,91 @@ class Views(QuerysetEndpoint[ViewItem]):
169
171
  error = "View item missing ID. View must be retrieved from server first."
170
172
  raise MissingRequiredFieldError(error)
171
173
 
172
- self._resource_tagger.update_tags(self.baseurl, view_item)
174
+ self.update_tags(view_item)
173
175
 
174
176
  # Returning view item to stay consistent with datasource/view update functions
175
177
  return view_item
178
+
179
+ @api(version="1.0")
180
+ def add_tags(self, item: Union[ViewItem, str], tags: Union[Iterable[str], str]) -> set[str]:
181
+ return super().add_tags(item, tags)
182
+
183
+ @api(version="1.0")
184
+ def delete_tags(self, item: Union[ViewItem, str], tags: Union[Iterable[str], str]) -> None:
185
+ return super().delete_tags(item, tags)
186
+
187
+ @api(version="1.0")
188
+ def update_tags(self, item: ViewItem) -> None:
189
+ return super().update_tags(item)
190
+
191
+ def filter(self, *invalid, page_size: Optional[int] = None, **kwargs) -> QuerySet[ViewItem]:
192
+ """
193
+ Queries the Tableau Server for items using the specified filters. Page
194
+ size can be specified to limit the number of items returned in a single
195
+ request. If not specified, the default page size is 100. Page size can
196
+ be an integer between 1 and 1000.
197
+
198
+ No positional arguments are allowed. All filters must be specified as
199
+ keyword arguments. If you use the equality operator, you can specify it
200
+ through <field_name>=<value>. If you want to use a different operator,
201
+ you can specify it through <field_name>__<operator>=<value>. Field
202
+ names can either be in snake_case or camelCase.
203
+
204
+ This endpoint supports the following fields and operators:
205
+
206
+
207
+ caption=...
208
+ caption__in=...
209
+ content_url=...
210
+ content_url__in=...
211
+ created_at=...
212
+ created_at__gt=...
213
+ created_at__gte=...
214
+ created_at__lt=...
215
+ created_at__lte=...
216
+ favorites_total=...
217
+ favorites_total__gt=...
218
+ favorites_total__gte=...
219
+ favorites_total__lt=...
220
+ favorites_total__lte=...
221
+ fields=...
222
+ fields__in=...
223
+ hits_total=...
224
+ hits_total__gt=...
225
+ hits_total__gte=...
226
+ hits_total__lt=...
227
+ hits_total__lte=...
228
+ name=...
229
+ name__in=...
230
+ owner_domain=...
231
+ owner_domain__in=...
232
+ owner_email=...
233
+ owner_email__in=...
234
+ owner_name=...
235
+ project_name=...
236
+ project_name__in=...
237
+ sheet_number=...
238
+ sheet_number__gt=...
239
+ sheet_number__gte=...
240
+ sheet_number__lt=...
241
+ sheet_number__lte=...
242
+ sheet_type=...
243
+ sheet_type__in=...
244
+ tags=...
245
+ tags__in=...
246
+ title=...
247
+ title__in=...
248
+ updated_at=...
249
+ updated_at__gt=...
250
+ updated_at__gte=...
251
+ updated_at__lt=...
252
+ updated_at__lte=...
253
+ view_url_name=...
254
+ view_url_name__in=...
255
+ workbook_description=...
256
+ workbook_description__in=...
257
+ workbook_name=...
258
+ workbook_name__in=...
259
+ """
260
+
261
+ return super().filter(*invalid, page_size=page_size, **kwargs)
@@ -0,0 +1,174 @@
1
+ from functools import partial
2
+ import json
3
+ from pathlib import Path
4
+ from typing import Optional, TYPE_CHECKING, Union
5
+ from collections.abc import Iterable
6
+
7
+ from tableauserverclient.models.connection_item import ConnectionItem
8
+ from tableauserverclient.models.pagination_item import PaginationItem
9
+ from tableauserverclient.models.revision_item import RevisionItem
10
+ from tableauserverclient.models.virtual_connection_item import VirtualConnectionItem
11
+ from tableauserverclient.server.request_factory import RequestFactory
12
+ from tableauserverclient.server.request_options import RequestOptions
13
+ from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api
14
+ from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
15
+ from tableauserverclient.server.endpoint.resource_tagger import TaggingMixin
16
+ from tableauserverclient.server.pager import Pager
17
+
18
+ if TYPE_CHECKING:
19
+ from tableauserverclient.server import Server
20
+
21
+
22
+ class VirtualConnections(QuerysetEndpoint[VirtualConnectionItem], TaggingMixin):
23
+ def __init__(self, parent_srv: "Server") -> None:
24
+ super().__init__(parent_srv)
25
+ self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
26
+
27
+ @property
28
+ def baseurl(self) -> str:
29
+ return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/virtualConnections"
30
+
31
+ @api(version="3.18")
32
+ def get(self, req_options: Optional[RequestOptions] = None) -> tuple[list[VirtualConnectionItem], PaginationItem]:
33
+ server_response = self.get_request(self.baseurl, req_options)
34
+ pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
35
+ virtual_connections = VirtualConnectionItem.from_response(server_response.content, self.parent_srv.namespace)
36
+ return virtual_connections, pagination_item
37
+
38
+ @api(version="3.18")
39
+ def populate_connections(self, virtual_connection: VirtualConnectionItem) -> VirtualConnectionItem:
40
+ def _connection_fetcher():
41
+ return Pager(partial(self._get_virtual_database_connections, virtual_connection))
42
+
43
+ virtual_connection._connections = _connection_fetcher
44
+ return virtual_connection
45
+
46
+ def _get_virtual_database_connections(
47
+ self, virtual_connection: VirtualConnectionItem, req_options: Optional[RequestOptions] = None
48
+ ) -> tuple[list[ConnectionItem], PaginationItem]:
49
+ server_response = self.get_request(f"{self.baseurl}/{virtual_connection.id}/connections", req_options)
50
+ connections = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)
51
+ pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
52
+
53
+ return connections, pagination_item
54
+
55
+ @api(version="3.18")
56
+ def update_connection_db_connection(
57
+ self, virtual_connection: Union[str, VirtualConnectionItem], connection: ConnectionItem
58
+ ) -> ConnectionItem:
59
+ vconn_id = getattr(virtual_connection, "id", virtual_connection)
60
+ url = f"{self.baseurl}/{vconn_id}/connections/{connection.id}/modify"
61
+ xml_request = RequestFactory.VirtualConnection.update_db_connection(connection)
62
+ server_response = self.put_request(url, xml_request)
63
+ return ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)[0]
64
+
65
+ @api(version="3.23")
66
+ def get_by_id(self, virtual_connection: Union[str, VirtualConnectionItem]) -> VirtualConnectionItem:
67
+ vconn_id = getattr(virtual_connection, "id", virtual_connection)
68
+ url = f"{self.baseurl}/{vconn_id}"
69
+ server_response = self.get_request(url)
70
+ return VirtualConnectionItem.from_response(server_response.content, self.parent_srv.namespace)[0]
71
+
72
+ @api(version="3.23")
73
+ def download(self, virtual_connection: Union[str, VirtualConnectionItem]) -> str:
74
+ v_conn = self.get_by_id(virtual_connection)
75
+ return json.dumps(v_conn.content)
76
+
77
+ @api(version="3.23")
78
+ def update(self, virtual_connection: VirtualConnectionItem) -> VirtualConnectionItem:
79
+ url = f"{self.baseurl}/{virtual_connection.id}"
80
+ xml_request = RequestFactory.VirtualConnection.update(virtual_connection)
81
+ server_response = self.put_request(url, xml_request)
82
+ return VirtualConnectionItem.from_response(server_response.content, self.parent_srv.namespace)[0]
83
+
84
+ @api(version="3.23")
85
+ def get_revisions(
86
+ self, virtual_connection: VirtualConnectionItem, req_options: Optional[RequestOptions] = None
87
+ ) -> tuple[list[RevisionItem], PaginationItem]:
88
+ server_response = self.get_request(f"{self.baseurl}/{virtual_connection.id}/revisions", req_options)
89
+ pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
90
+ revisions = RevisionItem.from_response(server_response.content, self.parent_srv.namespace, virtual_connection)
91
+ return revisions, pagination_item
92
+
93
+ @api(version="3.23")
94
+ def download_revision(self, virtual_connection: VirtualConnectionItem, revision_number: int) -> str:
95
+ url = f"{self.baseurl}/{virtual_connection.id}/revisions/{revision_number}"
96
+ server_response = self.get_request(url)
97
+ virtual_connection = VirtualConnectionItem.from_response(server_response.content, self.parent_srv.namespace)[0]
98
+ return json.dumps(virtual_connection.content)
99
+
100
+ @api(version="3.23")
101
+ def delete(self, virtual_connection: Union[VirtualConnectionItem, str]) -> None:
102
+ vconn_id = getattr(virtual_connection, "id", virtual_connection)
103
+ self.delete_request(f"{self.baseurl}/{vconn_id}")
104
+
105
+ @api(version="3.23")
106
+ def publish(
107
+ self,
108
+ virtual_connection: VirtualConnectionItem,
109
+ virtual_connection_content: str,
110
+ mode: str = "CreateNew",
111
+ publish_as_draft: bool = False,
112
+ ) -> VirtualConnectionItem:
113
+ """
114
+ Publish a virtual connection to the server.
115
+
116
+ For the virtual_connection object, name, project_id, and owner_id are
117
+ required.
118
+
119
+ The virtual_connection_content can be a json string or a file path to a
120
+ json file.
121
+
122
+ The mode can be "CreateNew" or "Overwrite". If mode is
123
+ "Overwrite" and the virtual connection already exists, it will be
124
+ overwritten.
125
+
126
+ If publish_as_draft is True, the virtual connection will be published
127
+ as a draft, and the id of the draft will be on the response object.
128
+ """
129
+ try:
130
+ json.loads(virtual_connection_content)
131
+ except json.JSONDecodeError:
132
+ file = Path(virtual_connection_content)
133
+ if not file.exists():
134
+ raise RuntimeError(f"{virtual_connection_content} is not valid json nor an existing file path")
135
+ content = file.read_text()
136
+ else:
137
+ content = virtual_connection_content
138
+
139
+ if mode not in ["CreateNew", "Overwrite"]:
140
+ raise ValueError(f"Invalid mode: {mode}")
141
+ overwrite = mode == "Overwrite"
142
+
143
+ url = f"{self.baseurl}?overwrite={str(overwrite).lower()}&publishAsDraft={str(publish_as_draft).lower()}"
144
+ xml_request = RequestFactory.VirtualConnection.publish(virtual_connection, content)
145
+ server_response = self.post_request(url, xml_request)
146
+ return VirtualConnectionItem.from_response(server_response.content, self.parent_srv.namespace)[0]
147
+
148
+ @api(version="3.22")
149
+ def populate_permissions(self, item: VirtualConnectionItem) -> None:
150
+ self._permissions.populate(item)
151
+
152
+ @api(version="3.22")
153
+ def add_permissions(self, resource, rules):
154
+ return self._permissions.update(resource, rules)
155
+
156
+ @api(version="3.22")
157
+ def delete_permission(self, item, capability_item):
158
+ return self._permissions.delete(item, capability_item)
159
+
160
+ @api(version="3.23")
161
+ def add_tags(
162
+ self, virtual_connection: Union[VirtualConnectionItem, str], tags: Union[Iterable[str], str]
163
+ ) -> set[str]:
164
+ return super().add_tags(virtual_connection, tags)
165
+
166
+ @api(version="3.23")
167
+ def delete_tags(
168
+ self, virtual_connection: Union[VirtualConnectionItem, str], tags: Union[Iterable[str], str]
169
+ ) -> None:
170
+ return super().delete_tags(virtual_connection, tags)
171
+
172
+ @api(version="3.23")
173
+ def update_tags(self, virtual_connection: VirtualConnectionItem) -> None:
174
+ raise NotImplementedError("Update tags is not implemented for Virtual Connections")
@@ -6,7 +6,7 @@ from tableauserverclient.models import WebhookItem, PaginationItem
6
6
 
7
7
  from tableauserverclient.helpers.logging import logger
8
8
 
9
- from typing import List, Optional, TYPE_CHECKING, Tuple
9
+ from typing import Optional, TYPE_CHECKING
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from ..server import Server
@@ -15,14 +15,14 @@ if TYPE_CHECKING:
15
15
 
16
16
  class Webhooks(Endpoint):
17
17
  def __init__(self, parent_srv: "Server") -> None:
18
- super(Webhooks, self).__init__(parent_srv)
18
+ super().__init__(parent_srv)
19
19
 
20
20
  @property
21
21
  def baseurl(self) -> str:
22
- return "{0}/sites/{1}/webhooks".format(self.parent_srv.baseurl, self.parent_srv.site_id)
22
+ return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/webhooks"
23
23
 
24
24
  @api(version="3.6")
25
- def get(self, req_options: Optional["RequestOptions"] = None) -> Tuple[List[WebhookItem], PaginationItem]:
25
+ def get(self, req_options: Optional["RequestOptions"] = None) -> tuple[list[WebhookItem], PaginationItem]:
26
26
  logger.info("Querying all Webhooks on site")
27
27
  url = self.baseurl
28
28
  server_response = self.get_request(url, req_options)
@@ -35,8 +35,8 @@ class Webhooks(Endpoint):
35
35
  if not webhook_id:
36
36
  error = "Webhook ID undefined."
37
37
  raise ValueError(error)
38
- logger.info("Querying single webhook (ID: {0})".format(webhook_id))
39
- url = "{0}/{1}".format(self.baseurl, webhook_id)
38
+ logger.info(f"Querying single webhook (ID: {webhook_id})")
39
+ url = f"{self.baseurl}/{webhook_id}"
40
40
  server_response = self.get_request(url)
41
41
  return WebhookItem.from_response(server_response.content, self.parent_srv.namespace)[0]
42
42
 
@@ -45,9 +45,9 @@ class Webhooks(Endpoint):
45
45
  if not webhook_id:
46
46
  error = "Webhook ID undefined."
47
47
  raise ValueError(error)
48
- url = "{0}/{1}".format(self.baseurl, webhook_id)
48
+ url = f"{self.baseurl}/{webhook_id}"
49
49
  self.delete_request(url)
50
- logger.info("Deleted single webhook (ID: {0})".format(webhook_id))
50
+ logger.info(f"Deleted single webhook (ID: {webhook_id})")
51
51
 
52
52
  @api(version="3.6")
53
53
  def create(self, webhook_item: WebhookItem) -> WebhookItem:
@@ -56,7 +56,7 @@ class Webhooks(Endpoint):
56
56
  server_response = self.post_request(url, create_req)
57
57
  new_webhook = WebhookItem.from_response(server_response.content, self.parent_srv.namespace)[0]
58
58
 
59
- logger.info("Created new webhook (ID: {0})".format(new_webhook.id))
59
+ logger.info(f"Created new webhook (ID: {new_webhook.id})")
60
60
  return new_webhook
61
61
 
62
62
  @api(version="3.6")
@@ -64,7 +64,7 @@ class Webhooks(Endpoint):
64
64
  if not webhook_id:
65
65
  error = "Webhook ID undefined."
66
66
  raise ValueError(error)
67
- url = "{0}/{1}/test".format(self.baseurl, webhook_id)
67
+ url = f"{self.baseurl}/{webhook_id}/test"
68
68
  testOutcome = self.get_request(url)
69
- logger.info("Testing webhook (ID: {0} returned {1})".format(webhook_id, testOutcome))
69
+ logger.info(f"Testing webhook (ID: {webhook_id} returned {testOutcome})")
70
70
  return testOutcome