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.
Files changed (94) hide show
  1. tableauserverclient/__init__.py +28 -22
  2. tableauserverclient/_version.py +3 -3
  3. tableauserverclient/config.py +5 -3
  4. tableauserverclient/models/column_item.py +1 -1
  5. tableauserverclient/models/connection_credentials.py +1 -1
  6. tableauserverclient/models/connection_item.py +6 -6
  7. tableauserverclient/models/custom_view_item.py +29 -6
  8. tableauserverclient/models/data_acceleration_report_item.py +2 -2
  9. tableauserverclient/models/data_alert_item.py +5 -5
  10. tableauserverclient/models/data_freshness_policy_item.py +6 -6
  11. tableauserverclient/models/database_item.py +3 -3
  12. tableauserverclient/models/datasource_item.py +10 -10
  13. tableauserverclient/models/dqw_item.py +1 -1
  14. tableauserverclient/models/favorites_item.py +5 -6
  15. tableauserverclient/models/fileupload_item.py +1 -1
  16. tableauserverclient/models/flow_item.py +6 -6
  17. tableauserverclient/models/flow_run_item.py +3 -3
  18. tableauserverclient/models/group_item.py +4 -4
  19. tableauserverclient/models/groupset_item.py +4 -4
  20. tableauserverclient/models/interval_item.py +9 -9
  21. tableauserverclient/models/job_item.py +8 -8
  22. tableauserverclient/models/linked_tasks_item.py +5 -5
  23. tableauserverclient/models/metric_item.py +5 -5
  24. tableauserverclient/models/pagination_item.py +1 -1
  25. tableauserverclient/models/permissions_item.py +12 -10
  26. tableauserverclient/models/project_item.py +35 -19
  27. tableauserverclient/models/property_decorators.py +12 -11
  28. tableauserverclient/models/reference_item.py +2 -2
  29. tableauserverclient/models/revision_item.py +3 -3
  30. tableauserverclient/models/schedule_item.py +2 -2
  31. tableauserverclient/models/server_info_item.py +26 -6
  32. tableauserverclient/models/site_item.py +69 -3
  33. tableauserverclient/models/subscription_item.py +3 -3
  34. tableauserverclient/models/table_item.py +1 -1
  35. tableauserverclient/models/tableau_auth.py +115 -5
  36. tableauserverclient/models/tableau_types.py +2 -2
  37. tableauserverclient/models/tag_item.py +3 -4
  38. tableauserverclient/models/task_item.py +4 -4
  39. tableauserverclient/models/user_item.py +47 -17
  40. tableauserverclient/models/view_item.py +11 -10
  41. tableauserverclient/models/virtual_connection_item.py +6 -5
  42. tableauserverclient/models/webhook_item.py +6 -6
  43. tableauserverclient/models/workbook_item.py +90 -12
  44. tableauserverclient/namespace.py +1 -1
  45. tableauserverclient/server/__init__.py +2 -1
  46. tableauserverclient/server/endpoint/auth_endpoint.py +65 -8
  47. tableauserverclient/server/endpoint/custom_views_endpoint.py +62 -18
  48. tableauserverclient/server/endpoint/data_acceleration_report_endpoint.py +2 -2
  49. tableauserverclient/server/endpoint/data_alert_endpoint.py +14 -14
  50. tableauserverclient/server/endpoint/databases_endpoint.py +13 -12
  51. tableauserverclient/server/endpoint/datasources_endpoint.py +49 -54
  52. tableauserverclient/server/endpoint/default_permissions_endpoint.py +19 -18
  53. tableauserverclient/server/endpoint/dqw_endpoint.py +9 -9
  54. tableauserverclient/server/endpoint/endpoint.py +19 -21
  55. tableauserverclient/server/endpoint/exceptions.py +23 -7
  56. tableauserverclient/server/endpoint/favorites_endpoint.py +31 -31
  57. tableauserverclient/server/endpoint/fileuploads_endpoint.py +9 -11
  58. tableauserverclient/server/endpoint/flow_runs_endpoint.py +15 -13
  59. tableauserverclient/server/endpoint/flow_task_endpoint.py +2 -2
  60. tableauserverclient/server/endpoint/flows_endpoint.py +30 -29
  61. tableauserverclient/server/endpoint/groups_endpoint.py +18 -17
  62. tableauserverclient/server/endpoint/groupsets_endpoint.py +2 -2
  63. tableauserverclient/server/endpoint/jobs_endpoint.py +7 -7
  64. tableauserverclient/server/endpoint/linked_tasks_endpoint.py +2 -2
  65. tableauserverclient/server/endpoint/metadata_endpoint.py +2 -2
  66. tableauserverclient/server/endpoint/metrics_endpoint.py +10 -10
  67. tableauserverclient/server/endpoint/permissions_endpoint.py +13 -15
  68. tableauserverclient/server/endpoint/projects_endpoint.py +81 -30
  69. tableauserverclient/server/endpoint/resource_tagger.py +14 -13
  70. tableauserverclient/server/endpoint/schedules_endpoint.py +17 -18
  71. tableauserverclient/server/endpoint/server_info_endpoint.py +40 -5
  72. tableauserverclient/server/endpoint/sites_endpoint.py +282 -17
  73. tableauserverclient/server/endpoint/subscriptions_endpoint.py +10 -10
  74. tableauserverclient/server/endpoint/tables_endpoint.py +15 -14
  75. tableauserverclient/server/endpoint/tasks_endpoint.py +8 -8
  76. tableauserverclient/server/endpoint/users_endpoint.py +366 -19
  77. tableauserverclient/server/endpoint/views_endpoint.py +19 -18
  78. tableauserverclient/server/endpoint/virtual_connections_endpoint.py +6 -5
  79. tableauserverclient/server/endpoint/webhooks_endpoint.py +11 -11
  80. tableauserverclient/server/endpoint/workbooks_endpoint.py +647 -61
  81. tableauserverclient/server/filter.py +2 -2
  82. tableauserverclient/server/pager.py +5 -6
  83. tableauserverclient/server/query.py +68 -19
  84. tableauserverclient/server/request_factory.py +37 -36
  85. tableauserverclient/server/request_options.py +123 -145
  86. tableauserverclient/server/server.py +65 -9
  87. tableauserverclient/server/sort.py +2 -2
  88. {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/METADATA +6 -6
  89. tableauserverclient-0.34.dist-info/RECORD +106 -0
  90. {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/WHEEL +1 -1
  91. tableauserverclient-0.33.dist-info/RECORD +0 -106
  92. {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/LICENSE +0 -0
  93. {tableauserverclient-0.33.dist-info → tableauserverclient-0.34.dist-info}/LICENSE.versioneer +0 -0
  94. {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(object):
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.info("Unexpected response for ServerInfo: {}".format(resp))
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.info("Unexpected response for ServerInfo: {}".format(resp))
48
- logger.info(error)
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 List, Optional, Union, TYPE_CHECKING
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(object):
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) -> List["SiteItem"]:
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 List, Type, TYPE_CHECKING
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(object):
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: Type, xml: bytes, ns) -> List["SubscriptionItem"]:
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(object):
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 Dict, Optional
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) -> Dict[str, str]:
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) -> Dict[str, str]:
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) -> Dict[str, str]:
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) -> Dict[str, str]:
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".format(content_type)
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(object):
6
+ class TagItem:
8
7
  @classmethod
