tableauserverclient 0.31__py3-none-any.whl → 0.33__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 (54) hide show
  1. tableauserverclient/__init__.py +74 -4
  2. tableauserverclient/_version.py +3 -3
  3. tableauserverclient/config.py +16 -4
  4. tableauserverclient/models/__init__.py +100 -37
  5. tableauserverclient/models/connection_item.py +4 -2
  6. tableauserverclient/models/database_item.py +6 -0
  7. tableauserverclient/models/datasource_item.py +18 -6
  8. tableauserverclient/models/favorites_item.py +7 -7
  9. tableauserverclient/models/flow_item.py +6 -6
  10. tableauserverclient/models/groupset_item.py +53 -0
  11. tableauserverclient/models/interval_item.py +27 -14
  12. tableauserverclient/models/job_item.py +18 -2
  13. tableauserverclient/models/linked_tasks_item.py +102 -0
  14. tableauserverclient/models/permissions_item.py +53 -5
  15. tableauserverclient/models/project_item.py +4 -3
  16. tableauserverclient/models/reference_item.py +5 -0
  17. tableauserverclient/models/tableau_auth.py +22 -23
  18. tableauserverclient/models/tableau_types.py +9 -7
  19. tableauserverclient/models/virtual_connection_item.py +77 -0
  20. tableauserverclient/server/__init__.py +83 -8
  21. tableauserverclient/server/endpoint/__init__.py +69 -28
  22. tableauserverclient/server/endpoint/auth_endpoint.py +3 -3
  23. tableauserverclient/server/endpoint/custom_views_endpoint.py +66 -5
  24. tableauserverclient/server/endpoint/databases_endpoint.py +21 -18
  25. tableauserverclient/server/endpoint/datasources_endpoint.py +115 -35
  26. tableauserverclient/server/endpoint/endpoint.py +53 -20
  27. tableauserverclient/server/endpoint/favorites_endpoint.py +1 -1
  28. tableauserverclient/server/endpoint/fileuploads_endpoint.py +2 -2
  29. tableauserverclient/server/endpoint/flow_runs_endpoint.py +45 -5
  30. tableauserverclient/server/endpoint/flows_endpoint.py +43 -16
  31. tableauserverclient/server/endpoint/groups_endpoint.py +85 -31
  32. tableauserverclient/server/endpoint/groupsets_endpoint.py +127 -0
  33. tableauserverclient/server/endpoint/jobs_endpoint.py +74 -7
  34. tableauserverclient/server/endpoint/linked_tasks_endpoint.py +45 -0
  35. tableauserverclient/server/endpoint/metadata_endpoint.py +3 -3
  36. tableauserverclient/server/endpoint/metrics_endpoint.py +1 -1
  37. tableauserverclient/server/endpoint/projects_endpoint.py +50 -18
  38. tableauserverclient/server/endpoint/resource_tagger.py +135 -3
  39. tableauserverclient/server/endpoint/tables_endpoint.py +19 -16
  40. tableauserverclient/server/endpoint/users_endpoint.py +40 -1
  41. tableauserverclient/server/endpoint/views_endpoint.py +97 -10
  42. tableauserverclient/server/endpoint/virtual_connections_endpoint.py +173 -0
  43. tableauserverclient/server/endpoint/workbooks_endpoint.py +97 -45
  44. tableauserverclient/server/pager.py +46 -46
  45. tableauserverclient/server/query.py +62 -33
  46. tableauserverclient/server/request_factory.py +192 -49
  47. tableauserverclient/server/request_options.py +4 -2
  48. tableauserverclient/server/server.py +13 -6
  49. {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/METADATA +15 -16
  50. {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/RECORD +54 -48
  51. {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/WHEEL +1 -1
  52. {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/LICENSE +0 -0
  53. {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/LICENSE.versioneer +0 -0
  54. {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/top_level.txt +0 -0
@@ -1,22 +1,24 @@
1
1
  import logging
2
2
 
3
- from .default_permissions_endpoint import _DefaultPermissionsEndpoint
4
- from .endpoint import QuerysetEndpoint, api, XML_CONTENT_TYPE
5
- from .exceptions import MissingRequiredFieldError
6
- from .permissions_endpoint import _PermissionsEndpoint
3
+ from tableauserverclient.server.endpoint.default_permissions_endpoint import _DefaultPermissionsEndpoint
4
+ from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api, XML_CONTENT_TYPE
5
+ from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError
6
+ from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
7
7
  from tableauserverclient.server import RequestFactory, RequestOptions
8
8
  from tableauserverclient.models import ProjectItem, PaginationItem, Resource
9
9
 
10
10
  from typing import List, Optional, Tuple, TYPE_CHECKING
11
11
 
12
+ from tableauserverclient.server.query import QuerySet
13
+
12
14
  if TYPE_CHECKING:
13
- from ..server import Server
14
- from ..request_options import RequestOptions
15
+ from tableauserverclient.server.server import Server
16
+ from tableauserverclient.server.request_options import RequestOptions
15
17
 
16
18
  from tableauserverclient.helpers.logging import logger
17
19
 
18
20
 
19
- class Projects(QuerysetEndpoint):
21
+ class Projects(QuerysetEndpoint[ProjectItem]):
20
22
  def __init__(self, parent_srv: "Server") -> None:
21
23
  super(Projects, self).__init__(parent_srv)
22
24
 
@@ -75,17 +77,6 @@ class Projects(QuerysetEndpoint):
75
77
  def populate_permissions(self, item: ProjectItem) -> None:
76
78
  self._permissions.populate(item)
77
79
 
78
- @api(version="2.0")
79
- def update_permission(self, item, rules):
80
- import warnings
81
-
82
- warnings.warn(
83
- "Server.projects.update_permission is deprecated, "
84
- "please use Server.projects.update_permissions instead.",
85
- DeprecationWarning,
86
- )
87
- return self._permissions.update(item, rules)
88
-
89
80
  @api(version="2.0")
90
81
  def update_permissions(self, item, rules):
91
82
  return self._permissions.update(item, rules)
@@ -165,3 +156,44 @@ class Projects(QuerysetEndpoint):
165
156
  @api(version="3.4")
166
157
  def delete_lens_default_permissions(self, item, rule):
167
158
  self._default_permissions.delete_default_permission(item, rule, Resource.Lens)
159
+
160
+ def filter(self, *invalid, page_size: Optional[int] = None, **kwargs) -> QuerySet[ProjectItem]:
161
+ """
162
+ Queries the Tableau Server for items using the specified filters. Page
163
+ size can be specified to limit the number of items returned in a single
164
+ request. If not specified, the default page size is 100. Page size can
165
+ be an integer between 1 and 1000.
166
+
167
+ No positional arguments are allowed. All filters must be specified as
168
+ keyword arguments. If you use the equality operator, you can specify it
169
+ through <field_name>=<value>. If you want to use a different operator,
170
+ you can specify it through <field_name>__<operator>=<value>. Field
171
+ names can either be in snake_case or camelCase.
172
+
173
+ This endpoint supports the following fields and operators:
174
+
175
+
176
+ created_at=...
177
+ created_at__gt=...
178
+ created_at__gte=...
179
+ created_at__lt=...
180
+ created_at__lte=...
181
+ name=...
182
+ name__in=...
183
+ owner_domain=...
184
+ owner_domain__in=...
185
+ owner_email=...
186
+ owner_email__in=...
187
+ owner_name=...
188
+ owner_name__in=...
189
+ parent_project_id=...
190
+ parent_project_id__in=...
191
+ top_level_project=...
192
+ updated_at=...
193
+ updated_at__gt=...
194
+ updated_at__gte=...
195
+ updated_at__lt=...
196
+ updated_at__lte=...
197
+ """
198
+
199
+ return super().filter(*invalid, page_size=page_size, **kwargs)
@@ -1,14 +1,25 @@
1
+ import abc
1
2
  import copy
3
+ from typing import Generic, Iterable, Optional, Protocol, Set, TypeVar, Union, TYPE_CHECKING, runtime_checkable
2
4
  import urllib.parse
3
5
 
4
- from .endpoint import Endpoint
5
- from .exceptions import ServerResponseError
6
- from ..exceptions import EndpointUnavailableError
6
+ from tableauserverclient.server.endpoint.endpoint import Endpoint, api
7
+ from tableauserverclient.server.endpoint.exceptions import ServerResponseError
8
+ from tableauserverclient.server.exceptions import EndpointUnavailableError
7
9
  from tableauserverclient.server import RequestFactory
8
10
  from tableauserverclient.models import TagItem
9
11
 
10
12
  from tableauserverclient.helpers.logging import logger
11
13
 
14
+ if TYPE_CHECKING:
15
+ from tableauserverclient.models.column_item import ColumnItem
16
+ from tableauserverclient.models.database_item import DatabaseItem
17
+ from tableauserverclient.models.datasource_item import DatasourceItem
18
+ from tableauserverclient.models.flow_item import FlowItem
19
+ from tableauserverclient.models.table_item import TableItem
20
+ from tableauserverclient.models.workbook_item import WorkbookItem
21
+ from tableauserverclient.server.server import Server
22
+
12
23
 
13
24
  class _ResourceTagger(Endpoint):
14
25
  # Add new tags to resource
@@ -49,3 +60,124 @@ class _ResourceTagger(Endpoint):
49
60
  resource_item.tags = self._add_tags(baseurl, resource_item.id, add_set)
50
61
  resource_item._initial_tags = copy.copy(resource_item.tags)
51
62
  logger.info("Updated tags to {0}".format(resource_item.tags))
63
+
64
+
65
+ class Response(Protocol):
66
+ content: bytes
67
+
68
+
69
+ @runtime_checkable
70
+ class Taggable(Protocol):
71
+ tags: Set[str]
72
+ _initial_tags: Set[str]
73
+
74
+ @property
75
+ def id(self) -> Optional[str]:
76
+ pass
77
+
78
+
79
+ T = TypeVar("T")
80
+
81
+
82
+ class TaggingMixin(abc.ABC, Generic[T]):
83
+ parent_srv: "Server"
84
+
85
+ @property
86
+ @abc.abstractmethod
87
+ def baseurl(self) -> str:
88
+ pass
89
+
90
+ @abc.abstractmethod
91
+ def put_request(self, url, request) -> Response:
92
+ pass
93
+
94
+ @abc.abstractmethod
95
+ def delete_request(self, url) -> None:
96
+ pass
97
+
98
+ def add_tags(self, item: Union[T, str], tags: Union[Iterable[str], str]) -> Set[str]:
99
+ item_id = getattr(item, "id", item)
100
+
101
+ if not isinstance(item_id, str):
102
+ raise ValueError("ID not found.")
103
+
104
+ if isinstance(tags, str):
105
+ tag_set = set([tags])
106
+ else:
107
+ tag_set = set(tags)
108
+
109
+ url = f"{self.baseurl}/{item_id}/tags"
110
+ add_req = RequestFactory.Tag.add_req(tag_set)
111
+ server_response = self.put_request(url, add_req)
112
+ return TagItem.from_response(server_response.content, self.parent_srv.namespace)
113
+
114
+ def delete_tags(self, item: Union[T, str], tags: Union[Iterable[str], str]) -> None:
115
+ item_id = getattr(item, "id", item)
116
+
117
+ if not isinstance(item_id, str):
118
+ raise ValueError("ID not found.")
119
+
120
+ if isinstance(tags, str):
121
+ tag_set = set([tags])
122
+ else:
123
+ tag_set = set(tags)
124
+
125
+ for tag in tag_set:
126
+ encoded_tag_name = urllib.parse.quote(tag)
127
+ url = f"{self.baseurl}/{item_id}/tags/{encoded_tag_name}"
128
+ self.delete_request(url)
129
+
130
+ def update_tags(self, item: T) -> None:
131
+ if (initial_tags := getattr(item, "_initial_tags", None)) is None:
132
+ raise ValueError(f"{item} does not have initial tags.")
133
+ if (tags := getattr(item, "tags", None)) is None:
134
+ raise ValueError(f"{item} does not have tags.")
135
+ if tags == initial_tags:
136
+ return
137
+
138
+ add_set = tags - initial_tags
139
+ remove_set = initial_tags - tags
140
+ self.delete_tags(item, remove_set)
141
+ if add_set:
142
+ tags = self.add_tags(item, add_set)
143
+ setattr(item, "tags", tags)
144
+
145
+ setattr(item, "_initial_tags", copy.copy(tags))
146
+ logger.info(f"Updated tags to {tags}")
147
+
148
+
149
+ content = Iterable[Union["ColumnItem", "DatabaseItem", "DatasourceItem", "FlowItem", "TableItem", "WorkbookItem"]]
150
+
151
+
152
+ class Tags(Endpoint):
153
+ def __init__(self, parent_srv: "Server"):
154
+ super().__init__(parent_srv)
155
+
156
+ @property
157
+ def baseurl(self):
158
+ return f"{self.parent_srv.baseurl}/tags"
159
+
160
+ @api(version="3.9")
161
+ def batch_add(self, tags: Union[Iterable[str], str], content: content) -> Set[str]:
162
+ if isinstance(tags, str):
163
+ tag_set = set([tags])
164
+ else:
165
+ tag_set = set(tags)
166
+
167
+ url = f"{self.baseurl}:batchCreate"
168
+ batch_create_req = RequestFactory.Tag.batch_create(tag_set, content)
169
+ server_response = self.put_request(url, batch_create_req)
170
+ return TagItem.from_response(server_response.content, self.parent_srv.namespace)
171
+
172
+ @api(version="3.9")
173
+ def batch_delete(self, tags: Union[Iterable[str], str], content: content) -> Set[str]:
174
+ if isinstance(tags, str):
175
+ tag_set = set([tags])
176
+ else:
177
+ tag_set = set(tags)
178
+
179
+ url = f"{self.baseurl}:batchDelete"
180
+ # The batch delete XML is the same as the batch create XML.
181
+ batch_delete_req = RequestFactory.Tag.batch_create(tag_set, content)
182
+ server_response = self.put_request(url, batch_delete_req)
183
+ return TagItem.from_response(server_response.content, self.parent_srv.namespace)
@@ -1,17 +1,19 @@
1
1
  import logging
2
+ from typing import Iterable, Set, Union
2
3
 
3
- from .dqw_endpoint import _DataQualityWarningEndpoint
4
- from .endpoint import api, Endpoint
5
- from .exceptions import MissingRequiredFieldError
6
- from .permissions_endpoint import _PermissionsEndpoint
4
+ from tableauserverclient.server.endpoint.dqw_endpoint import _DataQualityWarningEndpoint
5
+ from tableauserverclient.server.endpoint.endpoint import api, Endpoint
6
+ from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError
7
+ from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
8
+ from tableauserverclient.server.endpoint.resource_tagger import TaggingMixin
7
9
  from tableauserverclient.server import RequestFactory
8
10
  from tableauserverclient.models import TableItem, ColumnItem, PaginationItem
9
- from ..pager import Pager
11
+ from tableauserverclient.server.pager import Pager
10
12
 
11
13
  from tableauserverclient.helpers.logging import logger
12
14
 
13
15
 
14
- class Tables(Endpoint):
16
+ class Tables(Endpoint, TaggingMixin[TableItem]):
15
17
  def __init__(self, parent_srv):
16
18
  super(Tables, self).__init__(parent_srv)
17
19
 
@@ -101,16 +103,6 @@ class Tables(Endpoint):
101
103
  def populate_permissions(self, item):
102
104
  self._permissions.populate(item)
103
105
 
104
- @api(version="3.5")
105
- def update_permission(self, item, rules):
106
- import warnings
107
-
108
- warnings.warn(
109
- "Server.tables.update_permission is deprecated, " "please use Server.tables.update_permissions instead.",
110
- DeprecationWarning,
111
- )
112
- return self._permissions.update(item, rules)
113
-
114
106
  @api(version="3.5")
115
107
  def update_permissions(self, item, rules):
116
108
  return self._permissions.update(item, rules)
@@ -134,3 +126,14 @@ class Tables(Endpoint):
134
126
  @api(version="3.5")
135
127
  def delete_dqw(self, item):
136
128
  self._data_quality_warnings.clear(item)
129
+
130
+ @api(version="3.9")
131
+ def add_tags(self, item: Union[TableItem, str], tags: Union[Iterable[str], str]) -> Set[str]:
132
+ return super().add_tags(item, tags)
133
+
134
+ @api(version="3.9")
135
+ def delete_tags(self, item: Union[TableItem, str], tags: Union[Iterable[str], str]) -> None:
136
+ return super().delete_tags(item, tags)
137
+
138
+ def update_tags(self, item: TableItem) -> None: # type: ignore
139
+ raise NotImplementedError("Update tags is not implemented for TableItem")
@@ -2,6 +2,8 @@ import copy
2
2
  import logging
3
3
  from typing import List, Optional, Tuple
4
4
 
5
+ from tableauserverclient.server.query import QuerySet
6
+
5
7
  from .endpoint import QuerysetEndpoint, api
6
8
  from .exceptions import MissingRequiredFieldError, ServerResponseError
7
9
  from tableauserverclient.server import RequestFactory, RequestOptions
@@ -11,7 +13,7 @@ from ..pager import Pager
11
13
  from tableauserverclient.helpers.logging import logger
12
14
 
13
15
 
14
- class Users(QuerysetEndpoint):
16
+ class Users(QuerysetEndpoint[UserItem]):
15
17
  @property
16
18
  def baseurl(self) -> str:
17
19
  return "{0}/sites/{1}/users".format(self.parent_srv.baseurl, self.parent_srv.site_id)
@@ -166,3 +168,40 @@ class Users(QuerysetEndpoint):
166
168
  group_item = GroupItem.from_response(server_response.content, self.parent_srv.namespace)
167
169
  pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
168
170
  return group_item, pagination_item
171
+
172
+ def filter(self, *invalid, page_size: Optional[int] = None, **kwargs) -> QuerySet[UserItem]:
173
+ """
174
+ Queries the Tableau Server for items using the specified filters. Page
175
+ size can be specified to limit the number of items returned in a single
176
+ request. If not specified, the default page size is 100. Page size can
177
+ be an integer between 1 and 1000.
178
+
179
+ No positional arguments are allowed. All filters must be specified as
180
+ keyword arguments. If you use the equality operator, you can specify it
181
+ through <field_name>=<value>. If you want to use a different operator,
182
+ you can specify it through <field_name>__<operator>=<value>. Field
183
+ names can either be in snake_case or camelCase.
184
+
185
+ This endpoint supports the following fields and operators:
186
+
187
+
188
+ domain_name=...
189
+ domain_name__in=...
190
+ friendly_name=...
191
+ friendly_name__in=...
192
+ is_local=...
193
+ last_login=...
194
+ last_login__gt=...
195
+ last_login__gte=...
196
+ last_login__lt=...
197
+ last_login__lte=...
198
+ luid=...
199
+ luid__in=...
200
+ name__cieq=...
201
+ name=...
202
+ name__in=...
203
+ site_role=...
204
+ site_role__in=...
205
+ """
206
+
207
+ return super().filter(*invalid, page_size=page_size, **kwargs)
@@ -1,18 +1,20 @@
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 Iterable, Iterator, List, Optional, Set, Tuple, TYPE_CHECKING, Union
13
15
 
14
16
  if TYPE_CHECKING:
15
- from ..request_options import (
17
+ from tableauserverclient.server.request_options import (
16
18
  RequestOptions,
17
19
  CSVRequestOptions,
18
20
  PDFRequestOptions,
@@ -21,10 +23,9 @@ if TYPE_CHECKING:
21
23
  )
22
24
 
23
25
 
24
- class Views(QuerysetEndpoint):
26
+ class Views(QuerysetEndpoint[ViewItem], TaggingMixin[ViewItem]):
25
27
  def __init__(self, parent_srv):
26
28
  super(Views, self).__init__(parent_srv)
27
- self._resource_tagger = _ResourceTagger(parent_srv)
28
29
  self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
29
30
 
30
31
  # Used because populate_preview_image functionaliy requires workbook endpoint
@@ -50,12 +51,14 @@ class Views(QuerysetEndpoint):
50
51
  return all_view_items, pagination_item
51
52
 
52
53
  @api(version="3.1")
53
- def get_by_id(self, view_id: str) -> ViewItem:
54
+ def get_by_id(self, view_id: str, usage: bool = False) -> ViewItem:
54
55
  if not view_id:
55
56
  error = "View item missing ID."
56
57
  raise MissingRequiredFieldError(error)
57
58
  logger.info("Querying single view (ID: {0})".format(view_id))
58
59
  url = "{0}/{1}".format(self.baseurl, view_id)
60
+ if usage:
61
+ url += "?includeUsageStatistics=true"
59
62
  server_response = self.get_request(url)
60
63
  return ViewItem.from_response(server_response.content, self.parent_srv.namespace)[0]
61
64
 
@@ -167,7 +170,91 @@ class Views(QuerysetEndpoint):
167
170
  error = "View item missing ID. View must be retrieved from server first."
168
171
  raise MissingRequiredFieldError(error)
169
172
 
170
- self._resource_tagger.update_tags(self.baseurl, view_item)
173
+ self.update_tags(view_item)
171
174
 
172
175
  # Returning view item to stay consistent with datasource/view update functions
173
176
  return view_item
177
+
178
+ @api(version="1.0")
179
+ def add_tags(self, item: Union[ViewItem, str], tags: Union[Iterable[str], str]) -> Set[str]:
180
+ return super().add_tags(item, tags)
181
+
182
+ @api(version="1.0")
183
+ def delete_tags(self, item: Union[ViewItem, str], tags: Union[Iterable[str], str]) -> None:
184
+ return super().delete_tags(item, tags)
185
+
186
+ @api(version="1.0")
187
+ def update_tags(self, item: ViewItem) -> None:
188
+ return super().update_tags(item)
189
+
190
+ def filter(self, *invalid, page_size: Optional[int] = None, **kwargs) -> QuerySet[ViewItem]:
191
+ """
192
+ Queries the Tableau Server for items using the specified filters. Page
193
+ size can be specified to limit the number of items returned in a single
194
+ request. If not specified, the default page size is 100. Page size can
195
+ be an integer between 1 and 1000.
196
+
197
+ No positional arguments are allowed. All filters must be specified as
198
+ keyword arguments. If you use the equality operator, you can specify it
199
+ through <field_name>=<value>. If you want to use a different operator,
200
+ you can specify it through <field_name>__<operator>=<value>. Field
201
+ names can either be in snake_case or camelCase.
202
+
203
+ This endpoint supports the following fields and operators:
204
+
205
+
206
+ caption=...
207
+ caption__in=...
208
+ content_url=...
209
+ content_url__in=...
210
+ created_at=...
211
+ created_at__gt=...
212
+ created_at__gte=...
213
+ created_at__lt=...
214
+ created_at__lte=...
215
+ favorites_total=...
216
+ favorites_total__gt=...
217
+ favorites_total__gte=...
218
+ favorites_total__lt=...
219
+ favorites_total__lte=...
220
+ fields=...
221
+ fields__in=...
222
+ hits_total=...
223
+ hits_total__gt=...
224
+ hits_total__gte=...
225
+ hits_total__lt=...
226
+ hits_total__lte=...
227
+ name=...
228
+ name__in=...
229
+ owner_domain=...
230
+ owner_domain__in=...
231
+ owner_email=...
232
+ owner_email__in=...
233
+ owner_name=...
234
+ project_name=...
235
+ project_name__in=...
236
+ sheet_number=...
237
+ sheet_number__gt=...
238
+ sheet_number__gte=...
239
+ sheet_number__lt=...
240
+ sheet_number__lte=...
241
+ sheet_type=...
242
+ sheet_type__in=...
243
+ tags=...
244
+ tags__in=...
245
+ title=...
246
+ title__in=...
247
+ updated_at=...
248
+ updated_at__gt=...
249
+ updated_at__gte=...
250
+ updated_at__lt=...
251
+ updated_at__lte=...
252
+ view_url_name=...
253
+ view_url_name__in=...
254
+ workbook_description=...
255
+ workbook_description__in=...
256
+ workbook_name=...
257
+ workbook_name__in=...
258
+ """
259
+
260
+ return super().filter(*invalid, page_size=page_size, **kwargs)
@@ -0,0 +1,173 @@
1
+ from functools import partial
2
+ import json
3
+ from pathlib import Path
4
+ from typing import Iterable, List, Optional, Set, TYPE_CHECKING, Tuple, Union
5
+
6
+ from tableauserverclient.models.connection_item import ConnectionItem
7
+ from tableauserverclient.models.pagination_item import PaginationItem
8
+ from tableauserverclient.models.revision_item import RevisionItem
9
+ from tableauserverclient.models.virtual_connection_item import VirtualConnectionItem
10
+ from tableauserverclient.server.request_factory import RequestFactory
11
+ from tableauserverclient.server.request_options import RequestOptions
12
+ from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api
13
+ from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
14
+ from tableauserverclient.server.endpoint.resource_tagger import TaggingMixin
15
+ from tableauserverclient.server.pager import Pager
16
+
17
+ if TYPE_CHECKING:
18
+ from tableauserverclient.server import Server
19
+
20
+
21
+ class VirtualConnections(QuerysetEndpoint[VirtualConnectionItem], TaggingMixin):
22
+ def __init__(self, parent_srv: "Server") -> None:
23
+ super().__init__(parent_srv)
24
+ self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
25
+
26
+ @property
27
+ def baseurl(self) -> str:
28
+ return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/virtualConnections"
29
+
30
+ @api(version="3.18")
31
+ def get(self, req_options: Optional[RequestOptions] = None) -> Tuple[List[VirtualConnectionItem], PaginationItem]:
32
+ server_response = self.get_request(self.baseurl, req_options)
33
+ pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
34
+ virtual_connections = VirtualConnectionItem.from_response(server_response.content, self.parent_srv.namespace)
35
+ return virtual_connections, pagination_item
36
+
37
+ @api(version="3.18")
38
+ def populate_connections(self, virtual_connection: VirtualConnectionItem) -> VirtualConnectionItem:
39
+ def _connection_fetcher():
40
+ return Pager(partial(self._get_virtual_database_connections, virtual_connection))
41
+
42
+ virtual_connection._connections = _connection_fetcher
43
+ return virtual_connection
44
+
45
+ def _get_virtual_database_connections(
46
+ self, virtual_connection: VirtualConnectionItem, req_options: Optional[RequestOptions] = None
47
+ ) -> Tuple[List[ConnectionItem], PaginationItem]:
48
+ server_response = self.get_request(f"{self.baseurl}/{virtual_connection.id}/connections", req_options)
49
+ connections = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)
50
+ pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
51
+
52
+ return connections, pagination_item
53
+
54
+ @api(version="3.18")
55
+ def update_connection_db_connection(
56
+ self, virtual_connection: Union[str, VirtualConnectionItem], connection: ConnectionItem
57
+ ) -> ConnectionItem:
58
+ vconn_id = getattr(virtual_connection, "id", virtual_connection)
59
+ url = f"{self.baseurl}/{vconn_id}/connections/{connection.id}/modify"
60
+ xml_request = RequestFactory.VirtualConnection.update_db_connection(connection)
61
+ server_response = self.put_request(url, xml_request)
62
+ return ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)[0]
63
+
64
+ @api(version="3.23")
65
+ def get_by_id(self, virtual_connection: Union[str, VirtualConnectionItem]) -> VirtualConnectionItem:
66
+ vconn_id = getattr(virtual_connection, "id", virtual_connection)
67
+ url = f"{self.baseurl}/{vconn_id}"
68
+ server_response = self.get_request(url)
69
+ return VirtualConnectionItem.from_response(server_response.content, self.parent_srv.namespace)[0]
70
+
71
+ @api(version="3.23")
72
+ def download(self, virtual_connection: Union[str, VirtualConnectionItem]) -> str:
73
+ v_conn = self.get_by_id(virtual_connection)
74
+ return json.dumps(v_conn.content)
75
+
76
+ @api(version="3.23")
77
+ def update(self, virtual_connection: VirtualConnectionItem) -> VirtualConnectionItem:
78
+ url = f"{self.baseurl}/{virtual_connection.id}"
79
+ xml_request = RequestFactory.VirtualConnection.update(virtual_connection)
80
+ server_response = self.put_request(url, xml_request)
81
+ return VirtualConnectionItem.from_response(server_response.content, self.parent_srv.namespace)[0]
82
+
83
+ @api(version="3.23")
84
+ def get_revisions(
85
+ self, virtual_connection: VirtualConnectionItem, req_options: Optional[RequestOptions] = None
86
+ ) -> Tuple[List[RevisionItem], PaginationItem]:
87
+ server_response = self.get_request(f"{self.baseurl}/{virtual_connection.id}/revisions", req_options)
88
+ pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
89
+ revisions = RevisionItem.from_response(server_response.content, self.parent_srv.namespace, virtual_connection)
90
+ return revisions, pagination_item
91
+
92
+ @api(version="3.23")
93
+ def download_revision(self, virtual_connection: VirtualConnectionItem, revision_number: int) -> str:
94
+ url = f"{self.baseurl}/{virtual_connection.id}/revisions/{revision_number}"
95
+ server_response = self.get_request(url)
96
+ virtual_connection = VirtualConnectionItem.from_response(server_response.content, self.parent_srv.namespace)[0]
97
+ return json.dumps(virtual_connection.content)
98
+
99
+ @api(version="3.23")
100
+ def delete(self, virtual_connection: Union[VirtualConnectionItem, str]) -> None:
101
+ vconn_id = getattr(virtual_connection, "id", virtual_connection)
102
+ self.delete_request(f"{self.baseurl}/{vconn_id}")
103
+
104
+ @api(version="3.23")
105
+ def publish(
106
+ self,
107
+ virtual_connection: VirtualConnectionItem,
108
+ virtual_connection_content: str,
109
+ mode: str = "CreateNew",
110
+ publish_as_draft: bool = False,
111
+ ) -> VirtualConnectionItem:
112
+ """
113
+ Publish a virtual connection to the server.
114
+
115
+ For the virtual_connection object, name, project_id, and owner_id are
116
+ required.
117
+
118
+ The virtual_connection_content can be a json string or a file path to a
119
+ json file.
120
+
121
+ The mode can be "CreateNew" or "Overwrite". If mode is
122
+ "Overwrite" and the virtual connection already exists, it will be
123
+ overwritten.
124
+
125
+ If publish_as_draft is True, the virtual connection will be published
126
+ as a draft, and the id of the draft will be on the response object.
127
+ """
128
+ try:
129
+ json.loads(virtual_connection_content)
130
+ except json.JSONDecodeError:
131
+ file = Path(virtual_connection_content)
132
+ if not file.exists():
133
+ raise RuntimeError(f"{virtual_connection_content} is not valid json nor an existing file path")
134
+ content = file.read_text()
135
+ else:
136
+ content = virtual_connection_content
137
+
138
+ if mode not in ["CreateNew", "Overwrite"]:
139
+ raise ValueError(f"Invalid mode: {mode}")
140
+ overwrite = mode == "Overwrite"
141
+
142
+ url = f"{self.baseurl}?overwrite={str(overwrite).lower()}&publishAsDraft={str(publish_as_draft).lower()}"
143
+ xml_request = RequestFactory.VirtualConnection.publish(virtual_connection, content)
144
+ server_response = self.post_request(url, xml_request)
145
+ return VirtualConnectionItem.from_response(server_response.content, self.parent_srv.namespace)[0]
146
+
147
+ @api(version="3.22")
148
+ def populate_permissions(self, item: VirtualConnectionItem) -> None:
149
+ self._permissions.populate(item)
150
+
151
+ @api(version="3.22")
152
+ def add_permissions(self, resource, rules):
153
+ return self._permissions.update(resource, rules)
154
+
155
+ @api(version="3.22")
156
+ def delete_permission(self, item, capability_item):
157
+ return self._permissions.delete(item, capability_item)
158
+
159
+ @api(version="3.23")
160
+ def add_tags(
161
+ self, virtual_connection: Union[VirtualConnectionItem, str], tags: Union[Iterable[str], str]
162
+ ) -> Set[str]:
163
+ return super().add_tags(virtual_connection, tags)
164
+
165
+ @api(version="3.23")
166
+ def delete_tags(
167
+ self, virtual_connection: Union[VirtualConnectionItem, str], tags: Union[Iterable[str], str]
168
+ ) -> None:
169
+ return super().delete_tags(virtual_connection, tags)
170
+
171
+ @api(version="3.23")
172
+ def update_tags(self, virtual_connection: VirtualConnectionItem) -> None:
173
+ raise NotImplementedError("Update tags is not implemented for Virtual Connections")