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
@@ -9,11 +9,11 @@ from typing import Iterable, List, Optional, TYPE_CHECKING, Tuple, Union
9
9
 
10
10
  from tableauserverclient.helpers.headers import fix_filename
11
11
 
12
- from .dqw_endpoint import _DataQualityWarningEndpoint
13
- from .endpoint import QuerysetEndpoint, api
14
- from .exceptions import InternalServerError, MissingRequiredFieldError
15
- from .permissions_endpoint import _PermissionsEndpoint
16
- from .resource_tagger import _ResourceTagger
12
+ from tableauserverclient.server.endpoint.dqw_endpoint import _DataQualityWarningEndpoint
13
+ from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api
14
+ from tableauserverclient.server.endpoint.exceptions import InternalServerError, MissingRequiredFieldError
15
+ from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
16
+ from tableauserverclient.server.endpoint.resource_tagger import _ResourceTagger, TaggingMixin
17
17
  from tableauserverclient.models import FlowItem, PaginationItem, ConnectionItem, JobItem
18
18
  from tableauserverclient.server import RequestFactory
19
19
  from tableauserverclient.filesys_helpers import (
@@ -22,6 +22,7 @@ from tableauserverclient.filesys_helpers import (
22
22
  get_file_type,
23
23
  get_file_object_size,
24
24
  )
25
+ from tableauserverclient.server.query import QuerySet
25
26
 
26
27
  io_types_r = (io.BytesIO, io.BufferedReader)
27
28
  io_types_w = (io.BytesIO, io.BufferedWriter)
@@ -50,7 +51,7 @@ PathOrFileR = Union[FilePath, FileObjectR]
50
51
  PathOrFileW = Union[FilePath, FileObjectW]
51
52
 
52
53
 
53
- class Flows(QuerysetEndpoint):
54
+ class Flows(QuerysetEndpoint[FlowItem], TaggingMixin[FlowItem]):
54
55
  def __init__(self, parent_srv):
55
56
  super(Flows, self).__init__(parent_srv)
56
57
  self._resource_tagger = _ResourceTagger(parent_srv)
@@ -265,16 +266,6 @@ class Flows(QuerysetEndpoint):
265
266
  def populate_permissions(self, item: FlowItem) -> None:
266
267
  self._permissions.populate(item)
267
268
 
268
- @api(version="3.3")
269
- def update_permission(self, item, permission_item):
270
- import warnings
271
-
272
- warnings.warn(
273
- "Server.flows.update_permission is deprecated, " "please use Server.flows.update_permissions instead.",
274
- DeprecationWarning,
275
- )
276
- self._permissions.update(item, permission_item)
277
-
278
269
  @api(version="3.3")
279
270
  def update_permissions(self, item: FlowItem, permission_item: Iterable["PermissionsRule"]) -> None:
280
271
  self._permissions.update(item, permission_item)
@@ -305,3 +296,39 @@ class Flows(QuerysetEndpoint):
305
296
  self, schedule_id: str, item: FlowItem
306
297
  ) -> List["AddResponse"]: # actually should return a task
307
298
  return self.parent_srv.schedules.add_to_schedule(schedule_id, flow=item)
299
+
300
+ def filter(self, *invalid, page_size: Optional[int] = None, **kwargs) -> QuerySet[FlowItem]:
301
+ """
302
+ Queries the Tableau Server for items using the specified filters. Page
303
+ size can be specified to limit the number of items returned in a single
304
+ request. If not specified, the default page size is 100. Page size can
305
+ be an integer between 1 and 1000.
306
+
307
+ No positional arguments are allowed. All filters must be specified as
308
+ keyword arguments. If you use the equality operator, you can specify it
309
+ through <field_name>=<value>. If you want to use a different operator,
310
+ you can specify it through <field_name>__<operator>=<value>. Field
311
+ names can either be in snake_case or camelCase.
312
+
313
+ This endpoint supports the following fields and operators:
314
+
315
+
316
+ created_at=...
317
+ created_at__gt=...
318
+ created_at__gte=...
319
+ created_at__lt=...
320
+ created_at__lte=...
321
+ name=...
322
+ name__in=...
323
+ owner_name=...
324
+ project_id=...
325
+ project_name=...
326
+ project_name__in=...
327
+ updated=...
328
+ updated__gt=...
329
+ updated__gte=...
330
+ updated__lt=...
331
+ updated__lte=...
332
+ """
333
+
334
+ return super().filter(*invalid, page_size=page_size, **kwargs)
@@ -1,27 +1,29 @@
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 ..pager import Pager
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 ..request_options import RequestOptions
16
+ from tableauserverclient.server.request_options import RequestOptions
15
17
 
16
18
 
17
- class Groups(QuerysetEndpoint):
19
+ class Groups(QuerysetEndpoint[GroupItem]):
18
20
  @property
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):
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):
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):
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)
@@ -67,21 +69,7 @@ class Groups(QuerysetEndpoint):
67
69
  logger.info("Deleted single group (ID: {0})".format(group_id))
68
70
 
69
71
  @api(version="2.0")