9
- def from_response(cls, resp: bytes, ns) -> Set[str]:
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) -> Set[str]:
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 List, Optional
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(object):
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) -> List["TaskItem"]:
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:{}".format(task_type), namespaces=ns)
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 Dict, List, Optional, TYPE_CHECKING, Tuple
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(object):
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[Dict[str, List]] = None
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={}>".format(self.id, self.name, str_site_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) -> Dict[str, List]:
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) -> List["UserItem"]:
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) -> List["UserItem"]:
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(object):
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: List[str] = list(map(str.strip, line.split(",")))
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) -> Tuple[int, List[str]]:
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 {}".format(line[:4]))
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 {}: {}".format(line[:4], exc))
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: List[List[str]] = [
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 - {}".format(username))
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 {}: {}".format(UserItem.CSVImport.ColumnType(i).name, line[i]))
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: List[str], column_type) -> None:
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 {}".format(item, column_type))
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, Iterator, List, Optional, Set
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(object):
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: Set[str] = set()
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[[], List[PermissionsRule]]] = None
33
- self.tags: Set[str] = set()
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 {0} '{1}' contentUrl='{2}' project={3}>".format(
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) -> List[PermissionsRule]:
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[[], List[PermissionsRule]]) -> None:
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="") -> List["ViewItem"]:
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="") -> List["ViewItem"]:
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: