tableauserverclient 0.33__py3-none-any.whl → 0.34__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tableauserverclient/__init__.py +28 -22
- tableauserverclient/_version.py +3 -3
- tableauserverclient/config.py +5 -3
- tableauserverclient/models/column_item.py +1 -1
- tableauserverclient/models/connection_credentials.py +1 -1
- tableauserverclient/models/connection_item.py +6 -6
- tableauserverclient/models/custom_view_item.py +29 -6
- tableauserverclient/models/data_acceleration_report_item.py +2 -2
- tableauserverclient/models/data_alert_item.py +5 -5
- tableauserverclient/models/data_freshness_policy_item.py +6 -6
- tableauserverclient/models/database_item.py +3 -3
- tableauserverclient/models/datasource_item.py +10 -10
- tableauserverclient/models/dqw_item.py +1 -1
- tableauserverclient/models/favorites_item.py +5 -6
- tableauserverclient/models/fileupload_item.py +1 -1
- tableauserverclient/models/flow_item.py +6 -6
- tableauserverclient/models/flow_run_item.py +3 -3
- tableauserverclient/models/group_item.py +4 -4
- tableauserverclient/models/groupset_item.py +4 -4
- tableauserverclient/models/interval_item.py +9 -9
- tableauserverclient/models/job_item.py +8 -8
- tableauserverclient/models/linked_tasks_item.py +5 -5
- tableauserverclient/models/metric_item.py +5 -5
- tableauserverclient/models/pagination_item.py +1 -1
- tableauserverclient/models/permissions_item.py +12 -10
- tableauserverclient/models/project_item.py +35 -19
- tableauserverclient/models/property_decorators.py +12 -11
- tableauserverclient/models/reference_item.py +2 -2
- tableauserverclient/models/revision_item.py +3 -3
- tableauserverclient/models/schedule_item.py +2 -2
- tableauserverclient/models/server_info_item.py +26 -6
- tableauserverclient/models/site_item.py +69 -3
- tableauserverclient/models/subscription_item.py +3 -3
- tableauserverclient/models/table_item.py +1 -1
- tableauserverclient/models/tableau_auth.py +115 -5
- tableauserverclient/models/tableau_types.py +2 -2
- tableauserverclient/models/tag_item.py +3 -4
- tableauserverclient/models/task_item.py +4 -4
- tableauserverclient/models/user_item.py +47 -17
- tableauserverclient/models/view_item.py +11 -10
- tableauserverclient/models/virtual_connection_item.py +6 -5
- tableauserverclient/models/webhook_item.py +6 -6
- tableauserverclient/models/workbook_item.py +90 -12
- tableauserverclient/namespace.py +1 -1
- tableauserverclient/server/__init__.py +2 -1
- tableauserverclient/server/endpoint/auth_endpoint.py +65 -8
- tableauserverclient/server/endpoint/custom_views_endpoint.py +62 -18
- tableauserverclient/server/endpoint/data_acceleration_report_endpoint.py +2 -2
- tableauserverclient/server/endpoint/data_alert_endpoint.py +14 -14
- tableauserverclient/server/endpoint/databases_endpoint.py +13 -12
- tableauserverclient/server/endpoint/datasources_endpoint.py +49 -54
- tableauserverclient/server/endpoint/default_permissions_endpoint.py +19 -18
- tableauserverclient/server/endpoint/dqw_endpoint.py +9 -9
- tableauserverclient/server/endpoint/endpoint.py +19 -21
- tableauserverclient/server/endpoint/exceptions.py +23 -7
- tableauserverclient/server/endpoint/favorites_endpoint.py +31 -31
- tableauserverclient/server/endpoint/fileuploads_endpoint.py +9 -11
- tableauserverclient/server/endpoint/flow_runs_endpoint.py +15 -13
- tableauserverclient/server/endpoint/flow_task_endpoint.py +2 -2
- tableauserverclient/server/endpoint/flows_endpoint.py +30 -29
- tableauserverclient/server/endpoint/groups_endpoint.py +18 -17
- tableauserverclient/server/endpoint/groupsets_endpoint.py +2 -2
- tableauserverclient/server/endpoint/jobs_endpoint.py +7 -7
- tableauserverclient/server/endpoint/linked_tasks_endpoint.py +2 -2
- tableauserverclient/server/endpoint/metadata_endpoint.py +2 -2
- tableauserverclient/server/endpoint/metrics_endpoint.py +10 -10
- tableauserverclient/server/endpoint/permissions_endpoint.py +13 -15
- tableauserverclient/server/endpoint/projects_endpoint.py +81 -30
- tableauserverclient/server/endpoint/resource_tagger.py +14 -13
- tableauserverclient/server/endpoint/schedules_endpoint.py +17 -18
- tableauserverclient/server/endpoint/server_info_endpoint.py +40 -5
- tableauserverclient/server/endpoint/sites_endpoint.py +282 -17
- tableauserverclient/server/endpoint/subscriptions_endpoint.py +10 -10
- tableauserverclient/server/endpoint/tables_endpoint.py +15 -14
- tableauserverclient/server/endpoint/tasks_endpoint.py +8 -8
- tableauserverclient/server/endpoint/users_endpoint.py +366 -19
- tableauserverclient/server/endpoint/views_endpoint.py +19 -18
- tableauserverclient/server/endpoint/virtual_connections_endpoint.py +6 -5
- tableauserverclient/server/endpoint/webhooks_endpoint.py +11 -11
- tableauserverclient/server/endpoint/workbooks_endpoint.py +647 -61
- tableauserverclient/server/filter.py +2 -2
- tableauserverclient/server/pager.py +5 -6
- tableauserverclient/server/query.py +68 -19
- tableauserverclient/server/request_factory.py +37 -36
- tableauserverclient/server/request_options.py +123 -145
- tableauserverclient/server/server.py +65 -9
- tableauserverclient/server/sort.py +2 -2
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/METADATA +6 -6
- tableauserverclient-0.34.dist-info/RECORD +106 -0
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/WHEEL +1 -1
- tableauserverclient-0.33.dist-info/RECORD +0 -106
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/LICENSE +0 -0
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/LICENSE.versioneer +0 -0
- {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/top_level.txt +0 -0
|
@@ -7,7 +7,7 @@ from tableauserverclient.models import DataAlertItem, PaginationItem, UserItem
|
|
|
7
7
|
|
|
8
8
|
from tableauserverclient.helpers.logging import logger
|
|
9
9
|
|
|
10
|
-
from typing import
|
|
10
|
+
from typing import Optional, TYPE_CHECKING, Union
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
@@ -17,14 +17,14 @@ if TYPE_CHECKING:
|
|
|
17
17
|
|
|
18
18
|
class DataAlerts(Endpoint):
|
|
19
19
|
def __init__(self, parent_srv: "Server") -> None:
|
|
20
|
-
super(
|
|
20
|
+
super().__init__(parent_srv)
|
|
21
21
|
|
|
22
22
|
@property
|
|
23
23
|
def baseurl(self) -> str:
|
|
24
|
-
return "{
|
|
24
|
+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/dataAlerts"
|
|
25
25
|
|
|
26
26
|
@api(version="3.2")
|
|
27
|
-
def get(self, req_options: Optional["RequestOptions"] = None) ->
|
|
27
|
+
def get(self, req_options: Optional["RequestOptions"] = None) -> tuple[list[DataAlertItem], PaginationItem]:
|
|
28
28
|
logger.info("Querying all dataAlerts on site")
|
|
29
29
|
url = self.baseurl
|
|
30
30
|
server_response = self.get_request(url, req_options)
|
|
@@ -38,8 +38,8 @@ class DataAlerts(Endpoint):
|
|
|
38
38
|
if not dataAlert_id:
|
|
39
39
|
error = "dataAlert ID undefined."
|
|
40
40
|
raise ValueError(error)
|
|
41
|
-
logger.info("Querying single dataAlert (ID: {
|
|
42
|
-
url = "{
|
|
41
|
+
logger.info(f"Querying single dataAlert (ID: {dataAlert_id})")
|
|
42
|
+
url = f"{self.baseurl}/{dataAlert_id}"
|
|
43
43
|
server_response = self.get_request(url)
|
|
44
44
|
return DataAlertItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
45
45
|
|
|
@@ -55,9 +55,9 @@ class DataAlerts(Endpoint):
|
|
|
55
55
|
error = "Dataalert ID undefined."
|
|
56
56
|
raise ValueError(error)
|
|
57
57
|
# DELETE /api/api-version/sites/site-id/dataAlerts/data-alert-id/users/user-id
|
|
58
|
-
url = "{
|
|
58
|
+
url = f"{self.baseurl}/{dataAlert_id}"
|
|
59
59
|
self.delete_request(url)
|
|
60
|
-
logger.info("Deleted single dataAlert (ID: {
|
|
60
|
+
logger.info(f"Deleted single dataAlert (ID: {dataAlert_id})")
|
|
61
61
|
|
|
62
62
|
@api(version="3.2")
|
|
63
63
|
def delete_user_from_alert(self, dataAlert: Union[DataAlertItem, str], user: Union[UserItem, str]) -> None:
|
|
@@ -80,9 +80,9 @@ class DataAlerts(Endpoint):
|
|
|
80
80
|
error = "User ID undefined."
|
|
81
81
|
raise ValueError(error)
|
|
82
82
|
# DELETE /api/api-version/sites/site-id/dataAlerts/data-alert-id/users/user-id
|
|
83
|
-
url = "{
|
|
83
|
+
url = f"{self.baseurl}/{dataAlert_id}/users/{user_id}"
|
|
84
84
|
self.delete_request(url)
|
|
85
|
-
logger.info("Deleted User (ID {
|
|
85
|
+
logger.info(f"Deleted User (ID {user_id}) from dataAlert (ID: {dataAlert_id})")
|
|
86
86
|
|
|
87
87
|
@api(version="3.2")
|
|
88
88
|
def add_user_to_alert(self, dataAlert_item: DataAlertItem, user: Union[UserItem, str]) -> UserItem:
|
|
@@ -98,10 +98,10 @@ class DataAlerts(Endpoint):
|
|
|
98
98
|
if not user_id:
|
|
99
99
|
error = "User ID undefined."
|
|
100
100
|
raise ValueError(error)
|
|
101
|
-
url = "{
|
|
101
|
+
url = f"{self.baseurl}/{dataAlert_item.id}/users"
|
|
102
102
|
update_req = RequestFactory.DataAlert.add_user_to_alert(dataAlert_item, user_id)
|
|
103
103
|
server_response = self.post_request(url, update_req)
|
|
104
|
-
logger.info("Added user (ID {
|
|
104
|
+
logger.info(f"Added user (ID {user_id}) to dataAlert item (ID: {dataAlert_item.id})")
|
|
105
105
|
added_user = UserItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
106
106
|
return added_user
|
|
107
107
|
|
|
@@ -111,9 +111,9 @@ class DataAlerts(Endpoint):
|
|
|
111
111
|
error = "Dataalert item missing ID."
|
|
112
112
|
raise MissingRequiredFieldError(error)
|
|
113
113
|
|
|
114
|
-
url = "{
|
|
114
|
+
url = f"{self.baseurl}/{dataAlert_item.id}"
|
|
115
115
|
update_req = RequestFactory.DataAlert.update_req(dataAlert_item)
|
|
116
116
|
server_response = self.put_request(url, update_req)
|
|
117
|
-
logger.info("Updated dataAlert item (ID: {
|
|
117
|
+
logger.info(f"Updated dataAlert item (ID: {dataAlert_item.id})")
|
|
118
118
|
updated_dataAlert = DataAlertItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
119
119
|
return updated_dataAlert
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import Union
|
|
2
|
+
from typing import Union
|
|
3
|
+
from collections.abc import Iterable
|
|
3
4
|
|
|
4
5
|
from tableauserverclient.server.endpoint.default_permissions_endpoint import _DefaultPermissionsEndpoint
|
|
5
6
|
from tableauserverclient.server.endpoint.dqw_endpoint import _DataQualityWarningEndpoint
|
|
@@ -15,7 +16,7 @@ from tableauserverclient.helpers.logging import logger
|
|
|
15
16
|
|
|
16
17
|
class Databases(Endpoint, TaggingMixin):
|
|
17
18
|
def __init__(self, parent_srv):
|
|
18
|
-
super(
|
|
19
|
+
super().__init__(parent_srv)
|
|
19
20
|
|
|
20
21
|
self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
|
|
21
22
|
self._default_permissions = _DefaultPermissionsEndpoint(parent_srv, lambda: self.baseurl)
|
|
@@ -23,7 +24,7 @@ class Databases(Endpoint, TaggingMixin):
|
|
|
23
24
|
|
|
24
25
|
@property
|
|
25
26
|
def baseurl(self):
|
|
26
|
-
return "{
|
|
27
|
+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/databases"
|
|
27
28
|
|
|
28
29
|
@api(version="3.5")
|
|
29
30
|
def get(self, req_options=None):
|
|
@@ -40,8 +41,8 @@ class Databases(Endpoint, TaggingMixin):
|
|
|
40
41
|
if not database_id:
|
|
41
42
|
error = "database ID undefined."
|
|
42
43
|
raise ValueError(error)
|
|
43
|
-
logger.info("Querying single database (ID: {
|
|
44
|
-
url = "{
|
|
44
|
+
logger.info(f"Querying single database (ID: {database_id})")
|
|
45
|
+
url = f"{self.baseurl}/{database_id}"
|
|
45
46
|
server_response = self.get_request(url)
|
|
46
47
|
return DatabaseItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
47
48
|
|
|
@@ -50,9 +51,9 @@ class Databases(Endpoint, TaggingMixin):
|
|
|
50
51
|
if not database_id:
|
|
51
52
|
error = "Database ID undefined."
|
|
52
53
|
raise ValueError(error)
|
|
53
|
-
url = "{
|
|
54
|
+
url = f"{self.baseurl}/{database_id}"
|
|
54
55
|
self.delete_request(url)
|
|
55
|
-
logger.info("Deleted single database (ID: {
|
|
56
|
+
logger.info(f"Deleted single database (ID: {database_id})")
|
|
56
57
|
|
|
57
58
|
@api(version="3.5")
|
|
58
59
|
def update(self, database_item):
|
|
@@ -60,10 +61,10 @@ class Databases(Endpoint, TaggingMixin):
|
|
|
60
61
|
error = "Database item missing ID."
|
|
61
62
|
raise MissingRequiredFieldError(error)
|
|
62
63
|
|
|
63
|
-
url = "{
|
|
64
|
+
url = f"{self.baseurl}/{database_item.id}"
|
|
64
65
|
update_req = RequestFactory.Database.update_req(database_item)
|
|
65
66
|
server_response = self.put_request(url, update_req)
|
|
66
|
-
logger.info("Updated database item (ID: {
|
|
67
|
+
logger.info(f"Updated database item (ID: {database_item.id})")
|
|
67
68
|
updated_database = DatabaseItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
68
69
|
return updated_database
|
|
69
70
|
|
|
@@ -78,10 +79,10 @@ class Databases(Endpoint, TaggingMixin):
|
|
|
78
79
|
return self._get_tables_for_database(database_item)
|
|
79
80
|
|
|
80
81
|
database_item._set_tables(column_fetcher)
|
|
81
|
-
logger.info("Populated tables for database (ID: {
|
|
82
|
+
logger.info(f"Populated tables for database (ID: {database_item.id}")
|
|
82
83
|
|
|
83
84
|
def _get_tables_for_database(self, database_item):
|
|
84
|
-
url = "{
|
|
85
|
+
url = f"{self.baseurl}/{database_item.id}/tables"
|
|
85
86
|
server_response = self.get_request(url)
|
|
86
87
|
tables = TableItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
87
88
|
return tables
|
|
@@ -127,7 +128,7 @@ class Databases(Endpoint, TaggingMixin):
|
|
|
127
128
|
self._data_quality_warnings.clear(item)
|
|
128
129
|
|
|
129
130
|
@api(version="3.9")
|
|
130
|
-
def add_tags(self, item: Union[DatabaseItem, str], tags: Iterable[str]) ->
|
|
131
|
+
def add_tags(self, item: Union[DatabaseItem, str], tags: Iterable[str]) -> set[str]:
|
|
131
132
|
return super().add_tags(item, tags)
|
|
132
133
|
|
|
133
134
|
@api(version="3.9")
|
|
@@ -6,7 +6,8 @@ import os
|
|
|
6
6
|
|
|
7
7
|
from contextlib import closing
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import Optional, TYPE_CHECKING, Union
|
|
10
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
10
11
|
|
|
11
12
|
from tableauserverclient.helpers.headers import fix_filename
|
|
12
13
|
from tableauserverclient.server.query import QuerySet
|
|
@@ -22,7 +23,7 @@ from tableauserverclient.server.endpoint.exceptions import InternalServerError,
|
|
|
22
23
|
from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
|
|
23
24
|
from tableauserverclient.server.endpoint.resource_tagger import TaggingMixin
|
|
24
25
|
|
|
25
|
-
from tableauserverclient.config import ALLOWED_FILE_EXTENSIONS,
|
|
26
|
+
from tableauserverclient.config import ALLOWED_FILE_EXTENSIONS, BYTES_PER_MB, config
|
|
26
27
|
from tableauserverclient.filesys_helpers import (
|
|
27
28
|
make_download_path,
|
|
28
29
|
get_file_type,
|
|
@@ -57,7 +58,7 @@ PathOrFileW = Union[FilePath, FileObjectW]
|
|
|
57
58
|
|
|
58
59
|
class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]):
|
|
59
60
|
def __init__(self, parent_srv: "Server") -> None:
|
|
60
|
-
super(
|
|
61
|
+
super().__init__(parent_srv)
|
|
61
62
|
self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
|
|
62
63
|
self._data_quality_warnings = _DataQualityWarningEndpoint(self.parent_srv, "datasource")
|
|
63
64
|
|
|
@@ -65,11 +66,11 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
65
66
|
|
|
66
67
|
@property
|
|
67
68
|
def baseurl(self) -> str:
|
|
68
|
-
return "{
|
|
69
|
+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/datasources"
|
|
69
70
|
|
|
70
71
|
# Get all datasources
|
|
71
72
|
@api(version="2.0")
|
|
72
|
-
def get(self, req_options: Optional[RequestOptions] = None) ->
|
|
73
|
+
def get(self, req_options: Optional[RequestOptions] = None) -> tuple[list[DatasourceItem], PaginationItem]:
|
|
73
74
|
logger.info("Querying all datasources on site")
|
|
74
75
|
url = self.baseurl
|
|
75
76
|
server_response = self.get_request(url, req_options)
|
|
@@ -83,8 +84,8 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
83
84
|
if not datasource_id:
|
|
84
85
|
error = "Datasource ID undefined."
|
|
85
86
|
raise ValueError(error)
|
|
86
|
-
logger.info("Querying single datasource (ID: {
|
|
87
|
-
url = "{
|
|
87
|
+
logger.info(f"Querying single datasource (ID: {datasource_id})")
|
|
88
|
+
url = f"{self.baseurl}/{datasource_id}"
|
|
88
89
|
server_response = self.get_request(url)
|
|
89
90
|
return DatasourceItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
90
91
|
|
|
@@ -99,10 +100,10 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
99
100
|
return self._get_datasource_connections(datasource_item)
|
|
100
101
|
|
|
101
102
|
datasource_item._set_connections(connections_fetcher)
|
|
102
|
-
logger.info("Populated connections for datasource (ID: {
|
|
103
|
+
logger.info(f"Populated connections for datasource (ID: {datasource_item.id})")
|
|
103
104
|
|
|
104
105
|
def _get_datasource_connections(self, datasource_item, req_options=None):
|
|
105
|
-
url = "{
|
|
106
|
+
url = f"{self.baseurl}/{datasource_item.id}/connections"
|
|
106
107
|
server_response = self.get_request(url, req_options)
|
|
107
108
|
connections = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
108
109
|
return connections
|
|
@@ -113,9 +114,9 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
113
114
|
if not datasource_id:
|
|
114
115
|
error = "Datasource ID undefined."
|
|
115
116
|
raise ValueError(error)
|
|
116
|
-
url = "{
|
|
117
|
+
url = f"{self.baseurl}/{datasource_id}"
|
|
117
118
|
self.delete_request(url)
|
|
118
|
-
logger.info("Deleted single datasource (ID: {
|
|
119
|
+
logger.info(f"Deleted single datasource (ID: {datasource_id})")
|
|
119
120
|
|
|
120
121
|
# Download 1 datasource by id
|
|
121
122
|
@api(version="2.0")
|
|
@@ -152,11 +153,11 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
152
153
|
self.update_tags(datasource_item)
|
|
153
154
|
|
|
154
155
|
# Update the datasource itself
|
|
155
|
-
url = "{
|
|
156
|
+
url = f"{self.baseurl}/{datasource_item.id}"
|
|
156
157
|
|
|
157
158
|
update_req = RequestFactory.Datasource.update_req(datasource_item)
|
|
158
159
|
server_response = self.put_request(url, update_req)
|
|
159
|
-
logger.info("Updated datasource item (ID: {
|
|
160
|
+
logger.info(f"Updated datasource item (ID: {datasource_item.id})")
|
|
160
161
|
updated_datasource = copy.copy(datasource_item)
|
|
161
162
|
return updated_datasource._parse_common_elements(server_response.content, self.parent_srv.namespace)
|
|
162
163
|
|
|
@@ -165,7 +166,7 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
165
166
|
def update_connection(
|
|
166
167
|
self, datasource_item: DatasourceItem, connection_item: ConnectionItem
|
|
167
168
|
) -> Optional[ConnectionItem]:
|
|
168
|
-
url = "{
|
|
169
|
+
url = f"{self.baseurl}/{datasource_item.id}/connections/{connection_item.id}"
|
|
169
170
|
|
|
170
171
|
update_req = RequestFactory.Connection.update_req(connection_item)
|
|
171
172
|
server_response = self.put_request(url, update_req)
|
|
@@ -174,18 +175,16 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
174
175
|
return None
|
|
175
176
|
|
|
176
177
|
if len(connections) > 1:
|
|
177
|
-
logger.debug("Multiple connections returned ({
|
|
178
|
+
logger.debug(f"Multiple connections returned ({len(connections)})")
|
|
178
179
|
connection = list(filter(lambda x: x.id == connection_item.id, connections))[0]
|
|
179
180
|
|
|
180
|
-
logger.info(
|
|
181
|
-
"Updated datasource item (ID: {0} & connection item {1}".format(datasource_item.id, connection_item.id)
|
|
182
|
-
)
|
|
181
|
+
logger.info(f"Updated datasource item (ID: {datasource_item.id} & connection item {connection_item.id}")
|
|
183
182
|
return connection
|
|
184
183
|
|
|
185
184
|
@api(version="2.8")
|
|
186
185
|
def refresh(self, datasource_item: DatasourceItem) -> JobItem:
|
|
187
186
|
id_ = getattr(datasource_item, "id", datasource_item)
|
|
188
|
-
url = "{
|
|
187
|
+
url = f"{self.baseurl}/{id_}/refresh"
|
|
189
188
|
empty_req = RequestFactory.Empty.empty_req()
|
|
190
189
|
server_response = self.post_request(url, empty_req)
|
|
191
190
|
new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
@@ -194,7 +193,7 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
194
193
|
@api(version="3.5")
|
|
195
194
|
def create_extract(self, datasource_item: DatasourceItem, encrypt: bool = False) -> JobItem:
|
|
196
195
|
id_ = getattr(datasource_item, "id", datasource_item)
|
|
197
|
-
url = "{
|
|
196
|
+
url = f"{self.baseurl}/{id_}/createExtract?encrypt={encrypt}"
|
|
198
197
|
empty_req = RequestFactory.Empty.empty_req()
|
|
199
198
|
server_response = self.post_request(url, empty_req)
|
|
200
199
|
new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
@@ -203,7 +202,7 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
203
202
|
@api(version="3.5")
|
|
204
203
|
def delete_extract(self, datasource_item: DatasourceItem) -> None:
|
|
205
204
|
id_ = getattr(datasource_item, "id", datasource_item)
|
|
206
|
-
url = "{
|
|
205
|
+
url = f"{self.baseurl}/{id_}/deleteExtract"
|
|
207
206
|
empty_req = RequestFactory.Empty.empty_req()
|
|
208
207
|
self.post_request(url, empty_req)
|
|
209
208
|
|
|
@@ -223,12 +222,12 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
223
222
|
if isinstance(file, (os.PathLike, str)):
|
|
224
223
|
if not os.path.isfile(file):
|
|
225
224
|
error = "File path does not lead to an existing file."
|
|
226
|
-
raise
|
|
225
|
+
raise OSError(error)
|
|
227
226
|
|
|
228
227
|
filename = os.path.basename(file)
|
|
229
228
|
file_extension = os.path.splitext(filename)[1][1:]
|
|
230
229
|
file_size = os.path.getsize(file)
|
|
231
|
-
logger.debug("Publishing file `{}`, size `{}`"
|
|
230
|
+
logger.debug(f"Publishing file `{filename}`, size `{file_size}`")
|
|
232
231
|
# If name is not defined, grab the name from the file to publish
|
|
233
232
|
if not datasource_item.name:
|
|
234
233
|
datasource_item.name = os.path.splitext(filename)[0]
|
|
@@ -247,10 +246,10 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
247
246
|
elif file_type == "xml":
|
|
248
247
|
file_extension = "tds"
|
|
249
248
|
else:
|
|
250
|
-
error = "Unsupported file type {}"
|
|
249
|
+
error = f"Unsupported file type {file_type}"
|
|
251
250
|
raise ValueError(error)
|
|
252
251
|
|
|
253
|
-
filename = "{}.{}"
|
|
252
|
+
filename = f"{datasource_item.name}.{file_extension}"
|
|
254
253
|
file_size = get_file_object_size(file)
|
|
255
254
|
|
|
256
255
|
else:
|
|
@@ -261,27 +260,27 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
261
260
|
raise ValueError(error)
|
|
262
261
|
|
|
263
262
|
# Construct the url with the defined mode
|
|
264
|
-
url = "{
|
|
263
|
+
url = f"{self.baseurl}?datasourceType={file_extension}"
|
|
265
264
|
if mode == self.parent_srv.PublishMode.Overwrite or mode == self.parent_srv.PublishMode.Append:
|
|
266
|
-
url += "&{
|
|
265
|
+
url += f"&{mode.lower()}=true"
|
|
267
266
|
|
|
268
267
|
if as_job:
|
|
269
|
-
url += "&{
|
|
268
|
+
url += "&{}=true".format("asJob")
|
|
270
269
|
|
|
271
270
|
# Determine if chunking is required (64MB is the limit for single upload method)
|
|
272
|
-
if file_size >= FILESIZE_LIMIT_MB * BYTES_PER_MB:
|
|
271
|
+
if file_size >= config.FILESIZE_LIMIT_MB * BYTES_PER_MB:
|
|
273
272
|
logger.info(
|
|
274
273
|
"Publishing {} to server with chunking method (datasource over {}MB, chunk size {}MB)".format(
|
|
275
|
-
filename, FILESIZE_LIMIT_MB, config.CHUNK_SIZE_MB
|
|
274
|
+
filename, config.FILESIZE_LIMIT_MB, config.CHUNK_SIZE_MB
|
|
276
275
|
)
|
|
277
276
|
)
|
|
278
277
|
upload_session_id = self.parent_srv.fileuploads.upload(file)
|
|
279
|
-
url = "{
|
|
278
|
+
url = f"{url}&uploadSessionId={upload_session_id}"
|
|
280
279
|
xml_request, content_type = RequestFactory.Datasource.publish_req_chunked(
|
|
281
280
|
datasource_item, connection_credentials, connections
|
|
282
281
|
)
|
|
283
282
|
else:
|
|
284
|
-
logger.info("Publishing {
|
|
283
|
+
logger.info(f"Publishing {filename} to server")
|
|
285
284
|
|
|
286
285
|
if isinstance(file, (Path, str)):
|
|
287
286
|
with open(file, "rb") as f:
|
|
@@ -309,11 +308,11 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
309
308
|
|
|
310
309
|
if as_job:
|
|
311
310
|
new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
312
|
-
logger.info("Published {
|
|
311
|
+
logger.info(f"Published {filename} (JOB_ID: {new_job.id}")
|
|
313
312
|
return new_job
|
|
314
313
|
else:
|
|
315
314
|
new_datasource = DatasourceItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
316
|
-
logger.info("Published {
|
|
315
|
+
logger.info(f"Published {filename} (ID: {new_datasource.id})")
|
|
317
316
|
return new_datasource
|
|
318
317
|
|
|
319
318
|
@api(version="3.13")
|
|
@@ -327,23 +326,23 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
327
326
|
) -> JobItem:
|
|
328
327
|
if isinstance(datasource_or_connection_item, DatasourceItem):
|
|
329
328
|
datasource_id = datasource_or_connection_item.id
|
|
330
|
-
url = "{
|
|
329
|
+
url = f"{self.baseurl}/{datasource_id}/data"
|
|
331
330
|
elif isinstance(datasource_or_connection_item, ConnectionItem):
|
|
332
331
|
datasource_id = datasource_or_connection_item.datasource_id
|
|
333
332
|
connection_id = datasource_or_connection_item.id
|
|
334
|
-
url = "{
|
|
333
|
+
url = f"{self.baseurl}/{datasource_id}/connections/{connection_id}/data"
|
|
335
334
|
else:
|
|
336
335
|
assert isinstance(datasource_or_connection_item, str)
|
|
337
|
-
url = "{
|
|
336
|
+
url = f"{self.baseurl}/{datasource_or_connection_item}/data"
|
|
338
337
|
|
|
339
338
|
if payload is not None:
|
|
340
339
|
if not os.path.isfile(payload):
|
|
341
340
|
error = "File path does not lead to an existing file."
|
|
342
|
-
raise
|
|
341
|
+
raise OSError(error)
|
|
343
342
|
|
|
344
|
-
logger.info("Uploading {
|
|
343
|
+
logger.info(f"Uploading {payload} to server with chunking method for Update job")
|
|
345
344
|
upload_session_id = self.parent_srv.fileuploads.upload(payload)
|
|
346
|
-
url = "{
|
|
345
|
+
url = f"{url}?uploadSessionId={upload_session_id}"
|
|
347
346
|
|
|
348
347
|
json_request = json.dumps({"actions": actions})
|
|
349
348
|
parameters = {"headers": {"requestid": request_id}}
|
|
@@ -356,7 +355,7 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
356
355
|
self._permissions.populate(item)
|
|
357
356
|
|
|
358
357
|
@api(version="2.0")
|
|
359
|
-
def update_permissions(self, item: DatasourceItem, permission_item:
|
|
358
|
+
def update_permissions(self, item: DatasourceItem, permission_item: list["PermissionsRule"]) -> None:
|
|
360
359
|
self._permissions.update(item, permission_item)
|
|
361
360
|
|
|
362
361
|
@api(version="2.0")
|
|
@@ -390,12 +389,12 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
390
389
|
return self._get_datasource_revisions(datasource_item)
|
|
391
390
|
|
|
392
391
|
datasource_item._set_revisions(revisions_fetcher)
|
|
393
|
-
logger.info("Populated revisions for datasource (ID: {
|
|
392
|
+
logger.info(f"Populated revisions for datasource (ID: {datasource_item.id})")
|
|
394
393
|
|
|
395
394
|
def _get_datasource_revisions(
|
|
396
395
|
self, datasource_item: DatasourceItem, req_options: Optional["RequestOptions"] = None
|
|
397
|
-
) ->
|
|
398
|
-
url = "{
|
|
396
|
+
) -> list[RevisionItem]:
|
|
397
|
+
url = f"{self.baseurl}/{datasource_item.id}/revisions"
|
|
399
398
|
server_response = self.get_request(url, req_options)
|
|
400
399
|
revisions = RevisionItem.from_response(server_response.content, self.parent_srv.namespace, datasource_item)
|
|
401
400
|
return revisions
|
|
@@ -413,9 +412,9 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
413
412
|
error = "Datasource ID undefined."
|
|
414
413
|
raise ValueError(error)
|
|
415
414
|
if revision_number is None:
|
|
416
|
-
url = "{
|
|
415
|
+
url = f"{self.baseurl}/{datasource_id}/content"
|
|
417
416
|
else:
|
|
418
|
-
url = "{
|
|
417
|
+
url = f"{self.baseurl}/{datasource_id}/revisions/{revision_number}/content"
|
|
419
418
|
|
|
420
419
|
if not include_extract:
|
|
421
420
|
url += "?includeExtract=False"
|
|
@@ -437,9 +436,7 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
437
436
|
f.write(chunk)
|
|
438
437
|
return_path = os.path.abspath(download_path)
|
|
439
438
|
|
|
440
|
-
logger.info(
|
|
441
|
-
"Downloaded datasource revision {0} to {1} (ID: {2})".format(revision_number, return_path, datasource_id)
|
|
442
|
-
)
|
|
439
|
+
logger.info(f"Downloaded datasource revision {revision_number} to {return_path} (ID: {datasource_id})")
|
|
443
440
|
return return_path
|
|
444
441
|
|
|
445
442
|
@api(version="2.3")
|
|
@@ -449,19 +446,17 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
|
|
|
449
446
|
url = "/".join([self.baseurl, datasource_id, "revisions", revision_number])
|
|
450
447
|
|
|
451
448
|
self.delete_request(url)
|
|
452
|
-
logger.info(
|
|
453
|
-
"Deleted single datasource revision (ID: {0}) (Revision: {1})".format(datasource_id, revision_number)
|
|
454
|
-
)
|
|
449
|
+
logger.info(f"Deleted single datasource revision (ID: {datasource_id}) (Revision: {revision_number})")
|
|
455
450
|
|
|
456
451
|
# a convenience method
|
|
457
452
|
@api(version="2.8")
|
|
458
453
|
def schedule_extract_refresh(
|
|
459
454
|
self, schedule_id: str, item: DatasourceItem
|
|
460
|
-
) ->
|
|
455
|
+
) -> list["AddResponse"]: # actually should return a task
|
|
461
456
|
return self.parent_srv.schedules.add_to_schedule(schedule_id, datasource=item)
|
|
462
457
|
|
|
463
458
|
@api(version="1.0")
|
|
464
|
-
def add_tags(self, item: Union[DatasourceItem, str], tags: Union[Iterable[str], str]) ->
|
|
459
|
+
def add_tags(self, item: Union[DatasourceItem, str], tags: Union[Iterable[str], str]) -> set[str]:
|
|
465
460
|
return super().add_tags(item, tags)
|
|
466
461
|
|
|
467
462
|
@api(version="1.0")
|
|
@@ -4,7 +4,8 @@ from .endpoint import Endpoint
|
|
|
4
4
|
from .exceptions import MissingRequiredFieldError
|
|
5
5
|
from tableauserverclient.server import RequestFactory
|
|
6
6
|
from tableauserverclient.models import DatabaseItem, PermissionsRule, ProjectItem, plural_type, Resource
|
|
7
|
-
from typing import TYPE_CHECKING, Callable,
|
|
7
|
+
from typing import TYPE_CHECKING, Callable, Optional, Union
|
|
8
|
+
from collections.abc import Sequence
|
|
8
9
|
|
|
9
10
|
if TYPE_CHECKING:
|
|
10
11
|
from ..server import Server
|
|
@@ -25,7 +26,7 @@ class _DefaultPermissionsEndpoint(Endpoint):
|
|
|
25
26
|
"""
|
|
26
27
|
|
|
27
28
|
def __init__(self, parent_srv: "Server", owner_baseurl: Callable[[], str]) -> None:
|
|
28
|
-
super(
|
|
29
|
+
super().__init__(parent_srv)
|
|
29
30
|
|
|
30
31
|
# owner_baseurl is the baseurl of the parent, a project or database.
|
|
31
32
|
# It MUST be a lambda since we don't know the full site URL until we sign in.
|
|
@@ -33,23 +34,25 @@ class _DefaultPermissionsEndpoint(Endpoint):
|
|
|
33
34
|
self.owner_baseurl = owner_baseurl
|
|
34
35
|
|
|
35
36
|
def __str__(self):
|
|
36
|
-
return "<DefaultPermissionsEndpoint {} [Flow, Datasource, Workbook, Lens]>"
|
|
37
|
+
return f"<DefaultPermissionsEndpoint {self.owner_baseurl()} [Flow, Datasource, Workbook, Lens]>"
|
|
37
38
|
|
|
38
39
|
__repr__ = __str__
|
|
39
40
|
|
|
40
41
|
def update_default_permissions(
|
|
41
|
-
self, resource: BaseItem, permissions: Sequence[PermissionsRule], content_type: Resource
|
|
42
|
-
) ->
|
|
43
|
-
url = "{
|
|
42
|
+
self, resource: BaseItem, permissions: Sequence[PermissionsRule], content_type: Union[Resource, str]
|
|
43
|
+
) -> list[PermissionsRule]:
|
|
44
|
+
url = f"{self.owner_baseurl()}/{resource.id}/default-permissions/{plural_type(content_type)}"
|
|
44
45
|
update_req = RequestFactory.Permission.add_req(permissions)
|
|
45
46
|
response = self.put_request(url, update_req)
|
|
46
47
|
permissions = PermissionsRule.from_response(response.content, self.parent_srv.namespace)
|
|
47
|
-
logger.info("Updated default {} permissions for resource {
|
|
48
|
+
logger.info(f"Updated default {content_type} permissions for resource {resource.id}")
|
|
48
49
|
logger.info(permissions)
|
|
49
50
|
|
|
50
51
|
return permissions
|
|
51
52
|
|
|
52
|
-
def delete_default_permission(
|
|
53
|
+
def delete_default_permission(
|
|
54
|
+
self, resource: BaseItem, rule: PermissionsRule, content_type: Union[Resource, str]
|
|
55
|
+
) -> None:
|
|
53
56
|
for capability, mode in rule.capabilities.items():
|
|
54
57
|
# Made readability better but line is too long, will make this look better
|
|
55
58
|
url = (
|
|
@@ -65,29 +68,27 @@ class _DefaultPermissionsEndpoint(Endpoint):
|
|
|
65
68
|
)
|
|
66
69
|
)
|
|
67
70
|
|
|
68
|
-
logger.debug("Removing {
|
|
71
|
+
logger.debug(f"Removing {mode} permission for capability {capability}")
|
|
69
72
|
|
|
70
73
|
self.delete_request(url)
|
|
71
74
|
|
|
72
|
-
logger.info(
|
|
73
|
-
"Deleted permission for {0} {1} item {2}".format(rule.grantee.tag_name, rule.grantee.id, resource.id)
|
|
74
|
-
)
|
|
75
|
+
logger.info(f"Deleted permission for {rule.grantee.tag_name} {rule.grantee.id} item {resource.id}")
|
|
75
76
|
|
|
76
|
-
def populate_default_permissions(self, item: BaseItem, content_type: Resource) -> None:
|
|
77
|
+
def populate_default_permissions(self, item: BaseItem, content_type: Union[Resource, str]) -> None:
|
|
77
78
|
if not item.id:
|
|
78
79
|
error = "Server item is missing ID. Item must be retrieved from server first."
|
|
79
80
|
raise MissingRequiredFieldError(error)
|
|
80
81
|
|
|
81
|
-
def permission_fetcher() ->
|
|
82
|
+
def permission_fetcher() -> list[PermissionsRule]:
|
|
82
83
|
return self._get_default_permissions(item, content_type)
|
|
83
84
|
|
|
84
85
|
item._set_default_permissions(permission_fetcher, content_type)
|
|
85
|
-
logger.info("Populated default {
|
|
86
|
+
logger.info(f"Populated default {content_type} permissions for item (ID: {item.id})")
|
|
86
87
|
|
|
87
88
|
def _get_default_permissions(
|
|
88
|
-
self, item: BaseItem, content_type: Resource, req_options: Optional["RequestOptions"] = None
|
|
89
|
-
) ->
|
|
90
|
-
url = "{
|
|
89
|
+
self, item: BaseItem, content_type: Union[Resource, str], req_options: Optional["RequestOptions"] = None
|
|
90
|
+
) -> list[PermissionsRule]:
|
|
91
|
+
url = f"{self.owner_baseurl()}/{item.id}/default-permissions/{plural_type(content_type)}"
|
|
91
92
|
server_response = self.get_request(url, req_options)
|
|
92
93
|
permissions = PermissionsRule.from_response(server_response.content, self.parent_srv.namespace)
|
|
93
94
|
logger.info({"content_type": content_type, "permissions": permissions})
|
|
@@ -10,35 +10,35 @@ from tableauserverclient.helpers.logging import logger
|
|
|
10
10
|
|
|
11
11
|
class _DataQualityWarningEndpoint(Endpoint):
|
|
12
12
|
def __init__(self, parent_srv, resource_type):
|
|
13
|
-
super(
|
|
13
|
+
super().__init__(parent_srv)
|
|
14
14
|
self.resource_type = resource_type
|
|
15
15
|
|
|
16
16
|
@property
|
|
17
17
|
def baseurl(self):
|
|
18
|
-
return "{
|
|
18
|
+
return "{}/sites/{}/dataQualityWarnings/{}".format(
|
|
19
19
|
self.parent_srv.baseurl, self.parent_srv.site_id, self.resource_type
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
def add(self, resource, warning):
|
|
23
|
-
url = "{baseurl}/{
|
|
23
|
+
url = f"{self.baseurl}/{resource.id}"
|
|
24
24
|
add_req = RequestFactory.DQW.add_req(warning)
|
|
25
25
|
response = self.post_request(url, add_req)
|
|
26
26
|
warnings = DQWItem.from_response(response.content, self.parent_srv.namespace)
|
|
27
|
-
logger.info("Added dqw for resource {
|
|
27
|
+
logger.info(f"Added dqw for resource {resource.id}")
|
|
28
28
|
|
|
29
29
|
return warnings
|
|
30
30
|
|
|
31
31
|
def update(self, resource, warning):
|
|
32
|
-
url = "{baseurl}/{
|
|
32
|
+
url = f"{self.baseurl}/{resource.id}"
|
|
33
33
|
add_req = RequestFactory.DQW.update_req(warning)
|
|
34
34
|
response = self.put_request(url, add_req)
|
|
35
35
|
warnings = DQWItem.from_response(response.content, self.parent_srv.namespace)
|
|
36
|
-
logger.info("Added dqw for resource {
|
|
36
|
+
logger.info(f"Added dqw for resource {resource.id}")
|
|
37
37
|
|
|
38
38
|
return warnings
|
|
39
39
|
|
|
40
40
|
def clear(self, resource):
|
|
41
|
-
url = "{baseurl}/{
|
|
41
|
+
url = f"{self.baseurl}/{resource.id}"
|
|
42
42
|
return self.delete_request(url)
|
|
43
43
|
|
|
44
44
|
def populate(self, item):
|
|
@@ -50,10 +50,10 @@ class _DataQualityWarningEndpoint(Endpoint):
|
|
|
50
50
|
return self._get_data_quality_warnings(item)
|
|
51
51
|
|
|
52
52
|
item._set_data_quality_warnings(dqw_fetcher)
|
|
53
|
-
logger.info("Populated permissions for item (ID: {
|
|
53
|
+
logger.info(f"Populated permissions for item (ID: {item.id})")
|
|
54
54
|
|
|
55
55
|
def _get_data_quality_warnings(self, item, req_options=None):
|
|
56
|
-
url = "{baseurl}/{
|
|
56
|
+
url = f"{self.baseurl}/{item.id}"
|
|
57
57
|
server_response = self.get_request(url, req_options)
|
|
58
58
|
dqws = DQWItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
59
59
|
|