70
- def update(
71
- self, group_item: GroupItem, default_site_role: Optional[str] = None, as_job: bool = False
72
- ) -> Union[GroupItem, JobItem]:
73
- # (1/8/2021): Deprecated starting v0.15
74
- if default_site_role is not None:
75
- import warnings
76
-
77
- warnings.simplefilter("always", DeprecationWarning)
78
- warnings.warn(
79
- 'Groups.update(...default_site_role=""...) is deprecated, '
80
- "please set the minimum_site_role field of GroupItem",
81
- DeprecationWarning,
82
- )
83
- group_item.minimum_site_role = default_site_role
84
-
72
+ def update(self, group_item: GroupItem, as_job: bool = False) -> Union[GroupItem, JobItem]:
85
73
  url = "{0}/{1}".format(self.baseurl, group_item.id)
86
74
 
87
75
  if not group_item.id:
@@ -93,7 +81,7 @@ class Groups(QuerysetEndpoint):
93
81
  elif as_job:
94
82
  url = "?".join([url, "asJob=True"])
95
83
 
96
- update_req = RequestFactory.Group.update_req(group_item, None)
84
+ update_req = RequestFactory.Group.update_req(group_item)
97
85
  server_response = self.put_request(url, update_req)
98
86
  logger.info("Updated group item (ID: {0})".format(group_item.id))
99
87
  if as_job:
@@ -101,17 +89,17 @@ class Groups(QuerysetEndpoint):
101
89
  else:
102
90
  return GroupItem.from_response(server_response.content, self.parent_srv.namespace)[0]
103
91
 
104
- # Create a 'local' Tableau group
105
92
  @api(version="2.0")
106
93
  def create(self, group_item: GroupItem) -> GroupItem:
94
+ """Create a 'local' Tableau group"""
107
95
  url = self.baseurl
108
96
  create_req = RequestFactory.Group.create_local_req(group_item)
109
97
  server_response = self.post_request(url, create_req)
110
98
  return GroupItem.from_response(server_response.content, self.parent_srv.namespace)[0]
111
99
 
112
- # Create a group based on Active Directory
113
100
  @api(version="2.0")
114
101
  def create_AD_group(self, group_item: GroupItem, asJob: bool = False) -> Union[GroupItem, JobItem]:
102
+ """Create a group based on Active Directory"""
115
103
  asJobparameter = "?asJob=true" if asJob else ""
116
104
  url = self.baseurl + asJobparameter
117
105
  create_req = RequestFactory.Group.create_ad_req(group_item)
@@ -121,9 +109,9 @@ class Groups(QuerysetEndpoint):
121
109
  else:
122
110
  return GroupItem.from_response(server_response.content, self.parent_srv.namespace)[0]
123
111
 
124
- # Removes 1 user from 1 group
125
112
  @api(version="2.0")
126
113
  def remove_user(self, group_item: GroupItem, user_id: str) -> None:
114
+ """Removes 1 user from 1 group"""
127
115
  if not group_item.id:
128
116
  error = "Group item missing ID."
129
117
  raise MissingRequiredFieldError(error)
@@ -134,9 +122,22 @@ class Groups(QuerysetEndpoint):
134
122
  self.delete_request(url)
135
123
  logger.info("Removed user (id: {0}) from group (ID: {1})".format(user_id, group_item.id))
136
124
 
137
- # Adds 1 user to 1 group
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
+
138
138
  @api(version="2.0")
139
139
  def add_user(self, group_item: GroupItem, user_id: str) -> UserItem:
140
+ """Adds 1 user to 1 group"""
140
141
  if not group_item.id:
141
142
  error = "Group item missing ID."
142
143
  raise MissingRequiredFieldError(error)
@@ -149,3 +150,56 @@ class Groups(QuerysetEndpoint):
149
150
  user = UserItem.from_response(server_response.content, self.parent_srv.namespace).pop()
150
151
  logger.info("Added user (id: {0}) to group (ID: {1})".format(user_id, group_item.id))
151
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 ..request_options import RequestOptionsBase
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):
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)
@@ -42,9 +42,9 @@ def extract_values(obj, key):
42
42
 
43
43
 
44
44
  def get_page_info(result):
45
- next_page = extract_values(result, "hasNextPage").pop()
46
- cursor = extract_values(result, "endCursor").pop()
47
- return next_page, cursor
45
+ next_page = extract_values(result, "hasNextPage")
46
+ cursor = extract_values(result, "endCursor")
47
+ return next_page.pop() if next_page else None, cursor.pop() if cursor else None
48
48
 
49
49
 
50
50
  class Metadata(Endpoint):
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
18
18
  from tableauserverclient.helpers.logging import logger
19
19
 
20
20
 
21
- class Metrics(QuerysetEndpoint):
21
+ class Metrics(QuerysetEndpoint[MetricItem]):
22
22
  def __init__(self, parent_srv: "Server") -> None:
23
23
  super(Metrics, self).__init__(parent_srv)
24
24
  self._resource_tagger = _ResourceTagger(parent_srv)