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
2
|
|
|
3
|
-
from .endpoint import QuerysetEndpoint, api
|
|
4
|
-
from .exceptions import MissingRequiredFieldError
|
|
3
|
+
from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api
|
|
4
|
+
from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError
|
|
5
5
|
from tableauserverclient.server import RequestFactory
|
|
6
6
|
from tableauserverclient.models import GroupItem, UserItem, PaginationItem, JobItem
|
|
7
|
-
from
|
|
7
|
+
from tableauserverclient.server.pager import Pager
|
|
8
8
|
|
|
9
9
|
from tableauserverclient.helpers.logging import logger
|
|
10
10
|
|
|
11
|
-
from typing import List, Optional, TYPE_CHECKING, Tuple, Union
|
|
11
|
+
from typing import Iterable, List, Optional, TYPE_CHECKING, Tuple, Union
|
|
12
|
+
|
|
13
|
+
from tableauserverclient.server.query import QuerySet
|
|
12
14
|
|
|
13
15
|
if TYPE_CHECKING:
|
|
14
|
-
from
|
|
16
|
+
from tableauserverclient.server.request_options import RequestOptions
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
class Groups(QuerysetEndpoint[GroupItem]):
|
|
@@ -19,9 +21,9 @@ class Groups(QuerysetEndpoint[GroupItem]):
|
|
|
19
21
|
def baseurl(self) -> str:
|
|
20
22
|
return "{0}/sites/{1}/groups".format(self.parent_srv.baseurl, self.parent_srv.site_id)
|
|
21
23
|
|
|
22
|
-
# Gets all groups
|
|
23
24
|
@api(version="2.0")
|
|
24
25
|
def get(self, req_options: Optional["RequestOptions"] = None) -> Tuple[List[GroupItem], PaginationItem]:
|
|
26
|
+
"""Gets all groups"""
|
|
25
27
|
logger.info("Querying all groups on site")
|
|
26
28
|
url = self.baseurl
|
|
27
29
|
server_response = self.get_request(url, req_options)
|
|
@@ -29,9 +31,9 @@ class Groups(QuerysetEndpoint[GroupItem]):
|
|
|
29
31
|
all_group_items = GroupItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
30
32
|
return all_group_items, pagination_item
|
|
31
33
|
|
|
32
|
-
# Gets all users in a given group
|
|
33
34
|
@api(version="2.0")
|
|
34
|
-
def populate_users(self, group_item, req_options: Optional["RequestOptions"] = None) -> None:
|
|
35
|
+
def populate_users(self, group_item: GroupItem, req_options: Optional["RequestOptions"] = None) -> None:
|
|
36
|
+
"""Gets all users in a given group"""
|
|
35
37
|
if not group_item.id:
|
|
36
38
|
error = "Group item missing ID. Group must be retrieved from server first."
|
|
37
39
|
raise MissingRequiredFieldError(error)
|
|
@@ -47,7 +49,7 @@ class Groups(QuerysetEndpoint[GroupItem]):
|
|
|
47
49
|
group_item._set_users(user_pager)
|
|
48
50
|
|
|
49
51
|
def _get_users_for_group(
|
|
50
|
-
self, group_item, req_options: Optional["RequestOptions"] = None
|
|
52
|
+
self, group_item: GroupItem, req_options: Optional["RequestOptions"] = None
|
|
51
53
|
) -> Tuple[List[UserItem], PaginationItem]:
|
|
52
54
|
url = "{0}/{1}/users".format(self.baseurl, group_item.id)
|
|
53
55
|
server_response = self.get_request(url, req_options)
|
|
@@ -56,9 +58,9 @@ class Groups(QuerysetEndpoint[GroupItem]):
|
|
|
56
58
|
logger.info("Populated users for group (ID: {0})".format(group_item.id))
|
|
57
59
|
return user_item, pagination_item
|
|
58
60
|
|
|
59
|
-
# Deletes 1 group by id
|
|
60
61
|
@api(version="2.0")
|
|
61
62
|
def delete(self, group_id: str) -> None:
|
|
63
|
+
"""Deletes 1 group by id"""
|
|
62
64
|
if not group_id:
|
|
63
65
|
error = "Group ID undefined."
|
|
64
66
|
raise ValueError(error)
|
|
@@ -87,17 +89,17 @@ class Groups(QuerysetEndpoint[GroupItem]):
|
|
|
87
89
|
else:
|
|
88
90
|
return GroupItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
89
91
|
|
|
90
|
-
# Create a 'local' Tableau group
|
|
91
92
|
@api(version="2.0")
|
|
92
93
|
def create(self, group_item: GroupItem) -> GroupItem:
|
|
94
|
+
"""Create a 'local' Tableau group"""
|
|
93
95
|
url = self.baseurl
|
|
94
96
|
create_req = RequestFactory.Group.create_local_req(group_item)
|
|
95
97
|
server_response = self.post_request(url, create_req)
|
|
96
98
|
return GroupItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
97
99
|
|
|
98
|
-
# Create a group based on Active Directory
|
|
99
100
|
@api(version="2.0")
|
|
100
101
|
def create_AD_group(self, group_item: GroupItem, asJob: bool = False) -> Union[GroupItem, JobItem]:
|
|
102
|
+
"""Create a group based on Active Directory"""
|
|
101
103
|
asJobparameter = "?asJob=true" if asJob else ""
|
|
102
104
|
url = self.baseurl + asJobparameter
|
|
103
105
|
create_req = RequestFactory.Group.create_ad_req(group_item)
|
|
@@ -107,9 +109,9 @@ class Groups(QuerysetEndpoint[GroupItem]):
|
|
|
107
109
|
else:
|
|
108
110
|
return GroupItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
109
111
|
|
|
110
|
-
# Removes 1 user from 1 group
|
|
111
112
|
@api(version="2.0")
|
|
112
113
|
def remove_user(self, group_item: GroupItem, user_id: str) -> None:
|
|
114
|
+
"""Removes 1 user from 1 group"""
|
|
113
115
|
if not group_item.id:
|
|
114
116
|
error = "Group item missing ID."
|
|
115
117
|
raise MissingRequiredFieldError(error)
|
|
@@ -120,9 +122,22 @@ class Groups(QuerysetEndpoint[GroupItem]):
|
|
|
120
122
|
self.delete_request(url)
|
|
121
123
|
logger.info("Removed user (id: {0}) from group (ID: {1})".format(user_id, group_item.id))
|
|
122
124
|
|
|
123
|
-
|
|
125
|
+
@api(version="3.21")
|
|
126
|
+
def remove_users(self, group_item: GroupItem, users: Iterable[Union[str, UserItem]]) -> None:
|
|
127
|
+
"""Removes multiple users from 1 group"""
|
|
128
|
+
group_id = group_item.id if hasattr(group_item, "id") else group_item
|
|
129
|
+
if not isinstance(group_id, str):
|
|
130
|
+
raise ValueError(f"Invalid group provided: {group_item}")
|
|
131
|
+
|
|
132
|
+
url = f"{self.baseurl}/{group_id}/users/remove"
|
|
133
|
+
add_req = RequestFactory.Group.remove_users_req(users)
|
|
134
|
+
_ = self.put_request(url, add_req)
|
|
135
|
+
logger.info("Removed users to group (ID: {0})".format(group_item.id))
|
|
136
|
+
return None
|
|
137
|
+
|
|
124
138
|
@api(version="2.0")
|
|
125
139
|
def add_user(self, group_item: GroupItem, user_id: str) -> UserItem:
|
|
140
|
+
"""Adds 1 user to 1 group"""
|
|
126
141
|
if not group_item.id:
|
|
127
142
|
error = "Group item missing ID."
|
|
128
143
|
raise MissingRequiredFieldError(error)
|
|
@@ -135,3 +150,56 @@ class Groups(QuerysetEndpoint[GroupItem]):
|
|
|
135
150
|
user = UserItem.from_response(server_response.content, self.parent_srv.namespace).pop()
|
|
136
151
|
logger.info("Added user (id: {0}) to group (ID: {1})".format(user_id, group_item.id))
|
|
137
152
|
return user
|
|
153
|
+
|
|
154
|
+
@api(version="3.21")
|
|
155
|
+
def add_users(self, group_item: GroupItem, users: Iterable[Union[str, UserItem]]) -> List[UserItem]:
|
|
156
|
+
"""Adds multiple users to 1 group"""
|
|
157
|
+
group_id = group_item.id if hasattr(group_item, "id") else group_item
|
|
158
|
+
if not isinstance(group_id, str):
|
|
159
|
+
raise ValueError(f"Invalid group provided: {group_item}")
|
|
160
|
+
|
|
161
|
+
url = f"{self.baseurl}/{group_id}/users"
|
|
162
|
+
add_req = RequestFactory.Group.add_users_req(users)
|
|
163
|
+
server_response = self.post_request(url, add_req)
|
|
164
|
+
users = UserItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
165
|
+
logger.info("Added users to group (ID: {0})".format(group_item.id))
|
|
166
|
+
return users
|
|
167
|
+
|
|
168
|
+
def filter(self, *invalid, page_size: Optional[int] = None, **kwargs) -> QuerySet[GroupItem]:
|
|
169
|
+
"""
|
|
170
|
+
Queries the Tableau Server for items using the specified filters. Page
|
|
171
|
+
size can be specified to limit the number of items returned in a single
|
|
172
|
+
request. If not specified, the default page size is 100. Page size can
|
|
173
|
+
be an integer between 1 and 1000.
|
|
174
|
+
|
|
175
|
+
No positional arguments are allowed. All filters must be specified as
|
|
176
|
+
keyword arguments. If you use the equality operator, you can specify it
|
|
177
|
+
through <field_name>=<value>. If you want to use a different operator,
|
|
178
|
+
you can specify it through <field_name>__<operator>=<value>. Field
|
|
179
|
+
names can either be in snake_case or camelCase.
|
|
180
|
+
|
|
181
|
+
This endpoint supports the following fields and operators:
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
domain_name=...
|
|
185
|
+
domain_name__in=...
|
|
186
|
+
domain_nickname=...
|
|
187
|
+
domain_nickname__in=...
|
|
188
|
+
is_external_user_enabled=...
|
|
189
|
+
is_local=...
|
|
190
|
+
luid=...
|
|
191
|
+
luid__in=...
|
|
192
|
+
minimum_site_role=...
|
|
193
|
+
minimum_site_role__in=...
|
|
194
|
+
name__cieq=...
|
|
195
|
+
name=...
|
|
196
|
+
name__in=...
|
|
197
|
+
name__like=...
|
|
198
|
+
user_count=...
|
|
199
|
+
user_count__gt=...
|
|
200
|
+
user_count__gte=...
|
|
201
|
+
user_count__lt=...
|
|
202
|
+
user_count__lte=...
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
return super().filter(*invalid, page_size=page_size, **kwargs)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from typing import List, Literal, Optional, Tuple, TYPE_CHECKING, Union
|
|
2
|
+
|
|
3
|
+
from tableauserverclient.helpers.logging import logger
|
|
4
|
+
from tableauserverclient.models.group_item import GroupItem
|
|
5
|
+
from tableauserverclient.models.groupset_item import GroupSetItem
|
|
6
|
+
from tableauserverclient.models.pagination_item import PaginationItem
|
|
7
|
+
from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint
|
|
8
|
+
from tableauserverclient.server.query import QuerySet
|
|
9
|
+
from tableauserverclient.server.request_options import RequestOptions
|
|
10
|
+
from tableauserverclient.server.request_factory import RequestFactory
|
|
11
|
+
from tableauserverclient.server.endpoint.endpoint import api
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from tableauserverclient.server import Server
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GroupSets(QuerysetEndpoint[GroupSetItem]):
|
|
18
|
+
def __init__(self, parent_srv: "Server") -> None:
|
|
19
|
+
super().__init__(parent_srv)
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def baseurl(self) -> str:
|
|
23
|
+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/groupsets"
|
|
24
|
+
|
|
25
|
+
@api(version="3.22")
|
|
26
|
+
def get(
|
|
27
|
+
self,
|
|
28
|
+
request_options: Optional[RequestOptions] = None,
|
|
29
|
+
result_level: Optional[Literal["members", "local"]] = None,
|
|
30
|
+
) -> Tuple[List[GroupSetItem], PaginationItem]:
|
|
31
|
+
logger.info("Querying all group sets on site")
|
|
32
|
+
url = self.baseurl
|
|
33
|
+
if result_level:
|
|
34
|
+
url += f"?resultlevel={result_level}"
|
|
35
|
+
server_response = self.get_request(url, request_options)
|
|
36
|
+
pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
37
|
+
all_group_set_items = GroupSetItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
38
|
+
return all_group_set_items, pagination_item
|
|
39
|
+
|
|
40
|
+
@api(version="3.22")
|
|
41
|
+
def get_by_id(self, groupset_id: str) -> GroupSetItem:
|
|
42
|
+
logger.info(f"Querying group set (ID: {groupset_id})")
|
|
43
|
+
url = f"{self.baseurl}/{groupset_id}"
|
|
44
|
+
server_response = self.get_request(url)
|
|
45
|
+
all_group_set_items = GroupSetItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
46
|
+
return all_group_set_items[0]
|
|
47
|
+
|
|
48
|
+
@api(version="3.22")
|
|
49
|
+
def create(self, groupset_item: GroupSetItem) -> GroupSetItem:
|
|
50
|
+
logger.info(f"Creating group set (name: {groupset_item.name})")
|
|
51
|
+
url = self.baseurl
|
|
52
|
+
request = RequestFactory.GroupSet.create_request(groupset_item)
|
|
53
|
+
server_response = self.post_request(url, request)
|
|
54
|
+
created_groupset = GroupSetItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
55
|
+
return created_groupset[0]
|
|
56
|
+
|
|
57
|
+
@api(version="3.22")
|
|
58
|
+
def add_group(self, groupset_item: GroupSetItem, group: Union[GroupItem, str]) -> None:
|
|
59
|
+
group_id = group.id if isinstance(group, GroupItem) else group
|
|
60
|
+
logger.info(f"Adding group (ID: {group_id}) to group set (ID: {groupset_item.id})")
|
|
61
|
+
url = f"{self.baseurl}/{groupset_item.id}/groups/{group_id}"
|
|
62
|
+
_ = self.put_request(url)
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
@api(version="3.22")
|
|
66
|
+
def remove_group(self, groupset_item: GroupSetItem, group: Union[GroupItem, str]) -> None:
|
|
67
|
+
group_id = group.id if isinstance(group, GroupItem) else group
|
|
68
|
+
logger.info(f"Removing group (ID: {group_id}) from group set (ID: {groupset_item.id})")
|
|
69
|
+
url = f"{self.baseurl}/{groupset_item.id}/groups/{group_id}"
|
|
70
|
+
_ = self.delete_request(url)
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
@api(version="3.22")
|
|
74
|
+
def delete(self, groupset: Union[GroupSetItem, str]) -> None:
|
|
75
|
+
groupset_id = groupset.id if isinstance(groupset, GroupSetItem) else groupset
|
|
76
|
+
logger.info(f"Deleting group set (ID: {groupset_id})")
|
|
77
|
+
url = f"{self.baseurl}/{groupset_id}"
|
|
78
|
+
_ = self.delete_request(url)
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
@api(version="3.22")
|
|
82
|
+
def update(self, groupset: GroupSetItem) -> GroupSetItem:
|
|
83
|
+
logger.info(f"Updating group set (ID: {groupset.id})")
|
|
84
|
+
url = f"{self.baseurl}/{groupset.id}"
|
|
85
|
+
request = RequestFactory.GroupSet.update_request(groupset)
|
|
86
|
+
server_response = self.put_request(url, request)
|
|
87
|
+
updated_groupset = GroupSetItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
88
|
+
return updated_groupset[0]
|
|
89
|
+
|
|
90
|
+
def filter(self, *invalid, page_size: Optional[int] = None, **kwargs) -> QuerySet[GroupSetItem]:
|
|
91
|
+
"""
|
|
92
|
+
Queries the Tableau Server for items using the specified filters. Page
|
|
93
|
+
size can be specified to limit the number of items returned in a single
|
|
94
|
+
request. If not specified, the default page size is 100. Page size can
|
|
95
|
+
be an integer between 1 and 1000.
|
|
96
|
+
|
|
97
|
+
No positional arguments are allowed. All filters must be specified as
|
|
98
|
+
keyword arguments. If you use the equality operator, you can specify it
|
|
99
|
+
through <field_name>=<value>. If you want to use a different operator,
|
|
100
|
+
you can specify it through <field_name>__<operator>=<value>. Field
|
|
101
|
+
names can either be in snake_case or camelCase.
|
|
102
|
+
|
|
103
|
+
This endpoint supports the following fields and operators:
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
domain_name=...
|
|
107
|
+
domain_name__in=...
|
|
108
|
+
domain_nickname=...
|
|
109
|
+
domain_nickname__in=...
|
|
110
|
+
is_external_user_enabled=...
|
|
111
|
+
is_local=...
|
|
112
|
+
luid=...
|
|
113
|
+
luid__in=...
|
|
114
|
+
minimum_site_role=...
|
|
115
|
+
minimum_site_role__in=...
|
|
116
|
+
name__cieq=...
|
|
117
|
+
name=...
|
|
118
|
+
name__in=...
|
|
119
|
+
name__like=...
|
|
120
|
+
user_count=...
|
|
121
|
+
user_count__gt=...
|
|
122
|
+
user_count__gte=...
|
|
123
|
+
user_count__lt=...
|
|
124
|
+
user_count__lte=...
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
return super().filter(*invalid, page_size=page_size, **kwargs)
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from typing_extensions import Self, overload
|
|
3
|
+
|
|
2
4
|
|
|
3
|
-
from .endpoint import QuerysetEndpoint, api
|
|
4
|
-
from .exceptions import JobCancelledException, JobFailedException
|
|
5
5
|
from tableauserverclient.models import JobItem, BackgroundJobItem, PaginationItem
|
|
6
|
-
from
|
|
6
|
+
from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api
|
|
7
|
+
from tableauserverclient.server.endpoint.exceptions import JobCancelledException, JobFailedException
|
|
8
|
+
from tableauserverclient.server.query import QuerySet
|
|
9
|
+
from tableauserverclient.server.request_options import RequestOptionsBase
|
|
7
10
|
from tableauserverclient.exponential_backoff import ExponentialBackoffTimer
|
|
8
11
|
|
|
9
12
|
from tableauserverclient.helpers.logging import logger
|
|
@@ -11,15 +14,25 @@ from tableauserverclient.helpers.logging import logger
|
|
|
11
14
|
from typing import List, Optional, Tuple, Union
|
|
12
15
|
|
|
13
16
|
|
|
14
|
-
class Jobs(QuerysetEndpoint[
|
|
17
|
+
class Jobs(QuerysetEndpoint[BackgroundJobItem]):
|
|
15
18
|
@property
|
|
16
19
|
def baseurl(self):
|
|
17
20
|
return "{0}/sites/{1}/jobs".format(self.parent_srv.baseurl, self.parent_srv.site_id)
|
|
18
21
|
|
|
22
|
+
@overload # type: ignore[override]
|
|
23
|
+
def get(self: Self, job_id: str, req_options: Optional[RequestOptionsBase] = None) -> JobItem: # type: ignore[override]
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
@overload # type: ignore[override]
|
|
27
|
+
def get(self: Self, job_id: RequestOptionsBase, req_options: None) -> Tuple[List[BackgroundJobItem], PaginationItem]: # type: ignore[override]
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
@overload # type: ignore[override]
|
|
31
|
+
def get(self: Self, job_id: None, req_options: Optional[RequestOptionsBase]) -> Tuple[List[BackgroundJobItem], PaginationItem]: # type: ignore[override]
|
|
32
|
+
...
|
|
33
|
+
|
|
19
34
|
@api(version="2.6")
|
|
20
|
-
def get(
|
|
21
|
-
self, job_id: Optional[str] = None, req_options: Optional[RequestOptionsBase] = None
|
|
22
|
-
) -> Tuple[List[BackgroundJobItem], PaginationItem]:
|
|
35
|
+
def get(self, job_id=None, req_options=None):
|
|
23
36
|
# Backwards Compatibility fix until we rev the major version
|
|
24
37
|
if job_id is not None and isinstance(job_id, str):
|
|
25
38
|
import warnings
|
|
@@ -74,3 +87,57 @@ class Jobs(QuerysetEndpoint[JobItem]):
|
|
|
74
87
|
raise JobCancelledException(job)
|
|
75
88
|
else:
|
|
76
89
|
raise AssertionError("Unexpected finish_code in job", job)
|
|
90
|
+
|
|
91
|
+
def filter(self, *invalid, page_size: Optional[int] = None, **kwargs) -> QuerySet[BackgroundJobItem]:
|
|
92
|
+
"""
|
|
93
|
+
Queries the Tableau Server for items using the specified filters. Page
|
|
94
|
+
size can be specified to limit the number of items returned in a single
|
|
95
|
+
request. If not specified, the default page size is 100. Page size can
|
|
96
|
+
be an integer between 1 and 1000.
|
|
97
|
+
|
|
98
|
+
No positional arguments are allowed. All filters must be specified as
|
|
99
|
+
keyword arguments. If you use the equality operator, you can specify it
|
|
100
|
+
through <field_name>=<value>. If you want to use a different operator,
|
|
101
|
+
you can specify it through <field_name>__<operator>=<value>. Field
|
|
102
|
+
names can either be in snake_case or camelCase.
|
|
103
|
+
|
|
104
|
+
This endpoint supports the following fields and operators:
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
args__has=...
|
|
108
|
+
completed_at=...
|
|
109
|
+
completed_at__gt=...
|
|
110
|
+
completed_at__gte=...
|
|
111
|
+
completed_at__lt=...
|
|
112
|
+
completed_at__lte=...
|
|
113
|
+
created_at=...
|
|
114
|
+
created_at__gt=...
|
|
115
|
+
created_at__gte=...
|
|
116
|
+
created_at__lt=...
|
|
117
|
+
created_at__lte=...
|
|
118
|
+
job_type=...
|
|
119
|
+
job_type__in=...
|
|
120
|
+
notes__has=...
|
|
121
|
+
priority=...
|
|
122
|
+
priority__gt=...
|
|
123
|
+
priority__gte=...
|
|
124
|
+
priority__lt=...
|
|
125
|
+
priority__lte=...
|
|
126
|
+
progress=...
|
|
127
|
+
progress__gt=...
|
|
128
|
+
progress__gte=...
|
|
129
|
+
progress__lt=...
|
|
130
|
+
progress__lte=...
|
|
131
|
+
started_at=...
|
|
132
|
+
started_at__gt=...
|
|
133
|
+
started_at__gte=...
|
|
134
|
+
started_at__lt=...
|
|
135
|
+
started_at__lte=...
|
|
136
|
+
status=...
|
|
137
|
+
subtitle=...
|
|
138
|
+
subtitle__has=...
|
|
139
|
+
title=...
|
|
140
|
+
title__has=...
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
return super().filter(*invalid, page_size=page_size, **kwargs)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from typing import List, Optional, Tuple, Union
|
|
2
|
+
|
|
3
|
+
from tableauserverclient.helpers.logging import logger
|
|
4
|
+
from tableauserverclient.models.linked_tasks_item import LinkedTaskItem, LinkedTaskJobItem
|
|
5
|
+
from tableauserverclient.models.pagination_item import PaginationItem
|
|
6
|
+
from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api
|
|
7
|
+
from tableauserverclient.server.request_factory import RequestFactory
|
|
8
|
+
from tableauserverclient.server.request_options import RequestOptions
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LinkedTasks(QuerysetEndpoint[LinkedTaskItem]):
|
|
12
|
+
def __init__(self, parent_srv):
|
|
13
|
+
super().__init__(parent_srv)
|
|
14
|
+
self._parent_srv = parent_srv
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def baseurl(self) -> str:
|
|
18
|
+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/tasks/linked"
|
|
19
|
+
|
|
20
|
+
@api(version="3.15")
|
|
21
|
+
def get(self, req_options: Optional["RequestOptions"] = None) -> Tuple[List[LinkedTaskItem], PaginationItem]:
|
|
22
|
+
logger.info("Querying all linked tasks on site")
|
|
23
|
+
url = self.baseurl
|
|
24
|
+
server_response = self.get_request(url, req_options)
|
|
25
|
+
pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
26
|
+
all_group_items = LinkedTaskItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
27
|
+
return all_group_items, pagination_item
|
|
28
|
+
|
|
29
|
+
@api(version="3.15")
|
|
30
|
+
def get_by_id(self, linked_task: Union[LinkedTaskItem, str]) -> LinkedTaskItem:
|
|
31
|
+
task_id = getattr(linked_task, "id", linked_task)
|
|
32
|
+
logger.info("Querying all linked tasks on site")
|
|
33
|
+
url = f"{self.baseurl}/{task_id}"
|
|
34
|
+
server_response = self.get_request(url)
|
|
35
|
+
all_group_items = LinkedTaskItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
36
|
+
return all_group_items[0]
|
|
37
|
+
|
|
38
|
+
@api(version="3.15")
|
|
39
|
+
def run_now(self, linked_task: Union[LinkedTaskItem, str]) -> LinkedTaskJobItem:
|
|
40
|
+
task_id = getattr(linked_task, "id", linked_task)
|
|
41
|
+
logger.info(f"Running linked task {task_id} now")
|
|
42
|
+
url = f"{self.baseurl}/{task_id}/runNow"
|
|
43
|
+
empty_req = RequestFactory.Empty.empty_req()
|
|
44
|
+
server_response = self.post_request(url, empty_req)
|
|
45
|
+
return LinkedTaskJobItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
@@ -9,6 +9,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
15
|
from tableauserverclient.server.server import Server
|
|
14
16
|
from tableauserverclient.server.request_options import RequestOptions
|
|
@@ -154,3 +156,44 @@ class Projects(QuerysetEndpoint[ProjectItem]):
|
|
|
154
156
|
@api(version="3.4")
|
|
155
157
|
def delete_lens_default_permissions(self, item, rule):
|
|
156
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
|
|
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)
|