tableauserverclient 0.32__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.
- tableauserverclient/__init__.py +10 -0
- tableauserverclient/_version.py +3 -3
- tableauserverclient/config.py +16 -4
- tableauserverclient/models/__init__.py +12 -0
- tableauserverclient/models/connection_item.py +4 -2
- tableauserverclient/models/database_item.py +6 -0
- tableauserverclient/models/flow_item.py +6 -6
- tableauserverclient/models/groupset_item.py +53 -0
- tableauserverclient/models/interval_item.py +27 -14
- tableauserverclient/models/job_item.py +18 -2
- tableauserverclient/models/linked_tasks_item.py +102 -0
- tableauserverclient/models/permissions_item.py +7 -4
- tableauserverclient/models/tableau_types.py +9 -7
- tableauserverclient/models/virtual_connection_item.py +77 -0
- tableauserverclient/server/endpoint/__init__.py +8 -0
- tableauserverclient/server/endpoint/auth_endpoint.py +3 -3
- tableauserverclient/server/endpoint/custom_views_endpoint.py +65 -4
- tableauserverclient/server/endpoint/databases_endpoint.py +21 -7
- tableauserverclient/server/endpoint/datasources_endpoint.py +105 -9
- tableauserverclient/server/endpoint/endpoint.py +32 -14
- tableauserverclient/server/endpoint/fileuploads_endpoint.py +2 -2
- tableauserverclient/server/endpoint/flow_runs_endpoint.py +44 -4
- tableauserverclient/server/endpoint/flows_endpoint.py +43 -6
- tableauserverclient/server/endpoint/groups_endpoint.py +82 -14
- tableauserverclient/server/endpoint/groupsets_endpoint.py +127 -0
- tableauserverclient/server/endpoint/jobs_endpoint.py +74 -7
- tableauserverclient/server/endpoint/linked_tasks_endpoint.py +45 -0
- tableauserverclient/server/endpoint/projects_endpoint.py +43 -0
- tableauserverclient/server/endpoint/resource_tagger.py +135 -3
- tableauserverclient/server/endpoint/tables_endpoint.py +19 -6
- tableauserverclient/server/endpoint/users_endpoint.py +39 -0
- tableauserverclient/server/endpoint/views_endpoint.py +94 -9
- tableauserverclient/server/endpoint/virtual_connections_endpoint.py +173 -0
- tableauserverclient/server/endpoint/workbooks_endpoint.py +91 -10
- tableauserverclient/server/pager.py +6 -7
- tableauserverclient/server/query.py +2 -1
- tableauserverclient/server/request_factory.py +178 -7
- tableauserverclient/server/request_options.py +4 -2
- tableauserverclient/server/server.py +8 -0
- {tableauserverclient-0.32.dist-info → tableauserverclient-0.33.dist-info}/METADATA +15 -15
- {tableauserverclient-0.32.dist-info → tableauserverclient-0.33.dist-info}/RECORD +45 -39
- {tableauserverclient-0.32.dist-info → tableauserverclient-0.33.dist-info}/WHEEL +1 -1
- {tableauserverclient-0.32.dist-info → tableauserverclient-0.33.dist-info}/LICENSE +0 -0
- {tableauserverclient-0.32.dist-info → tableauserverclient-0.33.dist-info}/LICENSE.versioneer +0 -0
- {tableauserverclient-0.32.dist-info → tableauserverclient-0.33.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
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
|
|
|
@@ -124,3 +126,14 @@ class Tables(Endpoint):
|
|
|
124
126
|
@api(version="3.5")
|
|
125
127
|
def delete_dqw(self, item):
|
|
126
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
|
|
@@ -166,3 +168,40 @@ class Users(QuerysetEndpoint[UserItem]):
|
|
|
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
|
|
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
|
|
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[ViewItem]):
|
|
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
|
|
@@ -169,7 +170,91 @@ class Views(QuerysetEndpoint[ViewItem]):
|
|
|
169
170
|
error = "View item missing ID. View must be retrieved from server first."
|
|
170
171
|
raise MissingRequiredFieldError(error)
|
|
171
172
|
|
|
172
|
-
self.
|
|
173
|
+
self.update_tags(view_item)
|
|
173
174
|
|
|
174
175
|
# Returning view item to stay consistent with datasource/view update functions
|
|
175
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")
|
|
@@ -7,11 +7,12 @@ from contextlib import closing
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
9
|
from tableauserverclient.helpers.headers import fix_filename
|
|
10
|
+
from tableauserverclient.server.query import QuerySet
|
|
10
11
|
|
|
11
|
-
from .endpoint import QuerysetEndpoint, api, parameter_added_in
|
|
12
|
-
from .exceptions import InternalServerError, MissingRequiredFieldError
|
|
13
|
-
from .permissions_endpoint import _PermissionsEndpoint
|
|
14
|
-
from .resource_tagger import
|
|
12
|
+
from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api, parameter_added_in
|
|
13
|
+
from tableauserverclient.server.endpoint.exceptions import InternalServerError, MissingRequiredFieldError
|
|
14
|
+
from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
|
|
15
|
+
from tableauserverclient.server.endpoint.resource_tagger import TaggingMixin
|
|
15
16
|
|
|
16
17
|
from tableauserverclient.filesys_helpers import (
|
|
17
18
|
to_filename,
|
|
@@ -24,9 +25,11 @@ from tableauserverclient.models import WorkbookItem, ConnectionItem, ViewItem, P
|
|
|
24
25
|
from tableauserverclient.server import RequestFactory
|
|
25
26
|
|
|
26
27
|
from typing import (
|
|
28
|
+
Iterable,
|
|
27
29
|
List,
|
|
28
30
|
Optional,
|
|
29
31
|
Sequence,
|
|
32
|
+
Set,
|
|
30
33
|
Tuple,
|
|
31
34
|
TYPE_CHECKING,
|
|
32
35
|
Union,
|
|
@@ -35,8 +38,8 @@ from typing import (
|
|
|
35
38
|
if TYPE_CHECKING:
|
|
36
39
|
from tableauserverclient.server import Server
|
|
37
40
|
from tableauserverclient.server.request_options import RequestOptions
|
|
38
|
-
from tableauserverclient.models import DatasourceItem
|
|
39
|
-
from .schedules_endpoint import AddResponse
|
|
41
|
+
from tableauserverclient.models import DatasourceItem
|
|
42
|
+
from tableauserverclient.server.endpoint.schedules_endpoint import AddResponse
|
|
40
43
|
|
|
41
44
|
io_types_r = (io.BytesIO, io.BufferedReader)
|
|
42
45
|
io_types_w = (io.BytesIO, io.BufferedWriter)
|
|
@@ -56,10 +59,9 @@ PathOrFileR = Union[FilePath, FileObjectR]
|
|
|
56
59
|
PathOrFileW = Union[FilePath, FileObjectW]
|
|
57
60
|
|
|
58
61
|
|
|
59
|
-
class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
62
|
+
class Workbooks(QuerysetEndpoint[WorkbookItem], TaggingMixin[WorkbookItem]):
|
|
60
63
|
def __init__(self, parent_srv: "Server") -> None:
|
|
61
64
|
super(Workbooks, self).__init__(parent_srv)
|
|
62
|
-
self._resource_tagger = _ResourceTagger(parent_srv)
|
|
63
65
|
self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
|
|
64
66
|
|
|
65
67
|
return None
|
|
@@ -147,7 +149,7 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
147
149
|
error = "Workbook item missing ID. Workbook must be retrieved from server first."
|
|
148
150
|
raise MissingRequiredFieldError(error)
|
|
149
151
|
|
|
150
|
-
self.
|
|
152
|
+
self.update_tags(workbook_item)
|
|
151
153
|
|
|
152
154
|
# Update the workbook itself
|
|
153
155
|
url = "{0}/{1}".format(self.baseurl, workbook_item.id)
|
|
@@ -182,7 +184,7 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
182
184
|
workbook_id: str,
|
|
183
185
|
filepath: Optional[PathOrFileW] = None,
|
|
184
186
|
include_extract: bool = True,
|
|
185
|
-
) ->
|
|
187
|
+
) -> PathOrFileW:
|
|
186
188
|
return self.download_revision(
|
|
187
189
|
workbook_id,
|
|
188
190
|
None,
|
|
@@ -498,3 +500,82 @@ class Workbooks(QuerysetEndpoint[WorkbookItem]):
|
|
|
498
500
|
self, schedule_id: str, item: WorkbookItem
|
|
499
501
|
) -> List["AddResponse"]: # actually should return a task
|
|
500
502
|
return self.parent_srv.schedules.add_to_schedule(schedule_id, workbook=item)
|
|
503
|
+
|
|
504
|
+
@api(version="1.0")
|
|
505
|
+
def add_tags(self, item: Union[WorkbookItem, str], tags: Union[Iterable[str], str]) -> Set[str]:
|
|
506
|
+
return super().add_tags(item, tags)
|
|
507
|
+
|
|
508
|
+
@api(version="1.0")
|
|
509
|
+
def delete_tags(self, item: Union[WorkbookItem, str], tags: Union[Iterable[str], str]) -> None:
|
|
510
|
+
return super().delete_tags(item, tags)
|
|
511
|
+
|
|
512
|
+
@api(version="1.0")
|
|
513
|
+
def update_tags(self, item: WorkbookItem) -> None:
|
|
514
|
+
return super().update_tags(item)
|
|
515
|
+
|
|
516
|
+
def filter(self, *invalid, page_size: Optional[int] = None, **kwargs) -> QuerySet[WorkbookItem]:
|
|
517
|
+
"""
|
|
518
|
+
Queries the Tableau Server for items using the specified filters. Page
|
|
519
|
+
size can be specified to limit the number of items returned in a single
|
|
520
|
+
request. If not specified, the default page size is 100. Page size can
|
|
521
|
+
be an integer between 1 and 1000.
|
|
522
|
+
|
|
523
|
+
No positional arguments are allowed. All filters must be specified as
|
|
524
|
+
keyword arguments. If you use the equality operator, you can specify it
|
|
525
|
+
through <field_name>=<value>. If you want to use a different operator,
|
|
526
|
+
you can specify it through <field_name>__<operator>=<value>. Field
|
|
527
|
+
names can either be in snake_case or camelCase.
|
|
528
|
+
|
|
529
|
+
This endpoint supports the following fields and operators:
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
created_at=...
|
|
533
|
+
created_at__gt=...
|
|
534
|
+
created_at__gte=...
|
|
535
|
+
created_at__lt=...
|
|
536
|
+
created_at__lte=...
|
|
537
|
+
content_url=...
|
|
538
|
+
content_url__in=...
|
|
539
|
+
display_tabs=...
|
|
540
|
+
favorites_total=...
|
|
541
|
+
favorites_total__gt=...
|
|
542
|
+
favorites_total__gte=...
|
|
543
|
+
favorites_total__lt=...
|
|
544
|
+
favorites_total__lte=...
|
|
545
|
+
has_alerts=...
|
|
546
|
+
has_extracts=...
|
|
547
|
+
name=...
|
|
548
|
+
name__in=...
|
|
549
|
+
owner_domain=...
|
|
550
|
+
owner_domain__in=...
|
|
551
|
+
owner_email=...
|
|
552
|
+
owner_email__in=...
|
|
553
|
+
owner_name=...
|
|
554
|
+
owner_name__in=...
|
|
555
|
+
project_name=...
|
|
556
|
+
project_name__in=...
|
|
557
|
+
sheet_count=...
|
|
558
|
+
sheet_count__gt=...
|
|
559
|
+
sheet_count__gte=...
|
|
560
|
+
sheet_count__lt=...
|
|
561
|
+
sheet_count__lte=...
|
|
562
|
+
size=...
|
|
563
|
+
size__gt=...
|
|
564
|
+
size__gte=...
|
|
565
|
+
size__lt=...
|
|
566
|
+
size__lte=...
|
|
567
|
+
subscriptions_total=...
|
|
568
|
+
subscriptions_total__gt=...
|
|
569
|
+
subscriptions_total__gte=...
|
|
570
|
+
subscriptions_total__lt=...
|
|
571
|
+
subscriptions_total__lte=...
|
|
572
|
+
tags=...
|
|
573
|
+
tags__in=...
|
|
574
|
+
updated_at=...
|
|
575
|
+
updated_at__gt=...
|
|
576
|
+
updated_at__gte=...
|
|
577
|
+
updated_at__lt=...
|
|
578
|
+
updated_at__lte=...
|
|
579
|
+
"""
|
|
580
|
+
|
|
581
|
+
return super().filter(*invalid, page_size=page_size, **kwargs)
|
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
from functools import partial
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Iterable, Iterator, List, Optional, Protocol, Tuple, TypeVar, Union, runtime_checkable
|
|
4
4
|
|
|
5
5
|
from tableauserverclient.models.pagination_item import PaginationItem
|
|
6
6
|
from tableauserverclient.server.request_options import RequestOptions
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
T = TypeVar("T")
|
|
10
|
-
ReturnType = Tuple[List[T], PaginationItem]
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
@runtime_checkable
|
|
14
|
-
class Endpoint(Protocol):
|
|
15
|
-
def get(self, req_options: Optional[RequestOptions]
|
|
13
|
+
class Endpoint(Protocol[T]):
|
|
14
|
+
def get(self, req_options: Optional[RequestOptions]) -> Tuple[List[T], PaginationItem]:
|
|
16
15
|
...
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
@runtime_checkable
|
|
20
|
-
class CallableEndpoint(Protocol):
|
|
21
|
-
def __call__(self, __req_options: Optional[RequestOptions], **kwargs) ->
|
|
19
|
+
class CallableEndpoint(Protocol[T]):
|
|
20
|
+
def __call__(self, __req_options: Optional[RequestOptions], **kwargs) -> Tuple[List[T], PaginationItem]:
|
|
22
21
|
...
|
|
23
22
|
|
|
24
23
|
|
|
@@ -33,7 +32,7 @@ class Pager(Iterable[T]):
|
|
|
33
32
|
|
|
34
33
|
def __init__(
|
|
35
34
|
self,
|
|
36
|
-
endpoint: Union[CallableEndpoint, Endpoint],
|
|
35
|
+
endpoint: Union[CallableEndpoint[T], Endpoint[T]],
|
|
37
36
|
request_opts: Optional[RequestOptions] = None,
|
|
38
37
|
**kwargs,
|
|
39
38
|
) -> None:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from collections.abc import Sized
|
|
2
2
|
from itertools import count
|
|
3
3
|
from typing import Iterable, Iterator, List, Optional, Protocol, Tuple, TYPE_CHECKING, TypeVar, overload
|
|
4
|
+
from tableauserverclient.config import config
|
|
4
5
|
from tableauserverclient.models.pagination_item import PaginationItem
|
|
5
6
|
from tableauserverclient.server.filter import Filter
|
|
6
7
|
from tableauserverclient.server.request_options import RequestOptions
|
|
@@ -35,7 +36,7 @@ see pagination_sample
|
|
|
35
36
|
class QuerySet(Iterable[T], Sized):
|
|
36
37
|
def __init__(self, model: "QuerysetEndpoint[T]", page_size: Optional[int] = None) -> None:
|
|
37
38
|
self.model = model
|
|
38
|
-
self.request_options = RequestOptions(pagesize=page_size or
|
|
39
|
+
self.request_options = RequestOptions(pagesize=page_size or config.PAGE_SIZE)
|
|
39
40
|
self._result_cache: List[T] = []
|
|
40
41
|
self._pagination_item = PaginationItem()
|
|
41
42
|
|