tableauserverclient 0.32__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 +34 -18
- tableauserverclient/_version.py +3 -3
- tableauserverclient/config.py +20 -6
- tableauserverclient/models/__init__.py +12 -0
- tableauserverclient/models/column_item.py +1 -1
- tableauserverclient/models/connection_credentials.py +1 -1
- tableauserverclient/models/connection_item.py +10 -8
- 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 +8 -2
- 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 +12 -12
- tableauserverclient/models/flow_run_item.py +3 -3
- tableauserverclient/models/group_item.py +4 -4
- tableauserverclient/models/groupset_item.py +53 -0
- tableauserverclient/models/interval_item.py +36 -23
- tableauserverclient/models/job_item.py +26 -10
- tableauserverclient/models/linked_tasks_item.py +102 -0
- tableauserverclient/models/metric_item.py +5 -5
- tableauserverclient/models/pagination_item.py +1 -1
- tableauserverclient/models/permissions_item.py +19 -14
- 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 +11 -9
- 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 +78 -0
- 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/__init__.py +8 -0
- tableauserverclient/server/endpoint/auth_endpoint.py +68 -11
- tableauserverclient/server/endpoint/custom_views_endpoint.py +124 -19
- 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 +32 -17
- tableauserverclient/server/endpoint/datasources_endpoint.py +150 -59
- tableauserverclient/server/endpoint/default_permissions_endpoint.py +19 -18
- tableauserverclient/server/endpoint/dqw_endpoint.py +9 -9
- tableauserverclient/server/endpoint/endpoint.py +47 -31
- tableauserverclient/server/endpoint/exceptions.py +23 -7
- tableauserverclient/server/endpoint/favorites_endpoint.py +31 -31
- tableauserverclient/server/endpoint/fileuploads_endpoint.py +11 -13
- tableauserverclient/server/endpoint/flow_runs_endpoint.py +59 -17
- tableauserverclient/server/endpoint/flow_task_endpoint.py +2 -2
- tableauserverclient/server/endpoint/flows_endpoint.py +73 -35
- tableauserverclient/server/endpoint/groups_endpoint.py +96 -27
- tableauserverclient/server/endpoint/groupsets_endpoint.py +127 -0
- tableauserverclient/server/endpoint/jobs_endpoint.py +79 -12
- tableauserverclient/server/endpoint/linked_tasks_endpoint.py +45 -0
- 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 +124 -30
- tableauserverclient/server/endpoint/resource_tagger.py +139 -6
- 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 +33 -19
- tableauserverclient/server/endpoint/tasks_endpoint.py +8 -8
- tableauserverclient/server/endpoint/users_endpoint.py +405 -19
- tableauserverclient/server/endpoint/views_endpoint.py +111 -25
- tableauserverclient/server/endpoint/virtual_connections_endpoint.py +174 -0
- tableauserverclient/server/endpoint/webhooks_endpoint.py +11 -11
- tableauserverclient/server/endpoint/workbooks_endpoint.py +735 -68
- tableauserverclient/server/filter.py +2 -2
- tableauserverclient/server/pager.py +8 -10
- tableauserverclient/server/query.py +70 -20
- tableauserverclient/server/request_factory.py +213 -41
- tableauserverclient/server/request_options.py +125 -145
- tableauserverclient/server/server.py +73 -9
- tableauserverclient/server/sort.py +2 -2
- {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/METADATA +17 -17
- tableauserverclient-0.34.dist-info/RECORD +106 -0
- {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/WHEEL +1 -1
- tableauserverclient-0.32.dist-info/RECORD +0 -100
- {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/LICENSE +0 -0
- {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/LICENSE.versioneer +0 -0
- {tableauserverclient-0.32.dist-info → tableauserverclient-0.34.dist-info}/top_level.txt +0 -0
|
@@ -4,9 +4,9 @@ import warnings
|
|
|
4
4
|
|
|
5
5
|
from defusedxml.ElementTree import fromstring
|
|
6
6
|
|
|
7
|
-
from .endpoint import Endpoint, api
|
|
8
|
-
from .exceptions import ServerResponseError
|
|
9
|
-
from
|
|
7
|
+
from tableauserverclient.server.endpoint.endpoint import Endpoint, api
|
|
8
|
+
from tableauserverclient.server.endpoint.exceptions import ServerResponseError
|
|
9
|
+
from tableauserverclient.server.request_factory import RequestFactory
|
|
10
10
|
|
|
11
11
|
from tableauserverclient.helpers.logging import logger
|
|
12
12
|
|
|
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class Auth(Endpoint):
|
|
19
|
-
class contextmgr
|
|
19
|
+
class contextmgr:
|
|
20
20
|
def __init__(self, callback):
|
|
21
21
|
self._callback = callback
|
|
22
22
|
|
|
@@ -28,7 +28,7 @@ class Auth(Endpoint):
|
|
|
28
28
|
|
|
29
29
|
@property
|
|
30
30
|
def baseurl(self) -> str:
|
|
31
|
-
return "{
|
|
31
|
+
return f"{self.parent_srv.baseurl}/auth"
|
|
32
32
|
|
|
33
33
|
@api(version="2.0")
|
|
34
34
|
def sign_in(self, auth_req: "Credentials") -> contextmgr:
|
|
@@ -41,8 +41,32 @@ class Auth(Endpoint):
|
|
|
41
41
|
optionally a user_id to impersonate.
|
|
42
42
|
|
|
43
43
|
Creates a context manager that will sign out of the server upon exit.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
auth_req : Credentials
|
|
48
|
+
The credentials object to use for signing in. Can be a TableauAuth,
|
|
49
|
+
PersonalAccessTokenAuth, or JWTAuth object.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
contextmgr
|
|
54
|
+
A context manager that will sign out of the server upon exit.
|
|
55
|
+
|
|
56
|
+
Examples
|
|
57
|
+
--------
|
|
58
|
+
>>> import tableauserverclient as TSC
|
|
59
|
+
|
|
60
|
+
>>> # create an auth object
|
|
61
|
+
>>> tableau_auth = TSC.TableauAuth('USERNAME', 'PASSWORD')
|
|
62
|
+
|
|
63
|
+
>>> # create an instance for your server
|
|
64
|
+
>>> server = TSC.Server('https://SERVER_URL')
|
|
65
|
+
|
|
66
|
+
>>> # call the sign-in method with the auth object
|
|
67
|
+
>>> server.auth.sign_in(tableau_auth)
|
|
44
68
|
"""
|
|
45
|
-
url = "{
|
|
69
|
+
url = f"{self.baseurl}/signin"
|
|
46
70
|
signin_req = RequestFactory.Auth.signin_req(auth_req)
|
|
47
71
|
server_response = self.parent_srv.session.post(
|
|
48
72
|
url, data=signin_req, **self.parent_srv.http_options, allow_redirects=False
|
|
@@ -63,22 +87,25 @@ class Auth(Endpoint):
|
|
|
63
87
|
user_id = parsed_response.find(".//t:user", namespaces=self.parent_srv.namespace).get("id", None)
|
|
64
88
|
auth_token = parsed_response.find("t:credentials", namespaces=self.parent_srv.namespace).get("token", None)
|
|
65
89
|
self.parent_srv._set_auth(site_id, user_id, auth_token)
|
|
66
|
-
logger.info("Signed into {
|
|
90
|
+
logger.info(f"Signed into {self.parent_srv.server_address} as user with id {user_id}")
|
|
67
91
|
return Auth.contextmgr(self.sign_out)
|
|
68
92
|
|
|
69
93
|
# We use the same request that username/password login uses for all auth types.
|
|
70
94
|
# The distinct methods are mostly useful for explicitly showing api version support for each auth type
|
|
71
95
|
@api(version="3.6")
|
|
72
96
|
def sign_in_with_personal_access_token(self, auth_req: "Credentials") -> contextmgr:
|
|
97
|
+
"""Passthrough to sign_in method"""
|
|
73
98
|
return self.sign_in(auth_req)
|
|
74
99
|
|
|
75
100
|
@api(version="3.17")
|
|
76
101
|
def sign_in_with_json_web_token(self, auth_req: "Credentials") -> contextmgr:
|
|
102
|
+
"""Passthrough to sign_in method"""
|
|
77
103
|
return self.sign_in(auth_req)
|
|
78
104
|
|
|
79
105
|
@api(version="2.0")
|
|
80
106
|
def sign_out(self) -> None:
|
|
81
|
-
|
|
107
|
+
"""Sign out of current session."""
|
|
108
|
+
url = f"{self.baseurl}/signout"
|
|
82
109
|
# If there are no auth tokens you're already signed out. No-op
|
|
83
110
|
if not self.parent_srv.is_signed_in():
|
|
84
111
|
return
|
|
@@ -88,7 +115,34 @@ class Auth(Endpoint):
|
|
|
88
115
|
|
|
89
116
|
@api(version="2.6")
|
|
90
117
|
def switch_site(self, site_item: "SiteItem") -> contextmgr:
|
|
91
|
-
|
|
118
|
+
"""
|
|
119
|
+
Switch to a different site on the server. This will sign out of the
|
|
120
|
+
current site and sign in to the new site. If used as a context manager,
|
|
121
|
+
will sign out of the new site upon exit.
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
site_item : SiteItem
|
|
126
|
+
The site to switch to.
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
contextmgr
|
|
131
|
+
A context manager that will sign out of the new site upon exit.
|
|
132
|
+
|
|
133
|
+
Examples
|
|
134
|
+
--------
|
|
135
|
+
>>> import tableauserverclient as TSC
|
|
136
|
+
|
|
137
|
+
>>> # Find the site you want to switch to
|
|
138
|
+
>>> new_site = server.sites.get_by_id("9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d")
|
|
139
|
+
>>> # switch to the new site
|
|
140
|
+
>>> with server.auth.switch_site(new_site):
|
|
141
|
+
>>> # do something on the new site
|
|
142
|
+
>>> pass
|
|
143
|
+
|
|
144
|
+
"""
|
|
145
|
+
url = f"{self.baseurl}/switchSite"
|
|
92
146
|
switch_req = RequestFactory.Auth.switch_req(site_item.content_url)
|
|
93
147
|
try:
|
|
94
148
|
server_response = self.post_request(url, switch_req)
|
|
@@ -104,11 +158,14 @@ class Auth(Endpoint):
|
|
|
104
158
|
user_id = parsed_response.find(".//t:user", namespaces=self.parent_srv.namespace).get("id", None)
|
|
105
159
|
auth_token = parsed_response.find("t:credentials", namespaces=self.parent_srv.namespace).get("token", None)
|
|
106
160
|
self.parent_srv._set_auth(site_id, user_id, auth_token)
|
|
107
|
-
logger.info("Signed into {
|
|
161
|
+
logger.info(f"Signed into {self.parent_srv.server_address} as user with id {user_id}")
|
|
108
162
|
return Auth.contextmgr(self.sign_out)
|
|
109
163
|
|
|
110
164
|
@api(version="3.10")
|
|
111
165
|
def revoke_all_server_admin_tokens(self) -> None:
|
|
112
|
-
|
|
166
|
+
"""
|
|
167
|
+
Revokes all personal access tokens for all server admins on the server.
|
|
168
|
+
"""
|
|
169
|
+
url = f"{self.baseurl}/revokeAllServerAdminTokens"
|
|
113
170
|
self.post_request(url, "")
|
|
114
171
|
logger.info("Revoked all tokens for all server admins")
|
|
@@ -1,10 +1,23 @@
|
|
|
1
|
+
import io
|
|
1
2
|
import logging
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
from
|
|
3
|
+
import os
|
|
4
|
+
from contextlib import closing
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional, Union
|
|
7
|
+
from collections.abc import Iterator
|
|
8
|
+
|
|
9
|
+
from tableauserverclient.config import BYTES_PER_MB, config
|
|
10
|
+
from tableauserverclient.filesys_helpers import get_file_object_size
|
|
11
|
+
from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api
|
|
12
|
+
from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError
|
|
6
13
|
from tableauserverclient.models import CustomViewItem, PaginationItem
|
|
7
|
-
from tableauserverclient.server import
|
|
14
|
+
from tableauserverclient.server import (
|
|
15
|
+
RequestFactory,
|
|
16
|
+
RequestOptions,
|
|
17
|
+
ImageRequestOptions,
|
|
18
|
+
PDFRequestOptions,
|
|
19
|
+
CSVRequestOptions,
|
|
20
|
+
)
|
|
8
21
|
|
|
9
22
|
from tableauserverclient.helpers.logging import logger
|
|
10
23
|
|
|
@@ -16,14 +29,27 @@ Delete a custom view
|
|
|
16
29
|
update the name or owner of a custom view.
|
|
17
30
|
"""
|
|
18
31
|
|
|
32
|
+
FilePath = Union[str, os.PathLike]
|
|
33
|
+
FileObject = Union[io.BufferedReader, io.BytesIO]
|
|
34
|
+
FileObjectR = Union[io.BufferedReader, io.BytesIO]
|
|
35
|
+
FileObjectW = Union[io.BufferedWriter, io.BytesIO]
|
|
36
|
+
PathOrFileR = Union[FilePath, FileObjectR]
|
|
37
|
+
PathOrFileW = Union[FilePath, FileObjectW]
|
|
38
|
+
io_types_r = (io.BufferedReader, io.BytesIO)
|
|
39
|
+
io_types_w = (io.BufferedWriter, io.BytesIO)
|
|
40
|
+
|
|
19
41
|
|
|
20
42
|
class CustomViews(QuerysetEndpoint[CustomViewItem]):
|
|
21
43
|
def __init__(self, parent_srv):
|
|
22
|
-
super(
|
|
44
|
+
super().__init__(parent_srv)
|
|
23
45
|
|
|
24
46
|
@property
|
|
25
47
|
def baseurl(self) -> str:
|
|
26
|
-
return "{
|
|
48
|
+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/customviews"
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def expurl(self) -> str:
|
|
52
|
+
return f"{self.parent_srv._server_address}/api/exp/sites/{self.parent_srv.site_id}/customviews"
|
|
27
53
|
|
|
28
54
|
"""
|
|
29
55
|
If the request has no filter parameters: Administrators will see all custom views.
|
|
@@ -37,7 +63,7 @@ class CustomViews(QuerysetEndpoint[CustomViewItem]):
|
|
|
37
63
|
"""
|
|
38
64
|
|
|
39
65
|
@api(version="3.18")
|
|
40
|
-
def get(self, req_options: Optional["RequestOptions"] = None) ->
|
|
66
|
+
def get(self, req_options: Optional["RequestOptions"] = None) -> tuple[list[CustomViewItem], PaginationItem]:
|
|
41
67
|
logger.info("Querying all custom views on site")
|
|
42
68
|
url = self.baseurl
|
|
43
69
|
server_response = self.get_request(url, req_options)
|
|
@@ -50,8 +76,8 @@ class CustomViews(QuerysetEndpoint[CustomViewItem]):
|
|
|
50
76
|
if not view_id:
|
|
51
77
|
error = "Custom view item missing ID."
|
|
52
78
|
raise MissingRequiredFieldError(error)
|
|
53
|
-
logger.info("Querying custom view (ID: {
|
|
54
|
-
url = "{
|
|
79
|
+
logger.info(f"Querying custom view (ID: {view_id})")
|
|
80
|
+
url = f"{self.baseurl}/{view_id}"
|
|
55
81
|
server_response = self.get_request(url)
|
|
56
82
|
return CustomViewItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
57
83
|
|
|
@@ -65,17 +91,53 @@ class CustomViews(QuerysetEndpoint[CustomViewItem]):
|
|
|
65
91
|
return self._get_view_image(view_item, req_options)
|
|
66
92
|
|
|
67
93
|
view_item._set_image(image_fetcher)
|
|
68
|
-
logger.info("Populated image for custom view (ID: {
|
|
94
|
+
logger.info(f"Populated image for custom view (ID: {view_item.id})")
|
|
69
95
|
|
|
70
96
|
def _get_view_image(self, view_item: CustomViewItem, req_options: Optional["ImageRequestOptions"]) -> bytes:
|
|
71
|
-
url = "{
|
|
97
|
+
url = f"{self.baseurl}/{view_item.id}/image"
|
|
72
98
|
server_response = self.get_request(url, req_options)
|
|
73
99
|
image = server_response.content
|
|
74
100
|
return image
|
|
75
101
|
|
|
76
|
-
""
|
|
77
|
-
|
|
78
|
-
|
|
102
|
+
@api(version="3.23")
|
|
103
|
+
def populate_pdf(self, custom_view_item: CustomViewItem, req_options: Optional["PDFRequestOptions"] = None) -> None:
|
|
104
|
+
if not custom_view_item.id:
|
|
105
|
+
error = "Custom View item missing ID."
|
|
106
|
+
raise MissingRequiredFieldError(error)
|
|
107
|
+
|
|
108
|
+
def pdf_fetcher():
|
|
109
|
+
return self._get_custom_view_pdf(custom_view_item, req_options)
|
|
110
|
+
|
|
111
|
+
custom_view_item._set_pdf(pdf_fetcher)
|
|
112
|
+
logger.info(f"Populated pdf for custom view (ID: {custom_view_item.id})")
|
|
113
|
+
|
|
114
|
+
def _get_custom_view_pdf(
|
|
115
|
+
self, custom_view_item: CustomViewItem, req_options: Optional["PDFRequestOptions"]
|
|
116
|
+
) -> bytes:
|
|
117
|
+
url = f"{self.baseurl}/{custom_view_item.id}/pdf"
|
|
118
|
+
server_response = self.get_request(url, req_options)
|
|
119
|
+
pdf = server_response.content
|
|
120
|
+
return pdf
|
|
121
|
+
|
|
122
|
+
@api(version="3.23")
|
|
123
|
+
def populate_csv(self, custom_view_item: CustomViewItem, req_options: Optional["CSVRequestOptions"] = None) -> None:
|
|
124
|
+
if not custom_view_item.id:
|
|
125
|
+
error = "Custom View item missing ID."
|
|
126
|
+
raise MissingRequiredFieldError(error)
|
|
127
|
+
|
|
128
|
+
def csv_fetcher():
|
|
129
|
+
return self._get_custom_view_csv(custom_view_item, req_options)
|
|
130
|
+
|
|
131
|
+
custom_view_item._set_csv(csv_fetcher)
|
|
132
|
+
logger.info(f"Populated csv for custom view (ID: {custom_view_item.id})")
|
|
133
|
+
|
|
134
|
+
def _get_custom_view_csv(
|
|
135
|
+
self, custom_view_item: CustomViewItem, req_options: Optional["CSVRequestOptions"]
|
|
136
|
+
) -> Iterator[bytes]:
|
|
137
|
+
url = f"{self.baseurl}/{custom_view_item.id}/data"
|
|
138
|
+
|
|
139
|
+
with closing(self.get_request(url, request_object=req_options, parameters={"stream": True})) as server_response:
|
|
140
|
+
yield from server_response.iter_content(1024)
|
|
79
141
|
|
|
80
142
|
@api(version="3.18")
|
|
81
143
|
def update(self, view_item: CustomViewItem) -> Optional[CustomViewItem]:
|
|
@@ -87,10 +149,10 @@ class CustomViews(QuerysetEndpoint[CustomViewItem]):
|
|
|
87
149
|
return view_item
|
|
88
150
|
|
|
89
151
|
# Update the custom view owner or name
|
|
90
|
-
url = "{
|
|
152
|
+
url = f"{self.baseurl}/{view_item.id}"
|
|
91
153
|
update_req = RequestFactory.CustomView.update_req(view_item)
|
|
92
154
|
server_response = self.put_request(url, update_req)
|
|
93
|
-
logger.info("Updated custom view (ID: {
|
|
155
|
+
logger.info(f"Updated custom view (ID: {view_item.id})")
|
|
94
156
|
return CustomViewItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
95
157
|
|
|
96
158
|
# Delete 1 view by id
|
|
@@ -99,6 +161,49 @@ class CustomViews(QuerysetEndpoint[CustomViewItem]):
|
|
|
99
161
|
if not view_id:
|
|
100
162
|
error = "Custom View ID undefined."
|
|
101
163
|
raise ValueError(error)
|
|
102
|
-
url = "{
|
|
164
|
+
url = f"{self.baseurl}/{view_id}"
|
|
103
165
|
self.delete_request(url)
|
|
104
|
-
logger.info("Deleted single custom view (ID: {
|
|
166
|
+
logger.info(f"Deleted single custom view (ID: {view_id})")
|
|
167
|
+
|
|
168
|
+
@api(version="3.21")
|
|
169
|
+
def download(self, view_item: CustomViewItem, file: PathOrFileW) -> PathOrFileW:
|
|
170
|
+
url = f"{self.expurl}/{view_item.id}/content"
|
|
171
|
+
server_response = self.get_request(url)
|
|
172
|
+
if isinstance(file, io_types_w):
|
|
173
|
+
file.write(server_response.content)
|
|
174
|
+
return file
|
|
175
|
+
|
|
176
|
+
with open(file, "wb") as f:
|
|
177
|
+
f.write(server_response.content)
|
|
178
|
+
|
|
179
|
+
return file
|
|
180
|
+
|
|
181
|
+
@api(version="3.21")
|
|
182
|
+
def publish(self, view_item: CustomViewItem, file: PathOrFileR) -> Optional[CustomViewItem]:
|
|
183
|
+
url = self.expurl
|
|
184
|
+
if isinstance(file, io_types_r):
|
|
185
|
+
size = get_file_object_size(file)
|
|
186
|
+
elif isinstance(file, (str, Path)) and (p := Path(file)).is_file():
|
|
187
|
+
size = p.stat().st_size
|
|
188
|
+
else:
|
|
189
|
+
raise ValueError("File path or file object required for publishing custom view.")
|
|
190
|
+
|
|
191
|
+
if size >= config.FILESIZE_LIMIT_MB * BYTES_PER_MB:
|
|
192
|
+
upload_session_id = self.parent_srv.fileuploads.upload(file)
|
|
193
|
+
url = f"{url}?uploadSessionId={upload_session_id}"
|
|
194
|
+
xml_request, content_type = RequestFactory.CustomView.publish_req_chunked(view_item)
|
|
195
|
+
else:
|
|
196
|
+
if isinstance(file, io_types_r):
|
|
197
|
+
file.seek(0)
|
|
198
|
+
contents = file.read()
|
|
199
|
+
if view_item.name is None:
|
|
200
|
+
raise MissingRequiredFieldError("Custom view item missing name.")
|
|
201
|
+
filename = view_item.name
|
|
202
|
+
elif isinstance(file, (str, Path)):
|
|
203
|
+
filename = Path(file).name
|
|
204
|
+
contents = Path(file).read_bytes()
|
|
205
|
+
|
|
206
|
+
xml_request, content_type = RequestFactory.CustomView.publish_req(view_item, filename, contents)
|
|
207
|
+
|
|
208
|
+
server_response = self.post_request(url, xml_request, content_type)
|
|
209
|
+
return CustomViewItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
@@ -10,14 +10,14 @@ from tableauserverclient.helpers.logging import logger
|
|
|
10
10
|
|
|
11
11
|
class DataAccelerationReport(Endpoint):
|
|
12
12
|
def __init__(self, parent_srv):
|
|
13
|
-
super(
|
|
13
|
+
super().__init__(parent_srv)
|
|
14
14
|
|
|
15
15
|
self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
|
|
16
16
|
self._default_permissions = _DefaultPermissionsEndpoint(parent_srv, lambda: self.baseurl)
|
|
17
17
|
|
|
18
18
|
@property
|
|
19
19
|
def baseurl(self):
|
|
20
|
-
return "{
|
|
20
|
+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/dataAccelerationReport"
|
|
21
21
|
|
|
22
22
|
@api(version="3.8")
|
|
23
23
|
def get(self, req_options=None):
|
|
@@ -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,19 +1,22 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
|
|
3
|
-
from .
|
|
4
|
-
|
|
5
|
-
from .endpoint import
|
|
6
|
-
from .
|
|
7
|
-
from .
|
|
2
|
+
from typing import Union
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
|
|
5
|
+
from tableauserverclient.server.endpoint.default_permissions_endpoint import _DefaultPermissionsEndpoint
|
|
6
|
+
from tableauserverclient.server.endpoint.dqw_endpoint import _DataQualityWarningEndpoint
|
|
7
|
+
from tableauserverclient.server.endpoint.endpoint import api, Endpoint
|
|
8
|
+
from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError
|
|
9
|
+
from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
|
|
10
|
+
from tableauserverclient.server.endpoint.resource_tagger import TaggingMixin
|
|
8
11
|
from tableauserverclient.server import RequestFactory
|
|
9
12
|
from tableauserverclient.models import DatabaseItem, TableItem, PaginationItem, Resource
|
|
10
13
|
|
|
11
14
|
from tableauserverclient.helpers.logging import logger
|
|
12
15
|
|
|
13
16
|
|
|
14
|
-
class Databases(Endpoint):
|
|
17
|
+
class Databases(Endpoint, TaggingMixin):
|
|
15
18
|
def __init__(self, parent_srv):
|
|
16
|
-
super(
|
|
19
|
+
super().__init__(parent_srv)
|
|
17
20
|
|
|
18
21
|
self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
|
|
19
22
|
self._default_permissions = _DefaultPermissionsEndpoint(parent_srv, lambda: self.baseurl)
|
|
@@ -21,7 +24,7 @@ class Databases(Endpoint):
|
|
|
21
24
|
|
|
22
25
|
@property
|
|
23
26
|
def baseurl(self):
|
|
24
|
-
return "{
|
|
27
|
+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/databases"
|
|
25
28
|
|
|
26
29
|
@api(version="3.5")
|
|
27
30
|
def get(self, req_options=None):
|
|
@@ -38,8 +41,8 @@ class Databases(Endpoint):
|
|
|
38
41
|
if not database_id:
|
|
39
42
|
error = "database ID undefined."
|
|
40
43
|
raise ValueError(error)
|
|
41
|
-
logger.info("Querying single database (ID: {
|
|
42
|
-
url = "{
|
|
44
|
+
logger.info(f"Querying single database (ID: {database_id})")
|
|
45
|
+
url = f"{self.baseurl}/{database_id}"
|
|
43
46
|
server_response = self.get_request(url)
|
|
44
47
|
return DatabaseItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
45
48
|
|
|
@@ -48,9 +51,9 @@ class Databases(Endpoint):
|
|
|
48
51
|
if not database_id:
|
|
49
52
|
error = "Database ID undefined."
|
|
50
53
|
raise ValueError(error)
|
|
51
|
-
url = "{
|
|
54
|
+
url = f"{self.baseurl}/{database_id}"
|
|
52
55
|
self.delete_request(url)
|
|
53
|
-
logger.info("Deleted single database (ID: {
|
|
56
|
+
logger.info(f"Deleted single database (ID: {database_id})")
|
|
54
57
|
|
|
55
58
|
@api(version="3.5")
|
|
56
59
|
def update(self, database_item):
|
|
@@ -58,10 +61,10 @@ class Databases(Endpoint):
|
|
|
58
61
|
error = "Database item missing ID."
|
|
59
62
|
raise MissingRequiredFieldError(error)
|
|
60
63
|
|
|
61
|
-
url = "{
|
|
64
|
+
url = f"{self.baseurl}/{database_item.id}"
|
|
62
65
|
update_req = RequestFactory.Database.update_req(database_item)
|
|
63
66
|
server_response = self.put_request(url, update_req)
|
|
64
|
-
logger.info("Updated database item (ID: {
|
|
67
|
+
logger.info(f"Updated database item (ID: {database_item.id})")
|
|
65
68
|
updated_database = DatabaseItem.from_response(server_response.content, self.parent_srv.namespace)[0]
|
|
66
69
|
return updated_database
|
|
67
70
|
|
|
@@ -76,10 +79,10 @@ class Databases(Endpoint):
|
|
|
76
79
|
return self._get_tables_for_database(database_item)
|
|
77
80
|
|
|
78
81
|
database_item._set_tables(column_fetcher)
|
|
79
|
-
logger.info("Populated tables for database (ID: {
|
|
82
|
+
logger.info(f"Populated tables for database (ID: {database_item.id}")
|
|
80
83
|
|
|
81
84
|
def _get_tables_for_database(self, database_item):
|
|
82
|
-
url = "{
|
|
85
|
+
url = f"{self.baseurl}/{database_item.id}/tables"
|
|
83
86
|
server_response = self.get_request(url)
|
|
84
87
|
tables = TableItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
85
88
|
return tables
|
|
@@ -123,3 +126,15 @@ class Databases(Endpoint):
|
|
|
123
126
|
@api(version="3.5")
|
|
124
127
|
def delete_dqw(self, item):
|
|
125
128
|
self._data_quality_warnings.clear(item)
|
|
129
|
+
|
|
130
|
+
@api(version="3.9")
|
|
131
|
+
def add_tags(self, item: Union[DatabaseItem, str], tags: Iterable[str]) -> set[str]:
|
|
132
|
+
return super().add_tags(item, tags)
|
|
133
|
+
|
|
134
|
+
@api(version="3.9")
|
|
135
|
+
def delete_tags(self, item: Union[DatabaseItem, str], tags: Iterable[str]) -> None:
|
|
136
|
+
super().delete_tags(item, tags)
|
|
137
|
+
|
|
138
|
+
@api(version="3.9")
|
|
139
|
+
def update_tags(self, item: DatabaseItem) -> None:
|
|
140
|
+
raise NotImplementedError("Update tags is not supported for databases.")
|