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
|
@@ -6,7 +6,29 @@ from defusedxml.ElementTree import fromstring
|
|
|
6
6
|
from tableauserverclient.helpers.logging import logger
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class ServerInfoItem
|
|
9
|
+
class ServerInfoItem:
|
|
10
|
+
"""
|
|
11
|
+
The ServerInfoItem class contains the build and version information for
|
|
12
|
+
Tableau Server. The server information is accessed with the
|
|
13
|
+
server_info.get() method, which returns an instance of the ServerInfo class.
|
|
14
|
+
|
|
15
|
+
Attributes
|
|
16
|
+
----------
|
|
17
|
+
product_version : str
|
|
18
|
+
Shows the version of the Tableau Server or Tableau Cloud
|
|
19
|
+
(for example, 10.2.0).
|
|
20
|
+
|
|
21
|
+
build_number : str
|
|
22
|
+
Shows the specific build number (for example, 10200.17.0329.1446).
|
|
23
|
+
|
|
24
|
+
rest_api_version : str
|
|
25
|
+
Shows the supported REST API version number. Note that this might be
|
|
26
|
+
different from the default value specified for the server, with the
|
|
27
|
+
Server.version attribute. To take advantage of new features, you should
|
|
28
|
+
query the server and set the Server.version to match the supported REST
|
|
29
|
+
API version number.
|
|
30
|
+
"""
|
|
31
|
+
|
|
10
32
|
def __init__(self, product_version, build_number, rest_api_version):
|
|
11
33
|
self._product_version = product_version
|
|
12
34
|
self._build_number = build_number
|
|
@@ -40,13 +62,11 @@ class ServerInfoItem(object):
|
|
|
40
62
|
try:
|
|
41
63
|
parsed_response = fromstring(resp)
|
|
42
64
|
except xml.etree.ElementTree.ParseError as error:
|
|
43
|
-
logger.
|
|
44
|
-
logger.info(error)
|
|
65
|
+
logger.exception(f"Unexpected response for ServerInfo: {resp}")
|
|
45
66
|
return cls("Unknown", "Unknown", "Unknown")
|
|
46
67
|
except Exception as error:
|
|
47
|
-
logger.
|
|
48
|
-
|
|
49
|
-
return cls("Unknown", "Unknown", "Unknown")
|
|
68
|
+
logger.exception(f"Unexpected response for ServerInfo: {resp}")
|
|
69
|
+
raise error
|
|
50
70
|
|
|
51
71
|
product_version_tag = parsed_response.find(".//t:productVersion", namespaces=ns)
|
|
52
72
|
rest_api_version_tag = parsed_response.find(".//t:restApiVersion", namespaces=ns)
|
|
@@ -14,13 +14,79 @@ from .property_decorators import (
|
|
|
14
14
|
|
|
15
15
|
VALID_CONTENT_URL_RE = r"^[a-zA-Z0-9_\-]*$"
|
|
16
16
|
|
|
17
|
-
from typing import
|
|
17
|
+
from typing import Optional, Union, TYPE_CHECKING
|
|
18
18
|
|
|
19
19
|
if TYPE_CHECKING:
|
|
20
20
|
from tableauserverclient.server import Server
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
class SiteItem
|
|
23
|
+
class SiteItem:
|
|
24
|
+
"""
|
|
25
|
+
The SiteItem class contains the members or attributes for the site resources
|
|
26
|
+
on Tableau Server or Tableau Cloud. The SiteItem class defines the
|
|
27
|
+
information you can request or query from Tableau Server or Tableau Cloud.
|
|
28
|
+
The class members correspond to the attributes of a server request or
|
|
29
|
+
response payload.
|
|
30
|
+
|
|
31
|
+
Attributes
|
|
32
|
+
----------
|
|
33
|
+
name: str
|
|
34
|
+
The name of the site. The name of the default site is "".
|
|
35
|
+
|
|
36
|
+
content_url: str
|
|
37
|
+
The path to the site.
|
|
38
|
+
|
|
39
|
+
admin_mode: str
|
|
40
|
+
(Optional) For Tableau Server only. Specify ContentAndUsers to allow
|
|
41
|
+
site administrators to use the server interface and tabcmd commands to
|
|
42
|
+
add and remove users. (Specifying this option does not give site
|
|
43
|
+
administrators permissions to manage users using the REST API.) Specify
|
|
44
|
+
ContentOnly to prevent site administrators from adding or removing
|
|
45
|
+
users. (Server administrators can always add or remove users.)
|
|
46
|
+
|
|
47
|
+
user_quota: int
|
|
48
|
+
(Optional) Specifies the total number of users for the site. The number
|
|
49
|
+
can't exceed the number of licenses activated for the site; and if
|
|
50
|
+
tiered capacity attributes are set, then user_quota will equal the sum
|
|
51
|
+
of the tiered capacity values, and attempting to set user_quota will
|
|
52
|
+
cause an error.
|
|
53
|
+
|
|
54
|
+
tier_explorer_capacity: int
|
|
55
|
+
tier_creator_capacity: int
|
|
56
|
+
tier_viewer_capacity: int
|
|
57
|
+
(Optional) The maximum number of licenses for users with the Creator,
|
|
58
|
+
Explorer, or Viewer role, respectively, allowed on a site.
|
|
59
|
+
|
|
60
|
+
storage_quota: int
|
|
61
|
+
(Optional) Specifies the maximum amount of space for the new site, in
|
|
62
|
+
megabytes. If you set a quota and the site exceeds it, publishers will
|
|
63
|
+
be prevented from uploading new content until the site is under the
|
|
64
|
+
limit again.
|
|
65
|
+
|
|
66
|
+
disable_subscriptions: bool
|
|
67
|
+
(Optional) Specify true to prevent users from being able to subscribe
|
|
68
|
+
to workbooks on the specified site. The default is False.
|
|
69
|
+
|
|
70
|
+
subscribe_others_enabled: bool
|
|
71
|
+
(Optional) Specify false to prevent server administrators, site
|
|
72
|
+
administrators, and project or content owners from being able to
|
|
73
|
+
subscribe other users to workbooks on the specified site. The default
|
|
74
|
+
is True.
|
|
75
|
+
|
|
76
|
+
revision_history_enabled: bool
|
|
77
|
+
(Optional) Specify true to enable revision history for content resources
|
|
78
|
+
(workbooks and datasources). The default is False.
|
|
79
|
+
|
|
80
|
+
revision_limit: int
|
|
81
|
+
(Optional) Specifies the number of revisions of a content source
|
|
82
|
+
(workbook or data source) to allow. On Tableau Server, the default is
|
|
83
|
+
25.
|
|
84
|
+
|
|
85
|
+
state: str
|
|
86
|
+
Shows the current state of the site (Active or Suspended).
|
|
87
|
+
|
|
88
|
+
"""
|
|
89
|
+
|
|
24
90
|
_user_quota: Optional[int] = None
|
|
25
91
|
_tier_creator_capacity: Optional[int] = None
|
|
26
92
|
_tier_explorer_capacity: Optional[int] = None
|
|
@@ -873,7 +939,7 @@ class SiteItem(object):
|
|
|
873
939
|
self.auto_suspend_refresh_inactivity_window = auto_suspend_refresh_inactivity_window
|
|
874
940
|
|
|
875
941
|
@classmethod
|
|
876
|
-
def from_response(cls, resp, ns) ->
|
|
942
|
+
def from_response(cls, resp, ns) -> list["SiteItem"]:
|
|
877
943
|
all_site_items = list()
|
|
878
944
|
parsed_response = fromstring(resp)
|
|
879
945
|
all_site_xml = parsed_response.findall(".//t:site", namespaces=ns)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
2
|
|
|
3
3
|
from defusedxml.ElementTree import fromstring
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@ if TYPE_CHECKING:
|
|
|
10
10
|
from .target import Target
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
class SubscriptionItem
|
|
13
|
+
class SubscriptionItem:
|
|
14
14
|
def __init__(self, subject: str, schedule_id: str, user_id: str, target: "Target") -> None:
|
|
15
15
|
self._id = None
|
|
16
16
|
self.attach_image = True
|
|
@@ -79,7 +79,7 @@ class SubscriptionItem(object):
|
|
|
79
79
|
self._suspended = value
|
|
80
80
|
|
|
81
81
|
@classmethod
|
|
82
|
-
def from_response(cls:
|
|
82
|
+
def from_response(cls: type, xml: bytes, ns) -> list["SubscriptionItem"]:
|
|
83
83
|
parsed_response = fromstring(xml)
|
|
84
84
|
all_subscriptions_xml = parsed_response.findall(".//t:subscription", namespaces=ns)
|
|
85
85
|
|
|
@@ -4,7 +4,7 @@ from .exceptions import UnpopulatedPropertyError
|
|
|
4
4
|
from .property_decorators import property_not_empty, property_is_boolean
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
class TableItem
|
|
7
|
+
class TableItem:
|
|
8
8
|
def __init__(self, name, description=None):
|
|
9
9
|
self._id = None
|
|
10
10
|
self.description = description
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import abc
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Optional
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class Credentials(abc.ABC):
|
|
@@ -9,7 +9,7 @@ class Credentials(abc.ABC):
|
|
|
9
9
|
|
|
10
10
|
@property
|
|
11
11
|
@abc.abstractmethod
|
|
12
|
-
def credentials(self) ->
|
|
12
|
+
def credentials(self) -> dict[str, str]:
|
|
13
13
|
credentials = (
|
|
14
14
|
"Credentials can be username/password, Personal Access Token, or JWT"
|
|
15
15
|
"This method returns values to set as an attribute on the credentials element of the request"
|
|
@@ -32,6 +32,43 @@ def deprecate_site_attribute():
|
|
|
32
32
|
|
|
33
33
|
# The traditional auth type: username/password
|
|
34
34
|
class TableauAuth(Credentials):
|
|
35
|
+
"""
|
|
36
|
+
The TableauAuth class defines the information you can set in a sign-in
|
|
37
|
+
request. The class members correspond to the attributes of a server request
|
|
38
|
+
or response payload. To use this class, create a new instance, supplying
|
|
39
|
+
user name, password, and site information if necessary, and pass the
|
|
40
|
+
request object to the Auth.sign_in method.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
username : str
|
|
45
|
+
The user name for the sign-in request.
|
|
46
|
+
|
|
47
|
+
password : str
|
|
48
|
+
The password for the sign-in request.
|
|
49
|
+
|
|
50
|
+
site_id : str, optional
|
|
51
|
+
This corresponds to the contentUrl attribute in the Tableau REST API.
|
|
52
|
+
The site_id is the portion of the URL that follows the /site/ in the
|
|
53
|
+
URL. For example, "MarketingTeam" is the site_id in the following URL
|
|
54
|
+
MyServer/#/site/MarketingTeam/projects. To specify the default site on
|
|
55
|
+
Tableau Server, you can use an empty string '' (single quotes, no
|
|
56
|
+
space). For Tableau Cloud, you must provide a value for the site_id.
|
|
57
|
+
|
|
58
|
+
user_id_to_impersonate : str, optional
|
|
59
|
+
Specifies the id (not the name) of the user to sign in as. This is not
|
|
60
|
+
available for Tableau Online.
|
|
61
|
+
|
|
62
|
+
Examples
|
|
63
|
+
--------
|
|
64
|
+
>>> import tableauserverclient as TSC
|
|
65
|
+
|
|
66
|
+
>>> tableau_auth = TSC.TableauAuth('USERNAME', 'PASSWORD', site_id='CONTENTURL')
|
|
67
|
+
>>> server = TSC.Server('https://SERVER_URL', use_server_version=True)
|
|
68
|
+
>>> server.auth.sign_in(tableau_auth)
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
|
|
35
72
|
def __init__(
|
|
36
73
|
self, username: str, password: str, site_id: Optional[str] = None, user_id_to_impersonate: Optional[str] = None
|
|
37
74
|
) -> None:
|
|
@@ -42,7 +79,7 @@ class TableauAuth(Credentials):
|
|
|
42
79
|
self.username = username
|
|
43
80
|
|
|
44
81
|
@property
|
|
45
|
-
def credentials(self) ->
|
|
82
|
+
def credentials(self) -> dict[str, str]:
|
|
46
83
|
return {"name": self.username, "password": self.password}
|
|
47
84
|
|
|
48
85
|
def __repr__(self):
|
|
@@ -55,6 +92,43 @@ class TableauAuth(Credentials):
|
|
|
55
92
|
|
|
56
93
|
# A Tableau-generated Personal Access Token
|
|
57
94
|
class PersonalAccessTokenAuth(Credentials):
|
|
95
|
+
"""
|
|
96
|
+
The PersonalAccessTokenAuth class defines the information you can set in a sign-in
|
|
97
|
+
request. The class members correspond to the attributes of a server request
|
|
98
|
+
or response payload. To use this class, create a new instance, supplying
|
|
99
|
+
token name, token secret, and site information if necessary, and pass the
|
|
100
|
+
request object to the Auth.sign_in method.
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
token_name : str
|
|
105
|
+
The name of the personal access token.
|
|
106
|
+
|
|
107
|
+
personal_access_token : str
|
|
108
|
+
The personal access token secret for the sign in request.
|
|
109
|
+
|
|
110
|
+
site_id : str, optional
|
|
111
|
+
This corresponds to the contentUrl attribute in the Tableau REST API.
|
|
112
|
+
The site_id is the portion of the URL that follows the /site/ in the
|
|
113
|
+
URL. For example, "MarketingTeam" is the site_id in the following URL
|
|
114
|
+
MyServer/#/site/MarketingTeam/projects. To specify the default site on
|
|
115
|
+
Tableau Server, you can use an empty string '' (single quotes, no
|
|
116
|
+
space). For Tableau Cloud, you must provide a value for the site_id.
|
|
117
|
+
|
|
118
|
+
user_id_to_impersonate : str, optional
|
|
119
|
+
Specifies the id (not the name) of the user to sign in as. This is not
|
|
120
|
+
available for Tableau Online.
|
|
121
|
+
|
|
122
|
+
Examples
|
|
123
|
+
--------
|
|
124
|
+
>>> import tableauserverclient as TSC
|
|
125
|
+
|
|
126
|
+
>>> tableau_auth = TSC.PersonalAccessTokenAuth("token_name", "token_secret", site_id='CONTENTURL')
|
|
127
|
+
>>> server = TSC.Server('https://SERVER_URL', use_server_version=True)
|
|
128
|
+
>>> server.auth.sign_in(tableau_auth)
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
|
|
58
132
|
def __init__(
|
|
59
133
|
self,
|
|
60
134
|
token_name: str,
|
|
@@ -69,7 +143,7 @@ class PersonalAccessTokenAuth(Credentials):
|
|
|
69
143
|
self.personal_access_token = personal_access_token
|
|
70
144
|
|
|
71
145
|
@property
|
|
72
|
-
def credentials(self) ->
|
|
146
|
+
def credentials(self) -> dict[str, str]:
|
|
73
147
|
return {
|
|
74
148
|
"personalAccessTokenName": self.token_name,
|
|
75
149
|
"personalAccessTokenSecret": self.personal_access_token,
|
|
@@ -88,6 +162,42 @@ class PersonalAccessTokenAuth(Credentials):
|
|
|
88
162
|
|
|
89
163
|
# A standard JWT generated specifically for Tableau
|
|
90
164
|
class JWTAuth(Credentials):
|
|
165
|
+
"""
|
|
166
|
+
The JWTAuth class defines the information you can set in a sign-in
|
|
167
|
+
request. The class members correspond to the attributes of a server request
|
|
168
|
+
or response payload. To use this class, create a new instance, supplying
|
|
169
|
+
an encoded JSON Web Token, and site information if necessary, and pass the
|
|
170
|
+
request object to the Auth.sign_in method.
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
token : str
|
|
175
|
+
The encoded JSON Web Token.
|
|
176
|
+
|
|
177
|
+
site_id : str, optional
|
|
178
|
+
This corresponds to the contentUrl attribute in the Tableau REST API.
|
|
179
|
+
The site_id is the portion of the URL that follows the /site/ in the
|
|
180
|
+
URL. For example, "MarketingTeam" is the site_id in the following URL
|
|
181
|
+
MyServer/#/site/MarketingTeam/projects. To specify the default site on
|
|
182
|
+
Tableau Server, you can use an empty string '' (single quotes, no
|
|
183
|
+
space). For Tableau Cloud, you must provide a value for the site_id.
|
|
184
|
+
|
|
185
|
+
user_id_to_impersonate : str, optional
|
|
186
|
+
Specifies the id (not the name) of the user to sign in as. This is not
|
|
187
|
+
available for Tableau Online.
|
|
188
|
+
|
|
189
|
+
Examples
|
|
190
|
+
--------
|
|
191
|
+
>>> import jwt
|
|
192
|
+
>>> import tableauserverclient as TSC
|
|
193
|
+
|
|
194
|
+
>>> jwt_token = jwt.encode(...)
|
|
195
|
+
>>> tableau_auth = TSC.JWTAuth(token, site_id='CONTENTURL')
|
|
196
|
+
>>> server = TSC.Server('https://SERVER_URL', use_server_version=True)
|
|
197
|
+
>>> server.auth.sign_in(tableau_auth)
|
|
198
|
+
|
|
199
|
+
"""
|
|
200
|
+
|
|
91
201
|
def __init__(self, jwt: str, site_id: Optional[str] = None, user_id_to_impersonate: Optional[str] = None) -> None:
|
|
92
202
|
if jwt is None:
|
|
93
203
|
raise TabError("Must provide a JWT token when using JWT authentication")
|
|
@@ -95,7 +205,7 @@ class JWTAuth(Credentials):
|
|
|
95
205
|
self.jwt = jwt
|
|
96
206
|
|
|
97
207
|
@property
|
|
98
|
-
def credentials(self) ->
|
|
208
|
+
def credentials(self) -> dict[str, str]:
|
|
99
209
|
return {"jwt": self.jwt}
|
|
100
210
|
|
|
101
211
|
def __repr__(self):
|
|
@@ -28,8 +28,8 @@ class Resource:
|
|
|
28
28
|
TableauItem = Union[DatasourceItem, FlowItem, MetricItem, ProjectItem, ViewItem, WorkbookItem, VirtualConnectionItem]
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def plural_type(content_type: Resource) -> str:
|
|
31
|
+
def plural_type(content_type: Union[Resource, str]) -> str:
|
|
32
32
|
if content_type == Resource.Lens:
|
|
33
33
|
return "lenses"
|
|
34
34
|
else:
|
|
35
|
-
return "{}s"
|
|
35
|
+
return f"{content_type}s"
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import xml.etree.ElementTree as ET
|
|
2
|
-
from typing import Set
|
|
3
2
|
|
|
4
3
|
from defusedxml.ElementTree import fromstring
|
|
5
4
|
|
|
6
5
|
|
|
7
|
-
class TagItem
|
|
6
|
+
class TagItem:
|
|
8
7
|
@classmethod
|
|
9
|
-
def from_response(cls, resp: bytes, ns) ->
|
|
8
|
+
def from_response(cls, resp: bytes, ns) -> set[str]:
|
|
10
9
|
return cls.from_xml_element(fromstring(resp), ns)
|
|
11
10
|
|
|
12
11
|
@classmethod
|
|
13
|
-
def from_xml_element(cls, parsed_response: ET.Element, ns) ->
|
|
12
|
+
def from_xml_element(cls, parsed_response: ET.Element, ns) -> set[str]:
|
|
14
13
|
all_tags = set()
|
|
15
14
|
tag_elem = parsed_response.findall(".//t:tag", namespaces=ns)
|
|
16
15
|
for tag_xml in tag_elem:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Optional
|
|
3
3
|
|
|
4
4
|
from defusedxml.ElementTree import fromstring
|
|
5
5
|
|
|
@@ -8,7 +8,7 @@ from tableauserverclient.models.schedule_item import ScheduleItem
|
|
|
8
8
|
from tableauserverclient.models.target import Target
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class TaskItem
|
|
11
|
+
class TaskItem:
|
|
12
12
|
class Type:
|
|
13
13
|
ExtractRefresh = "extractRefresh"
|
|
14
14
|
DataAcceleration = "dataAcceleration"
|
|
@@ -48,9 +48,9 @@ class TaskItem(object):
|
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
@classmethod
|
|
51
|
-
def from_response(cls, xml, ns, task_type=Type.ExtractRefresh) ->
|
|
51
|
+
def from_response(cls, xml, ns, task_type=Type.ExtractRefresh) -> list["TaskItem"]:
|
|
52
52
|
parsed_response = fromstring(xml)
|
|
53
|
-
all_tasks_xml = parsed_response.findall(".//t:task/t:{}"
|
|
53
|
+
all_tasks_xml = parsed_response.findall(f".//t:task/t:{task_type}", namespaces=ns)
|
|
54
54
|
|
|
55
55
|
all_tasks = (TaskItem._parse_element(x, ns) for x in all_tasks_xml)
|
|
56
56
|
|
|
@@ -2,7 +2,7 @@ import io
|
|
|
2
2
|
import xml.etree.ElementTree as ET
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from enum import IntEnum
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import Optional, TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
from defusedxml.ElementTree import fromstring
|
|
8
8
|
|
|
@@ -18,10 +18,35 @@ if TYPE_CHECKING:
|
|
|
18
18
|
from tableauserverclient.server import Pager
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
class UserItem
|
|
21
|
+
class UserItem:
|
|
22
|
+
"""
|
|
23
|
+
The UserItem class contains the members or attributes for the view
|
|
24
|
+
resources on Tableau Server. The UserItem class defines the information you
|
|
25
|
+
can request or query from Tableau Server. The class attributes correspond
|
|
26
|
+
to the attributes of a server request or response payload.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
name: str
|
|
32
|
+
The name of the user.
|
|
33
|
+
|
|
34
|
+
site_role: str
|
|
35
|
+
The role of the user on the site.
|
|
36
|
+
|
|
37
|
+
auth_setting: str
|
|
38
|
+
Required attribute for Tableau Cloud. How the user autenticates to the
|
|
39
|
+
server.
|
|
40
|
+
"""
|
|
41
|
+
|
|
22
42
|
tag_name: str = "user"
|
|
23
43
|
|
|
24
44
|
class Roles:
|
|
45
|
+
"""
|
|
46
|
+
The Roles class contains the possible roles for a user on Tableau
|
|
47
|
+
Server.
|
|
48
|
+
"""
|
|
49
|
+
|
|
25
50
|
Interactor = "Interactor"
|
|
26
51
|
Publisher = "Publisher"
|
|
27
52
|
ServerAdministrator = "ServerAdministrator"
|
|
@@ -43,6 +68,11 @@ class UserItem(object):
|
|
|
43
68
|
SupportUser = "SupportUser"
|
|
44
69
|
|
|
45
70
|
class Auth:
|
|
71
|
+
"""
|
|
72
|
+
The Auth class contains the possible authentication settings for a user
|
|
73
|
+
on Tableau Cloud.
|
|
74
|
+
"""
|
|
75
|
+
|
|
46
76
|
OpenID = "OpenID"
|
|
47
77
|
SAML = "SAML"
|
|
48
78
|
TableauIDWithMFA = "TableauIDWithMFA"
|
|
@@ -57,7 +87,7 @@ class UserItem(object):
|
|
|
57
87
|
self._id: Optional[str] = None
|
|
58
88
|
self._last_login: Optional[datetime] = None
|
|
59
89
|
self._workbooks = None
|
|
60
|
-
self._favorites: Optional[
|
|
90
|
+
self._favorites: Optional[dict[str, list]] = None
|
|
61
91
|
self._groups = None
|
|
62
92
|
self.email: Optional[str] = None
|
|
63
93
|
self.fullname: Optional[str] = None
|
|
@@ -69,7 +99,7 @@ class UserItem(object):
|
|
|
69
99
|
|
|
70
100
|
def __str__(self) -> str:
|
|
71
101
|
str_site_role = self.site_role or "None"
|
|
72
|
-
return "<User {} name={} role={}>"
|
|
102
|
+
return f"<User {self.id} name={self.name} role={str_site_role}>"
|
|
73
103
|
|
|
74
104
|
def __repr__(self):
|
|
75
105
|
return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
|
|
@@ -141,7 +171,7 @@ class UserItem(object):
|
|
|
141
171
|
return self._workbooks()
|
|
142
172
|
|
|
143
173
|
@property
|
|
144
|
-
def favorites(self) ->
|
|
174
|
+
def favorites(self) -> dict[str, list]:
|
|
145
175
|
if self._favorites is None:
|
|
146
176
|
error = "User item must be populated with favorites first."
|
|
147
177
|
raise UnpopulatedPropertyError(error)
|
|
@@ -210,12 +240,12 @@ class UserItem(object):
|
|
|
210
240
|
self._domain_name = domain_name
|
|
211
241
|
|
|
212
242
|
@classmethod
|
|
213
|
-
def from_response(cls, resp, ns) ->
|
|
243
|
+
def from_response(cls, resp, ns) -> list["UserItem"]:
|
|
214
244
|
element_name = ".//t:user"
|
|
215
245
|
return cls._parse_xml(element_name, resp, ns)
|
|
216
246
|
|
|
217
247
|
@classmethod
|
|
218
|
-
def from_response_as_owner(cls, resp, ns) ->
|
|
248
|
+
def from_response_as_owner(cls, resp, ns) -> list["UserItem"]:
|
|
219
249
|
element_name = ".//t:owner"
|
|
220
250
|
return cls._parse_xml(element_name, resp, ns)
|
|
221
251
|
|
|
@@ -283,7 +313,7 @@ class UserItem(object):
|
|
|
283
313
|
domain_name,
|
|
284
314
|
)
|
|
285
315
|
|
|
286
|
-
class CSVImport
|
|
316
|
+
class CSVImport:
|
|
287
317
|
"""
|
|
288
318
|
This class includes hardcoded options and logic for the CSV file format defined for user import
|
|
289
319
|
https://help.tableau.com/current/server/en-us/users_import.htm
|
|
@@ -308,7 +338,7 @@ class UserItem(object):
|
|
|
308
338
|
if line is None or line is False or line == "\n" or line == "":
|
|
309
339
|
return None
|
|
310
340
|
line = line.strip().lower()
|
|
311
|
-
values:
|
|
341
|
+
values: list[str] = list(map(str.strip, line.split(",")))
|
|
312
342
|
user = UserItem(values[UserItem.CSVImport.ColumnType.USERNAME])
|
|
313
343
|
if len(values) > 1:
|
|
314
344
|
if len(values) > UserItem.CSVImport.ColumnType.MAX:
|
|
@@ -337,7 +367,7 @@ class UserItem(object):
|
|
|
337
367
|
# Read through an entire CSV file meant for user import
|
|
338
368
|
# Return the number of valid lines and a list of all the invalid lines
|
|
339
369
|
@staticmethod
|
|
340
|
-
def validate_file_for_import(csv_file: io.TextIOWrapper, logger) ->
|
|
370
|
+
def validate_file_for_import(csv_file: io.TextIOWrapper, logger) -> tuple[int, list[str]]:
|
|
341
371
|
num_valid_lines = 0
|
|
342
372
|
invalid_lines = []
|
|
343
373
|
csv_file.seek(0) # set to start of file in case it has been read earlier
|
|
@@ -345,11 +375,11 @@ class UserItem(object):
|
|
|
345
375
|
while line and line != "":
|
|
346
376
|
try:
|
|
347
377
|
# do not print passwords
|
|
348
|
-
logger.info("Reading user {
|
|
378
|
+
logger.info(f"Reading user {line[:4]}")
|
|
349
379
|
UserItem.CSVImport._validate_import_line_or_throw(line, logger)
|
|
350
380
|
num_valid_lines += 1
|
|
351
381
|
except Exception as exc:
|
|
352
|
-
logger.info("Error parsing {
|
|
382
|
+
logger.info(f"Error parsing {line[:4]}: {exc}")
|
|
353
383
|
invalid_lines.append(line)
|
|
354
384
|
line = csv_file.readline()
|
|
355
385
|
return num_valid_lines, invalid_lines
|
|
@@ -358,7 +388,7 @@ class UserItem(object):
|
|
|
358
388
|
# Iterate through each field and validate the given value against hardcoded constraints
|
|
359
389
|
@staticmethod
|
|
360
390
|
def _validate_import_line_or_throw(incoming, logger) -> None:
|
|
361
|
-
_valid_attributes:
|
|
391
|
+
_valid_attributes: list[list[str]] = [
|
|
362
392
|
[],
|
|
363
393
|
[],
|
|
364
394
|
[],
|
|
@@ -373,23 +403,23 @@ class UserItem(object):
|
|
|
373
403
|
if len(line) > UserItem.CSVImport.ColumnType.MAX:
|
|
374
404
|
raise AttributeError("Too many attributes in line")
|
|
375
405
|
username = line[UserItem.CSVImport.ColumnType.USERNAME.value]
|
|
376
|
-
logger.debug("> details - {}"
|
|
406
|
+
logger.debug(f"> details - {username}")
|
|
377
407
|
UserItem.validate_username_or_throw(username)
|
|
378
408
|
for i in range(1, len(line)):
|
|
379
|
-
logger.debug("column {
|
|
409
|
+
logger.debug(f"column {UserItem.CSVImport.ColumnType(i).name}: {line[i]}")
|
|
380
410
|
UserItem.CSVImport._validate_attribute_value(
|
|
381
411
|
line[i], _valid_attributes[i], UserItem.CSVImport.ColumnType(i)
|
|
382
412
|
)
|
|
383
413
|
|
|
384
414
|
# Given a restricted set of possible values, confirm the item is in that set
|
|
385
415
|
@staticmethod
|
|
386
|
-
def _validate_attribute_value(item: str, possible_values:
|
|
416
|
+
def _validate_attribute_value(item: str, possible_values: list[str], column_type) -> None:
|
|
387
417
|
if item is None or item == "":
|
|
388
418
|
# value can be empty for any column except user, which is checked elsewhere
|
|
389
419
|
return
|
|
390
420
|
if item in possible_values or possible_values == []:
|
|
391
421
|
return
|
|
392
|
-
raise AttributeError("Invalid value {} for {}"
|
|
422
|
+
raise AttributeError(f"Invalid value {item} for {column_type}")
|
|
393
423
|
|
|
394
424
|
# https://help.tableau.com/current/server/en-us/csvguidelines.htm#settings_and_site_roles
|
|
395
425
|
# This logic is hardcoded to match the existing rules for import csv files
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from requests import Response
|
|
4
|
-
from typing import Callable,
|
|
4
|
+
from typing import Callable, Optional
|
|
5
|
+
from collections.abc import Iterator
|
|
5
6
|
|
|
6
7
|
from defusedxml.ElementTree import fromstring
|
|
7
8
|
|
|
@@ -11,13 +12,13 @@ from .permissions_item import PermissionsRule
|
|
|
11
12
|
from .tag_item import TagItem
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
class ViewItem
|
|
15
|
+
class ViewItem:
|
|
15
16
|
def __init__(self) -> None:
|
|
16
17
|
self._content_url: Optional[str] = None
|
|
17
18
|
self._created_at: Optional[datetime] = None
|
|
18
19
|
self._id: Optional[str] = None
|
|
19
20
|
self._image: Optional[Callable[[], bytes]] = None
|
|
20
|
-
self._initial_tags:
|
|
21
|
+
self._initial_tags: set[str] = set()
|
|
21
22
|
self._name: Optional[str] = None
|
|
22
23
|
self._owner_id: Optional[str] = None
|
|
23
24
|
self._preview_image: Optional[Callable[[], bytes]] = None
|
|
@@ -29,15 +30,15 @@ class ViewItem(object):
|
|
|
29
30
|
self._sheet_type: Optional[str] = None
|
|
30
31
|
self._updated_at: Optional[datetime] = None
|
|
31
32
|
self._workbook_id: Optional[str] = None
|
|
32
|
-
self._permissions: Optional[Callable[[],
|
|
33
|
-
self.tags:
|
|
33
|
+
self._permissions: Optional[Callable[[], list[PermissionsRule]]] = None
|
|
34
|
+
self.tags: set[str] = set()
|
|
34
35
|
self._data_acceleration_config = {
|
|
35
36
|
"acceleration_enabled": None,
|
|
36
37
|
"acceleration_status": None,
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
def __str__(self):
|
|
40
|
-
return "<ViewItem {
|
|
41
|
+
return "<ViewItem {} '{}' contentUrl='{}' project={}>".format(
|
|
41
42
|
self._id, self.name, self.content_url, self.project_id
|
|
42
43
|
)
|
|
43
44
|
|
|
@@ -146,21 +147,21 @@ class ViewItem(object):
|
|
|
146
147
|
self._data_acceleration_config = value
|
|
147
148
|
|
|
148
149
|
@property
|
|
149
|
-
def permissions(self) ->
|
|
150
|
+
def permissions(self) -> list[PermissionsRule]:
|
|
150
151
|
if self._permissions is None:
|
|
151
152
|
error = "View item must be populated with permissions first."
|
|
152
153
|
raise UnpopulatedPropertyError(error)
|
|
153
154
|
return self._permissions()
|
|
154
155
|
|
|
155
|
-
def _set_permissions(self, permissions: Callable[[],
|
|
156
|
+
def _set_permissions(self, permissions: Callable[[], list[PermissionsRule]]) -> None:
|
|
156
157
|
self._permissions = permissions
|
|
157
158
|
|
|
158
159
|
@classmethod
|
|
159
|
-
def from_response(cls, resp: "Response", ns, workbook_id="") ->
|
|
160
|
+
def from_response(cls, resp: "Response", ns, workbook_id="") -> list["ViewItem"]:
|
|
160
161
|
return cls.from_xml_element(fromstring(resp), ns, workbook_id)
|
|
161
162
|
|
|
162
163
|
@classmethod
|
|
163
|
-
def from_xml_element(cls, parsed_response, ns, workbook_id="") ->
|
|
164
|
+
def from_xml_element(cls, parsed_response, ns, workbook_id="") -> list["ViewItem"]:
|
|
164
165
|
all_view_items = list()
|
|
165
166
|
all_view_xml = parsed_response.findall(".//t:view", namespaces=ns)
|
|
166
167
|
for view_xml in all_view_xml:
|