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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import datetime as dt
|
|
2
2
|
import json
|
|
3
|
-
from typing import Callable,
|
|
3
|
+
from typing import Callable, Optional
|
|
4
|
+
from collections.abc import Iterable
|
|
4
5
|
from xml.etree.ElementTree import Element
|
|
5
6
|
|
|
6
7
|
from defusedxml.ElementTree import fromstring
|
|
@@ -23,7 +24,7 @@ class VirtualConnectionItem:
|
|
|
23
24
|
self._connections: Optional[Callable[[], Iterable[ConnectionItem]]] = None
|
|
24
25
|
self.project_id: Optional[str] = None
|
|
25
26
|
self.owner_id: Optional[str] = None
|
|
26
|
-
self.content: Optional[
|
|
27
|
+
self.content: Optional[dict[str, dict]] = None
|
|
27
28
|
self.certification_note: Optional[str] = None
|
|
28
29
|
|
|
29
30
|
def __str__(self) -> str:
|
|
@@ -40,7 +41,7 @@ class VirtualConnectionItem:
|
|
|
40
41
|
return self._id
|
|
41
42
|
|
|
42
43
|
@property
|
|
43
|
-
def permissions(self) ->
|
|
44
|
+
def permissions(self) -> list[PermissionsRule]:
|
|
44
45
|
if self._permissions is None:
|
|
45
46
|
error = "Workbook item must be populated with permissions first."
|
|
46
47
|
raise UnpopulatedPropertyError(error)
|
|
@@ -53,12 +54,12 @@ class VirtualConnectionItem:
|
|
|
53
54
|
return self._connections()
|
|
54
55
|
|
|
55
56
|
@classmethod
|
|
56
|
-
def from_response(cls, response: bytes, ns:
|
|
57
|
+
def from_response(cls, response: bytes, ns: dict[str, str]) -> list["VirtualConnectionItem"]:
|
|
57
58
|
parsed_response = fromstring(response)
|
|
58
59
|
return [cls.from_xml(xml, ns) for xml in parsed_response.findall(".//t:virtualConnection[@name]", ns)]
|
|
59
60
|
|
|
60
61
|
@classmethod
|
|
61
|
-
def from_xml(cls, xml: Element, ns:
|
|
62
|
+
def from_xml(cls, xml: Element, ns: dict[str, str]) -> "VirtualConnectionItem":
|
|
62
63
|
v_conn = cls(xml.get("name", ""))
|
|
63
64
|
v_conn._id = xml.get("id", None)
|
|
64
65
|
v_conn.webpage_url = xml.get("webpageUrl", None)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import re
|
|
2
2
|
import xml.etree.ElementTree as ET
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Optional
|
|
4
4
|
|
|
5
5
|
from defusedxml.ElementTree import fromstring
|
|
6
6
|
|
|
@@ -13,7 +13,7 @@ def _parse_event(events):
|
|
|
13
13
|
return NAMESPACE_RE.sub("", event.tag)
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class WebhookItem
|
|
16
|
+
class WebhookItem:
|
|
17
17
|
def __init__(self):
|
|
18
18
|
self._id: Optional[str] = None
|
|
19
19
|
self.name: Optional[str] = None
|
|
@@ -45,10 +45,10 @@ class WebhookItem(object):
|
|
|
45
45
|
|
|
46
46
|
@event.setter
|
|
47
47
|
def event(self, value: str) -> None:
|
|
48
|
-
self._event = "webhook-source-event-{}"
|
|
48
|
+
self._event = f"webhook-source-event-{value}"
|
|
49
49
|
|
|
50
50
|
@classmethod
|
|
51
|
-
def from_response(cls:
|
|
51
|
+
def from_response(cls: type["WebhookItem"], resp: bytes, ns) -> list["WebhookItem"]:
|
|
52
52
|
all_webhooks_items = list()
|
|
53
53
|
parsed_response = fromstring(resp)
|
|
54
54
|
all_webhooks_xml = parsed_response.findall(".//t:webhook", namespaces=ns)
|
|
@@ -61,7 +61,7 @@ class WebhookItem(object):
|
|
|
61
61
|
return all_webhooks_items
|
|
62
62
|
|
|
63
63
|
@staticmethod
|
|
64
|
-
def _parse_element(webhook_xml: ET.Element, ns) ->
|
|
64
|
+
def _parse_element(webhook_xml: ET.Element, ns) -> tuple:
|
|
65
65
|
id = webhook_xml.get("id", None)
|
|
66
66
|
name = webhook_xml.get("name", None)
|
|
67
67
|
|
|
@@ -82,4 +82,4 @@ class WebhookItem(object):
|
|
|
82
82
|
return id, name, url, event, owner_id
|
|
83
83
|
|
|
84
84
|
def __repr__(self) -> str:
|
|
85
|
-
return "<Webhook id={} name={} url={} event={}>"
|
|
85
|
+
return f"<Webhook id={self.id} name={self.name} url={self.url} event={self.event}>"
|
|
@@ -2,7 +2,7 @@ import copy
|
|
|
2
2
|
import datetime
|
|
3
3
|
import uuid
|
|
4
4
|
import xml.etree.ElementTree as ET
|
|
5
|
-
from typing import Callable,
|
|
5
|
+
from typing import Callable, Optional
|
|
6
6
|
|
|
7
7
|
from defusedxml.ElementTree import fromstring
|
|
8
8
|
|
|
@@ -20,7 +20,85 @@ from .view_item import ViewItem
|
|
|
20
20
|
from .data_freshness_policy_item import DataFreshnessPolicyItem
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
class WorkbookItem
|
|
23
|
+
class WorkbookItem:
|
|
24
|
+
"""
|
|
25
|
+
The workbook resources for Tableau are defined in the WorkbookItem class.
|
|
26
|
+
The class corresponds to the workbook resources you can access using the
|
|
27
|
+
Tableau REST API. Some workbook methods take an instance of the WorkbookItem
|
|
28
|
+
class as arguments. The workbook item specifies the project.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
project_id : Optional[str], optional
|
|
33
|
+
The project ID for the workbook, by default None.
|
|
34
|
+
|
|
35
|
+
name : Optional[str], optional
|
|
36
|
+
The name of the workbook, by default None.
|
|
37
|
+
|
|
38
|
+
show_tabs : bool, optional
|
|
39
|
+
Determines whether the workbook shows tabs for the view.
|
|
40
|
+
|
|
41
|
+
Attributes
|
|
42
|
+
----------
|
|
43
|
+
connections : list[ConnectionItem]
|
|
44
|
+
The list of data connections (ConnectionItem) for the data sources used
|
|
45
|
+
by the workbook. You must first call the workbooks.populate_connections
|
|
46
|
+
method to access this data. See the ConnectionItem class.
|
|
47
|
+
|
|
48
|
+
content_url : Optional[str]
|
|
49
|
+
The name of the workbook as it appears in the URL.
|
|
50
|
+
|
|
51
|
+
created_at : Optional[datetime.datetime]
|
|
52
|
+
The date and time the workbook was created.
|
|
53
|
+
|
|
54
|
+
description : Optional[str]
|
|
55
|
+
User-defined description of the workbook.
|
|
56
|
+
|
|
57
|
+
id : Optional[str]
|
|
58
|
+
The identifier for the workbook. You need this value to query a specific
|
|
59
|
+
workbook or to delete a workbook with the get_by_id and delete methods.
|
|
60
|
+
|
|
61
|
+
owner_id : Optional[str]
|
|
62
|
+
The identifier for the owner (UserItem) of the workbook.
|
|
63
|
+
|
|
64
|
+
preview_image : bytes
|
|
65
|
+
The thumbnail image for the view. You must first call the
|
|
66
|
+
workbooks.populate_preview_image method to access this data.
|
|
67
|
+
|
|
68
|
+
project_name : Optional[str]
|
|
69
|
+
The name of the project that contains the workbook.
|
|
70
|
+
|
|
71
|
+
size: int
|
|
72
|
+
The size of the workbook in megabytes.
|
|
73
|
+
|
|
74
|
+
hidden_views: Optional[list[str]]
|
|
75
|
+
List of string names of views that need to be hidden when the workbook
|
|
76
|
+
is published.
|
|
77
|
+
|
|
78
|
+
tags: set[str]
|
|
79
|
+
The set of tags associated with the workbook.
|
|
80
|
+
|
|
81
|
+
updated_at : Optional[datetime.datetime]
|
|
82
|
+
The date and time the workbook was last updated.
|
|
83
|
+
|
|
84
|
+
views : list[ViewItem]
|
|
85
|
+
The list of views (ViewItem) for the workbook. You must first call the
|
|
86
|
+
workbooks.populate_views method to access this data. See the ViewItem
|
|
87
|
+
class.
|
|
88
|
+
|
|
89
|
+
web_page_url : Optional[str]
|
|
90
|
+
The full URL for the workbook.
|
|
91
|
+
|
|
92
|
+
Examples
|
|
93
|
+
--------
|
|
94
|
+
# creating a new instance of a WorkbookItem
|
|
95
|
+
>>> import tableauserverclient as TSC
|
|
96
|
+
|
|
97
|
+
>>> # Create new workbook_item with project id '3a8b6148-493c-11e6-a621-6f3499394a39'
|
|
98
|
+
|
|
99
|
+
>>> new_workbook = TSC.WorkbookItem('3a8b6148-493c-11e6-a621-6f3499394a39')
|
|
100
|
+
"""
|
|
101
|
+
|
|
24
102
|
def __init__(self, project_id: Optional[str] = None, name: Optional[str] = None, show_tabs: bool = False) -> None:
|
|
25
103
|
self._connections = None
|
|
26
104
|
self._content_url = None
|
|
@@ -35,15 +113,15 @@ class WorkbookItem(object):
|
|
|
35
113
|
self._revisions = None
|
|
36
114
|
self._size = None
|
|
37
115
|
self._updated_at = None
|
|
38
|
-
self._views: Optional[Callable[[],
|
|
116
|
+
self._views: Optional[Callable[[], list[ViewItem]]] = None
|
|
39
117
|
self.name = name
|
|
40
118
|
self._description = None
|
|
41
119
|
self.owner_id: Optional[str] = None
|
|
42
120
|
# workaround for Personal Space workbooks without a project
|
|
43
121
|
self.project_id: Optional[str] = project_id or uuid.uuid4().__str__()
|
|
44
122
|
self.show_tabs = show_tabs
|
|
45
|
-
self.hidden_views: Optional[
|
|
46
|
-
self.tags:
|
|
123
|
+
self.hidden_views: Optional[list[str]] = None
|
|
124
|
+
self.tags: set[str] = set()
|
|
47
125
|
self.data_acceleration_config = {
|
|
48
126
|
"acceleration_enabled": None,
|
|
49
127
|
"accelerate_now": None,
|
|
@@ -56,7 +134,7 @@ class WorkbookItem(object):
|
|
|
56
134
|
return None
|
|
57
135
|
|
|
58
136
|
def __str__(self):
|
|
59
|
-
return "<WorkbookItem {
|
|
137
|
+
return "<WorkbookItem {} '{}' contentUrl='{}' project={}>".format(
|
|
60
138
|
self._id, self.name, self.content_url, self.project_id
|
|
61
139
|
)
|
|
62
140
|
|
|
@@ -64,14 +142,14 @@ class WorkbookItem(object):
|
|
|
64
142
|
return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
|
|
65
143
|
|
|
66
144
|
@property
|
|
67
|
-
def connections(self) ->
|
|
145
|
+
def connections(self) -> list[ConnectionItem]:
|
|
68
146
|
if self._connections is None:
|
|
69
147
|
error = "Workbook item must be populated with connections first."
|
|
70
148
|
raise UnpopulatedPropertyError(error)
|
|
71
149
|
return self._connections()
|
|
72
150
|
|
|
73
151
|
@property
|
|
74
|
-
def permissions(self) ->
|
|
152
|
+
def permissions(self) -> list[PermissionsRule]:
|
|
75
153
|
if self._permissions is None:
|
|
76
154
|
error = "Workbook item must be populated with permissions first."
|
|
77
155
|
raise UnpopulatedPropertyError(error)
|
|
@@ -152,7 +230,7 @@ class WorkbookItem(object):
|
|
|
152
230
|
return self._updated_at
|
|
153
231
|
|
|
154
232
|
@property
|
|
155
|
-
def views(self) ->
|
|
233
|
+
def views(self) -> list[ViewItem]:
|
|
156
234
|
# Views can be set in an initial workbook response OR by a call
|
|
157
235
|
# to Server. Without getting too fancy, I think we can rely on
|
|
158
236
|
# returning a list from the response, until they call
|
|
@@ -191,7 +269,7 @@ class WorkbookItem(object):
|
|
|
191
269
|
self._data_freshness_policy = value
|
|
192
270
|
|
|
193
271
|
@property
|
|
194
|
-
def revisions(self) ->
|
|
272
|
+
def revisions(self) -> list[RevisionItem]:
|
|
195
273
|
if self._revisions is None:
|
|
196
274
|
error = "Workbook item must be populated with revisions first."
|
|
197
275
|
raise UnpopulatedPropertyError(error)
|
|
@@ -203,7 +281,7 @@ class WorkbookItem(object):
|
|
|
203
281
|
def _set_permissions(self, permissions):
|
|
204
282
|
self._permissions = permissions
|
|
205
283
|
|
|
206
|
-
def _set_views(self, views: Callable[[],
|
|
284
|
+
def _set_views(self, views: Callable[[], list[ViewItem]]) -> None:
|
|
207
285
|
self._views = views
|
|
208
286
|
|
|
209
287
|
def _set_pdf(self, pdf: Callable[[], bytes]) -> None:
|
|
@@ -316,7 +394,7 @@ class WorkbookItem(object):
|
|
|
316
394
|
self.data_freshness_policy = data_freshness_policy
|
|
317
395
|
|
|
318
396
|
@classmethod
|
|
319
|
-
def from_response(cls, resp: str, ns:
|
|
397
|
+
def from_response(cls, resp: str, ns: dict[str, str]) -> list["WorkbookItem"]:
|
|
320
398
|
all_workbook_items = list()
|
|
321
399
|
parsed_response = fromstring(resp)
|
|
322
400
|
all_workbook_xml = parsed_response.findall(".//t:workbook", namespaces=ns)
|
tableauserverclient/namespace.py
CHANGED
|
@@ -11,7 +11,7 @@ from tableauserverclient.server.filter import Filter
|
|
|
11
11
|
from tableauserverclient.server.sort import Sort
|
|
12
12
|
from tableauserverclient.server.server import Server
|
|
13
13
|
from tableauserverclient.server.pager import Pager
|
|
14
|
-
from tableauserverclient.server.endpoint.exceptions import NotSignedInError
|
|
14
|
+
from tableauserverclient.server.endpoint.exceptions import FailedSignInError, NotSignedInError
|
|
15
15
|
|
|
16
16
|
from tableauserverclient.server.endpoint import (
|
|
17
17
|
Auth,
|
|
@@ -57,6 +57,7 @@ __all__ = [
|
|
|
57
57
|
"Sort",
|
|
58
58
|
"Server",
|
|
59
59
|
"Pager",
|
|
60
|
+
"FailedSignInError",
|
|
60
61
|
"NotSignedInError",
|
|
61
62
|
"Auth",
|
|
62
63
|
"CustomViews",
|
|
@@ -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,15 +1,23 @@
|
|
|
1
1
|
import io
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
+
from contextlib import closing
|
|
4
5
|
from pathlib import Path
|
|
5
|
-
from typing import
|
|
6
|
+
from typing import Optional, Union
|
|
7
|
+
from collections.abc import Iterator
|
|
6
8
|
|
|
7
|
-
from tableauserverclient.config import BYTES_PER_MB,
|
|
9
|
+
from tableauserverclient.config import BYTES_PER_MB, config
|
|
8
10
|
from tableauserverclient.filesys_helpers import get_file_object_size
|
|
9
11
|
from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api
|
|
10
12
|
from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError
|
|
11
13
|
from tableauserverclient.models import CustomViewItem, PaginationItem
|
|
12
|
-
from tableauserverclient.server import
|
|
14
|
+
from tableauserverclient.server import (
|
|
15
|
+
RequestFactory,
|
|
16
|
+
RequestOptions,
|
|
17
|
+
ImageRequestOptions,
|
|
18
|
+
PDFRequestOptions,
|
|
19
|
+
CSVRequestOptions,
|
|
20
|
+
)
|
|
13
21
|
|
|
14
22
|
from tableauserverclient.helpers.logging import logger
|
|
15
23
|
|
|
@@ -33,11 +41,11 @@ io_types_w = (io.BufferedWriter, io.BytesIO)
|
|
|
33
41
|
|
|
34
42
|
class CustomViews(QuerysetEndpoint[CustomViewItem]):
|
|
35
43
|
def __init__(self, parent_srv):
|
|
36
|
-
super(
|
|
44
|
+
super().__init__(parent_srv)
|
|
37
45
|
|
|
38
46
|
@property
|
|
39
47
|
def baseurl(self) -> str:
|
|
40
|
-
return "{
|
|
48
|
+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/customviews"
|
|
41
49
|
|
|
42
50
|
@property
|
|
43
51
|
def expurl(self) -> str:
|
|
@@ -55,7 +63,7 @@ class CustomViews(QuerysetEndpoint[CustomViewItem]):
|
|
|
55
63
|
"""
|
|
56
64
|
|
|
57
65
|
@api(version="3.18")
|
|
58
|
-
def get(self, req_options: Optional["RequestOptions"] = None) ->
|
|
66
|
+
def get(self, req_options: Optional["RequestOptions"] = None) -> tuple[list[CustomViewItem], PaginationItem]:
|
|
59
67
|
logger.info("Querying all custom views on site")
|
|
60
68
|
url = self.baseurl
|
|
61
69
|
server_response = self.get_request(url, req_options)
|
|
@@ -68,8 +76,8 @@ class CustomViews(QuerysetEndpoint[CustomViewItem]):
|
|
|
68
76
|
if not view_id:
|
|
69
77
|
error = "Custom view item missing ID."
|
|
70
78
|
raise MissingRequiredFieldError(error)
|
|
71
|
-
logger.info("Querying custom view (ID: {
|
|
72
|
-
url = "{
|
|
79
|
+
logger.info(f"Querying custom view (ID: {view_id})")
|
|
80
|
+
url = f"{self.baseurl}/{view_id}"
|
|
73
81
|
server_response = self.get_request(url)
|
|
74
82
|
return CustomViewItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
75
83
|
|
|
@@ -83,17 +91,53 @@ class CustomViews(QuerysetEndpoint[CustomViewItem]):
|
|
|
83
91
|
return self._get_view_image(view_item, req_options)
|
|
84
92
|
|
|
85
93
|
view_item._set_image(image_fetcher)
|
|
86
|
-
logger.info("Populated image for custom view (ID: {
|
|
94
|
+
logger.info(f"Populated image for custom view (ID: {view_item.id})")
|
|
87
95
|
|
|
88
96
|
def _get_view_image(self, view_item: CustomViewItem, req_options: Optional["ImageRequestOptions"]) -> bytes:
|
|
89
|
-
url = "{
|
|
97
|
+
url = f"{self.baseurl}/{view_item.id}/image"
|
|
90
98
|
server_response = self.get_request(url, req_options)
|
|
91
99
|
image = server_response.content
|
|
92
100
|
return image
|
|
93
101
|
|
|
94
|
-
""
|
|
95
|
-
|
|
96
|
-
|
|
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)
|
|
97
141
|
|
|
98
142
|
@api(version="3.18")
|
|
99
143
|
def update(self, view_item: CustomViewItem) -> Optional[CustomViewItem]:
|
|
@@ -105,10 +149,10 @@ class CustomViews(QuerysetEndpoint[CustomViewItem]):
|
|
|
105
149
|
return view_item
|
|
106
150
|
|
|
107
151
|
# Update the custom view owner or name
|
|
108
|
-
url = "{
|
|
152
|
+
url = f"{self.baseurl}/{view_item.id}"
|
|
109
153
|
update_req = RequestFactory.CustomView.update_req(view_item)
|
|
110
154
|
server_response = self.put_request(url, update_req)
|
|
111
|
-
logger.info("Updated custom view (ID: {
|
|
155
|
+
logger.info(f"Updated custom view (ID: {view_item.id})")
|
|
112
156
|
return CustomViewItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
113
157
|
|
|
114
158
|
# Delete 1 view by id
|
|
@@ -117,9 +161,9 @@ class CustomViews(QuerysetEndpoint[CustomViewItem]):
|
|
|
117
161
|
if not view_id:
|
|
118
162
|
error = "Custom View ID undefined."
|
|
119
163
|
raise ValueError(error)
|
|
120
|
-
url = "{
|
|
164
|
+
url = f"{self.baseurl}/{view_id}"
|
|
121
165
|
self.delete_request(url)
|
|
122
|
-
logger.info("Deleted single custom view (ID: {
|
|
166
|
+
logger.info(f"Deleted single custom view (ID: {view_id})")
|
|
123
167
|
|
|
124
168
|
@api(version="3.21")
|
|
125
169
|
def download(self, view_item: CustomViewItem, file: PathOrFileW) -> PathOrFileW:
|
|
@@ -144,7 +188,7 @@ class CustomViews(QuerysetEndpoint[CustomViewItem]):
|
|
|
144
188
|
else:
|
|
145
189
|
raise ValueError("File path or file object required for publishing custom view.")
|
|
146
190
|
|
|
147
|
-
if size >= FILESIZE_LIMIT_MB * BYTES_PER_MB:
|
|
191
|
+
if size >= config.FILESIZE_LIMIT_MB * BYTES_PER_MB:
|
|
148
192
|
upload_session_id = self.parent_srv.fileuploads.upload(file)
|
|
149
193
|
url = f"{url}?uploadSessionId={upload_session_id}"
|
|
150
194
|
xml_request, content_type = RequestFactory.CustomView.publish_req_chunked(view_item)
|
|
@@ -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):
|