tableauserverclient 0.38__py3-none-any.whl → 0.40__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 (46) hide show
  1. _version.py +21 -0
  2. tableauserverclient/__init__.py +10 -5
  3. tableauserverclient/bin/__init__.py +3 -0
  4. tableauserverclient/bin/_version.py +3 -3
  5. tableauserverclient/models/__init__.py +9 -0
  6. tableauserverclient/models/collection_item.py +52 -0
  7. tableauserverclient/models/connection_item.py +16 -2
  8. tableauserverclient/models/custom_view_item.py +8 -0
  9. tableauserverclient/models/data_freshness_policy_item.py +3 -3
  10. tableauserverclient/models/datasource_item.py +3 -1
  11. tableauserverclient/models/extensions_item.py +186 -0
  12. tableauserverclient/models/favorites_item.py +21 -8
  13. tableauserverclient/models/flow_item.py +1 -1
  14. tableauserverclient/models/group_item.py +7 -1
  15. tableauserverclient/models/groupset_item.py +14 -0
  16. tableauserverclient/models/interval_item.py +2 -1
  17. tableauserverclient/models/oidc_item.py +82 -0
  18. tableauserverclient/models/permissions_item.py +2 -0
  19. tableauserverclient/models/project_item.py +3 -2
  20. tableauserverclient/models/property_decorators.py +2 -2
  21. tableauserverclient/models/reference_item.py +12 -6
  22. tableauserverclient/models/schedule_item.py +10 -1
  23. tableauserverclient/models/site_item.py +26 -0
  24. tableauserverclient/models/tableau_auth.py +13 -6
  25. tableauserverclient/models/user_item.py +10 -3
  26. tableauserverclient/models/workbook_item.py +2 -2
  27. tableauserverclient/server/endpoint/__init__.py +4 -0
  28. tableauserverclient/server/endpoint/datasources_endpoint.py +152 -22
  29. tableauserverclient/server/endpoint/extensions_endpoint.py +79 -0
  30. tableauserverclient/server/endpoint/flow_task_endpoint.py +1 -1
  31. tableauserverclient/server/endpoint/flows_endpoint.py +5 -4
  32. tableauserverclient/server/endpoint/oidc_endpoint.py +157 -0
  33. tableauserverclient/server/endpoint/projects_endpoint.py +12 -0
  34. tableauserverclient/server/endpoint/schedules_endpoint.py +48 -1
  35. tableauserverclient/server/endpoint/users_endpoint.py +274 -5
  36. tableauserverclient/server/endpoint/views_endpoint.py +23 -0
  37. tableauserverclient/server/endpoint/workbooks_endpoint.py +124 -9
  38. tableauserverclient/server/request_factory.py +281 -2
  39. tableauserverclient/server/request_options.py +12 -2
  40. tableauserverclient/server/server.py +4 -0
  41. {tableauserverclient-0.38.dist-info → tableauserverclient-0.40.dist-info}/METADATA +5 -26
  42. {tableauserverclient-0.38.dist-info → tableauserverclient-0.40.dist-info}/RECORD +45 -39
  43. {tableauserverclient-0.38.dist-info → tableauserverclient-0.40.dist-info}/WHEEL +1 -1
  44. tableauserverclient-0.38.dist-info/licenses/LICENSE.versioneer +0 -7
  45. {tableauserverclient-0.38.dist-info → tableauserverclient-0.40.dist-info}/licenses/LICENSE +0 -0
  46. {tableauserverclient-0.38.dist-info → tableauserverclient-0.40.dist-info}/top_level.txt +0 -0
@@ -305,6 +305,7 @@ class MonthlyInterval:
305
305
  "Fourth",
306
306
  "Fifth",
307
307
  "Last",
308
+ "Customized Monthly",
308
309
  ]
309
310
  for value in range(1, 32):
310
311
  VALID_INTERVALS.append(str(value))
@@ -318,4 +319,4 @@ class MonthlyInterval:
318
319
  self._interval = interval_values
319
320
 
320
321
  def _interval_type_pairs(self):
321
- return [(IntervalItem.Occurrence.MonthDay, self.interval)]
322
+ return [(IntervalItem.Occurrence.MonthDay, str(day)) for day in self.interval]
@@ -0,0 +1,82 @@
1
+ from typing import Optional
2
+ from defusedxml.ElementTree import fromstring
3
+
4
+
5
+ class SiteOIDCConfiguration:
6
+ def __init__(self) -> None:
7
+ self.enabled: bool = False
8
+ self.test_login_url: Optional[str] = None
9
+ self.known_provider_alias: Optional[str] = None
10
+ self.allow_embedded_authentication: bool = False
11
+ self.use_full_name: bool = False
12
+ self.idp_configuration_name: Optional[str] = None
13
+ self.idp_configuration_id: Optional[str] = None
14
+ self.client_id: Optional[str] = None
15
+ self.client_secret: Optional[str] = None
16
+ self.authorization_endpoint: Optional[str] = None
17
+ self.token_endpoint: Optional[str] = None
18
+ self.userinfo_endpoint: Optional[str] = None
19
+ self.jwks_uri: Optional[str] = None
20
+ self.end_session_endpoint: Optional[str] = None
21
+ self.custom_scope: Optional[str] = None
22
+ self.essential_acr_values: Optional[str] = None
23
+ self.email_mapping: Optional[str] = None
24
+ self.first_name_mapping: Optional[str] = None
25
+ self.last_name_mapping: Optional[str] = None
26
+ self.full_name_mapping: Optional[str] = None
27
+ self.prompt: Optional[str] = None
28
+ self.client_authentication: Optional[str] = None
29
+ self.voluntary_acr_values: Optional[str] = None
30
+
31
+ def __str__(self) -> str:
32
+ return (
33
+ f"{self.__class__.__qualname__}(enabled={self.enabled}, "
34
+ f"test_login_url={self.test_login_url}, "
35
+ f"idp_configuration_name={self.idp_configuration_name}, "
36
+ f"idp_configuration_id={self.idp_configuration_id}, "
37
+ f"client_id={self.client_id})"
38
+ )
39
+
40
+ def __repr__(self) -> str:
41
+ return f"<{str(self)}>"
42
+
43
+ @classmethod
44
+ def from_response(cls, raw_xml: bytes, ns) -> "SiteOIDCConfiguration":
45
+ """
46
+ Parses the raw XML bytes and returns a SiteOIDCConfiguration object.
47
+ """
48
+ root = fromstring(raw_xml)
49
+ elem = root.find("t:siteOIDCConfiguration", namespaces=ns)
50
+ if elem is None:
51
+ raise ValueError("No siteOIDCConfiguration element found in the XML.")
52
+ config = cls()
53
+
54
+ config.enabled = str_to_bool(elem.get("enabled", "false"))
55
+ config.test_login_url = elem.get("testLoginUrl")
56
+ config.known_provider_alias = elem.get("knownProviderAlias")
57
+ config.allow_embedded_authentication = str_to_bool(elem.get("allowEmbeddedAuthentication", "false").lower())
58
+ config.use_full_name = str_to_bool(elem.get("useFullName", "false").lower())
59
+ config.idp_configuration_name = elem.get("idpConfigurationName")
60
+ config.idp_configuration_id = elem.get("idpConfigurationId")
61
+ config.client_id = elem.get("clientId")
62
+ config.client_secret = elem.get("clientSecret")
63
+ config.authorization_endpoint = elem.get("authorizationEndpoint")
64
+ config.token_endpoint = elem.get("tokenEndpoint")
65
+ config.userinfo_endpoint = elem.get("userinfoEndpoint")
66
+ config.jwks_uri = elem.get("jwksUri")
67
+ config.end_session_endpoint = elem.get("endSessionEndpoint")
68
+ config.custom_scope = elem.get("customScope")
69
+ config.essential_acr_values = elem.get("essentialAcrValues")
70
+ config.email_mapping = elem.get("emailMapping")
71
+ config.first_name_mapping = elem.get("firstNameMapping")
72
+ config.last_name_mapping = elem.get("lastNameMapping")
73
+ config.full_name_mapping = elem.get("fullNameMapping")
74
+ config.prompt = elem.get("prompt")
75
+ config.client_authentication = elem.get("clientAuthentication")
76
+ config.voluntary_acr_values = elem.get("voluntaryAcrValues")
77
+
78
+ return config
79
+
80
+
81
+ def str_to_bool(s: str) -> bool:
82
+ return s == "true"
@@ -43,6 +43,8 @@ class Permission:
43
43
  CreateRefreshMetrics = "CreateRefreshMetrics"
44
44
  SaveAs = "SaveAs"
45
45
  PulseMetricDefine = "PulseMetricDefine"
46
+ ExtractRefresh = "ExtractRefresh"
47
+ WebAuthoringForFlows = "WebAuthoringForFlows"
46
48
 
47
49
  def __repr__(self):
48
50
  return "<Enum Capability: AddComment | ChangeHierarchy | ChangePermission ... (17 more) >"
@@ -86,9 +86,10 @@ class ProjectItem:
86
86
  content_permissions: Optional[str] = None,
87
87
  parent_id: Optional[str] = None,
88
88
  samples: Optional[bool] = None,
89
+ id: Optional[str] = None,
89
90
  ) -> None:
90
91
  self._content_permissions = None
91
- self._id: Optional[str] = None
92
+ self._id: Optional[str] = id
92
93
  self.description: Optional[str] = description
93
94
  self.name: str = name
94
95
  self.content_permissions: Optional[str] = content_permissions
@@ -194,7 +195,7 @@ class ProjectItem:
194
195
  return self._name
195
196
 
196
197
  @name.setter
197
- def name(self, value: str) -> None:
198
+ def name(self, value: Optional[str]) -> None:
198
199
  self._name = value
199
200
 
200
201
  @property
@@ -1,7 +1,7 @@
1
1
  import datetime
2
2
  import re
3
3
  from functools import wraps
4
- from typing import Any, Optional
4
+ from typing import Any
5
5
  from collections.abc import Container
6
6
 
7
7
  from tableauserverclient.datetime_helpers import parse_datetime
@@ -67,7 +67,7 @@ def property_is_valid_time(func):
67
67
  return wrapper
68
68
 
69
69
 
70
- def property_is_int(range: tuple[int, int], allowed: Optional[Container[Any]] = None):
70
+ def property_is_int(range: tuple[int, int], allowed: Container[Any] | None = None):
71
71
  """Takes a range of ints and a list of exemptions to check against
72
72
  when setting a property on a model. The range is a tuple of (min, max) and the
73
73
  allowed list (empty by default) allows values outside that range.
@@ -1,9 +1,12 @@
1
+ from typing_extensions import Self
2
+
3
+
1
4
  class ResourceReference:
2
- def __init__(self, id_, tag_name):
5
+ def __init__(self, id_: str | None, tag_name: str) -> None:
3
6
  self.id = id_
4
7
  self.tag_name = tag_name
5
8
 
6
- def __str__(self):
9
+ def __str__(self) -> str:
7
10
  return f"<ResourceReference id={self._id} tag={self._tag_name}>"
8
11
 
9
12
  __repr__ = __str__
@@ -13,18 +16,21 @@ class ResourceReference:
13
16
  return False
14
17
  return (self.id == other.id) and (self.tag_name == other.tag_name)
15
18
 
19
+ def __hash__(self: Self) -> int:
20
+ return hash((self.id, self.tag_name))
21
+
16
22
  @property
17
- def id(self):
23
+ def id(self) -> str | None:
18
24
  return self._id
19
25
 
20
26
  @id.setter
21
- def id(self, value):
27
+ def id(self, value: str | None) -> None:
22
28
  self._id = value
23
29
 
24
30
  @property
25
- def tag_name(self):
31
+ def tag_name(self) -> str:
26
32
  return self._tag_name
27
33
 
28
34
  @tag_name.setter
29
- def tag_name(self, value):
35
+ def tag_name(self, value: str) -> None:
30
36
  self._tag_name = value
@@ -1,6 +1,6 @@
1
1
  import xml.etree.ElementTree as ET
2
2
  from datetime import datetime
3
- from typing import Optional, Union
3
+ from typing import Optional, Union, TYPE_CHECKING
4
4
 
5
5
  from defusedxml.ElementTree import fromstring
6
6
 
@@ -16,6 +16,10 @@ from .property_decorators import (
16
16
  property_is_enum,
17
17
  )
18
18
 
19
+ if TYPE_CHECKING:
20
+ from requests import Response
21
+
22
+
19
23
  Interval = Union[HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval]
20
24
 
21
25
 
@@ -407,3 +411,8 @@ class ScheduleItem:
407
411
  for warning_xml in all_warning_xml:
408
412
  warnings.append(warning_xml.get("message", None))
409
413
  return warnings
414
+
415
+
416
+ def parse_batch_schedule_state(response: "Response", ns) -> list[str]:
417
+ xml = fromstring(response.content)
418
+ return [text for tag in xml.findall(".//t:scheduleLuid", namespaces=ns) if (text := tag.text)]
@@ -85,6 +85,9 @@ class SiteItem:
85
85
  state: str
86
86
  Shows the current state of the site (Active or Suspended).
87
87
 
88
+ attribute_capture_enabled: Optional[str]
89
+ Enables user attributes for all Tableau Server embedding workflows.
90
+
88
91
  """
89
92
 
90
93
  _user_quota: Optional[int] = None
@@ -164,6 +167,7 @@ class SiteItem:
164
167
  time_zone=None,
165
168
  auto_suspend_refresh_enabled: bool = True,
166
169
  auto_suspend_refresh_inactivity_window: int = 30,
170
+ attribute_capture_enabled: Optional[bool] = None,
167
171
  ):
168
172
  self._admin_mode = None
169
173
  self._id: Optional[str] = None
@@ -217,6 +221,7 @@ class SiteItem:
217
221
  self.time_zone = time_zone
218
222
  self.auto_suspend_refresh_enabled = auto_suspend_refresh_enabled
219
223
  self.auto_suspend_refresh_inactivity_window = auto_suspend_refresh_inactivity_window
224
+ self.attribute_capture_enabled = attribute_capture_enabled
220
225
 
221
226
  @property
222
227
  def admin_mode(self) -> Optional[str]:
@@ -720,6 +725,7 @@ class SiteItem:
720
725
  time_zone,
721
726
  auto_suspend_refresh_enabled,
722
727
  auto_suspend_refresh_inactivity_window,
728
+ attribute_capture_enabled,
723
729
  ) = self._parse_element(site_xml, ns)
724
730
 
725
731
  self._set_values(
@@ -774,6 +780,7 @@ class SiteItem:
774
780
  time_zone,
775
781
  auto_suspend_refresh_enabled,
776
782
  auto_suspend_refresh_inactivity_window,
783
+ attribute_capture_enabled,
777
784
  )
778
785
  return self
779
786
 
@@ -830,6 +837,7 @@ class SiteItem:
830
837
  time_zone,
831
838
  auto_suspend_refresh_enabled,
832
839
  auto_suspend_refresh_inactivity_window,
840
+ attribute_capture_enabled,
833
841
  ):
834
842
  if id is not None:
835
843
  self._id = id
@@ -937,6 +945,7 @@ class SiteItem:
937
945
  self.auto_suspend_refresh_enabled = auto_suspend_refresh_enabled
938
946
  if auto_suspend_refresh_inactivity_window is not None:
939
947
  self.auto_suspend_refresh_inactivity_window = auto_suspend_refresh_inactivity_window
948
+ self.attribute_capture_enabled = attribute_capture_enabled
940
949
 
941
950
  @classmethod
942
951
  def from_response(cls, resp, ns) -> list["SiteItem"]:
@@ -996,6 +1005,7 @@ class SiteItem:
996
1005
  time_zone,
997
1006
  auto_suspend_refresh_enabled,
998
1007
  auto_suspend_refresh_inactivity_window,
1008
+ attribute_capture_enabled,
999
1009
  ) = cls._parse_element(site_xml, ns)
1000
1010
 
1001
1011
  site_item = cls(name, content_url)
@@ -1051,6 +1061,7 @@ class SiteItem:
1051
1061
  time_zone,
1052
1062
  auto_suspend_refresh_enabled,
1053
1063
  auto_suspend_refresh_inactivity_window,
1064
+ attribute_capture_enabled,
1054
1065
  )
1055
1066
  all_site_items.append(site_item)
1056
1067
  return all_site_items
@@ -1132,6 +1143,9 @@ class SiteItem:
1132
1143
 
1133
1144
  flows_enabled = string_to_bool(site_xml.get("flowsEnabled", ""))
1134
1145
  cataloging_enabled = string_to_bool(site_xml.get("catalogingEnabled", ""))
1146
+ attribute_capture_enabled = (
1147
+ string_to_bool(ace) if (ace := site_xml.get("attributeCaptureEnabled")) is not None else None
1148
+ )
1135
1149
 
1136
1150
  return (
1137
1151
  id,
@@ -1185,6 +1199,7 @@ class SiteItem:
1185
1199
  time_zone,
1186
1200
  auto_suspend_refresh_enabled,
1187
1201
  auto_suspend_refresh_inactivity_window,
1202
+ attribute_capture_enabled,
1188
1203
  )
1189
1204
 
1190
1205
 
@@ -1215,6 +1230,17 @@ class SiteAuthConfiguration:
1215
1230
  all_auth_configs.append(auth_config)
1216
1231
  return all_auth_configs
1217
1232
 
1233
+ def __str__(self):
1234
+ return (
1235
+ f"{self.__class__.__qualname__}(auth_setting={self.auth_setting}, "
1236
+ f"enabled={self.enabled}, "
1237
+ f"idp_configuration_id={self.idp_configuration_id}, "
1238
+ f"idp_configuration_name={self.idp_configuration_name})"
1239
+ )
1240
+
1241
+ def __repr__(self):
1242
+ return f"<{str(self)}>"
1243
+
1218
1244
 
1219
1245
  # Used to convert string represented boolean to a boolean type
1220
1246
  def string_to_bool(s: str) -> bool:
@@ -87,7 +87,7 @@ class TableauAuth(Credentials):
87
87
  uid = f", user_id_to_impersonate=f{self.user_id_to_impersonate}"
88
88
  else:
89
89
  uid = ""
90
- return f"<Credentials username={self.username} password=redacted (site={self.site_id}{uid})>"
90
+ return f"<{self.__class__.__qualname__} username={self.username} password=redacted (site={self.site_id}{uid})>"
91
91
 
92
92
 
93
93
  # A Tableau-generated Personal Access Token
@@ -155,8 +155,8 @@ class PersonalAccessTokenAuth(Credentials):
155
155
  else:
156
156
  uid = ""
157
157
  return (
158
- f"<PersonalAccessToken name={self.token_name} token={self.personal_access_token[:2]}..."
159
- f"(site={self.site_id}{uid} >"
158
+ f"<{self.__class__.__qualname__}(name={self.token_name} token={self.personal_access_token[:2]}..."
159
+ f"site={self.site_id}{uid}) >"
160
160
  )
161
161
 
162
162
 
@@ -198,19 +198,26 @@ class JWTAuth(Credentials):
198
198
 
199
199
  """
200
200
 
201
- def __init__(self, jwt: str, site_id: Optional[str] = None, user_id_to_impersonate: Optional[str] = None) -> None:
201
+ def __init__(
202
+ self,
203
+ jwt: str,
204
+ isUat: bool = False,
205
+ site_id: Optional[str] = None,
206
+ user_id_to_impersonate: Optional[str] = None,
207
+ ) -> None:
202
208
  if jwt is None:
203
209
  raise TabError("Must provide a JWT token when using JWT authentication")
204
210
  super().__init__(site_id, user_id_to_impersonate)
205
211
  self.jwt = jwt
212
+ self.isUat = isUat
206
213
 
207
214
  @property
208
215
  def credentials(self) -> dict[str, str]:
209
- return {"jwt": self.jwt}
216
+ return {"jwt": self.jwt, "isUat": str(self.isUat).lower()}
210
217
 
211
218
  def __repr__(self):
212
219
  if self.user_id_to_impersonate:
213
220
  uid = f", user_id_to_impersonate=f{self.user_id_to_impersonate}"
214
221
  else:
215
222
  uid = ""
216
- return f"<{self.__class__.__qualname__} jwt={self.jwt[:5]}... (site={self.site_id}{uid})>"
223
+ return f"<{self.__class__.__qualname__} jwt={self.jwt[:5]}... isUat={self.isUat} (site={self.site_id}{uid})>"
@@ -5,6 +5,7 @@ from enum import IntEnum
5
5
  from typing import Optional, TYPE_CHECKING
6
6
 
7
7
  from defusedxml.ElementTree import fromstring
8
+ from typing_extensions import Self
8
9
 
9
10
  from tableauserverclient.datetime_helpers import parse_datetime
10
11
  from tableauserverclient.models.site_item import SiteAuthConfiguration
@@ -17,6 +18,7 @@ from .reference_item import ResourceReference
17
18
 
18
19
  if TYPE_CHECKING:
19
20
  from tableauserverclient.server import Pager
21
+ from tableauserverclient.models.favorites_item import FavoriteType
20
22
 
21
23
 
22
24
  class UserItem:
@@ -131,7 +133,7 @@ class UserItem:
131
133
  self._id: Optional[str] = None
132
134
  self._last_login: Optional[datetime] = None
133
135
  self._workbooks = None
134
- self._favorites: Optional[dict[str, list]] = None
136
+ self._favorites: Optional["FavoriteType"] = None
135
137
  self._groups = None
136
138
  self.email: Optional[str] = None
137
139
  self.fullname: Optional[str] = None
@@ -185,7 +187,7 @@ class UserItem:
185
187
  return self._name
186
188
 
187
189
  @name.setter
188
- def name(self, value: str):
190
+ def name(self, value: Optional[str]):
189
191
  self._name = value
190
192
 
191
193
  # valid: username, domain/username, username@domain, domain/username@email
@@ -218,7 +220,7 @@ class UserItem:
218
220
  return self._workbooks()
219
221
 
220
222
  @property
221
- def favorites(self) -> dict[str, list]:
223
+ def favorites(self) -> "FavoriteType":
222
224
  if self._favorites is None:
223
225
  error = "User item must be populated with favorites first."
224
226
  raise UnpopulatedPropertyError(error)
@@ -376,6 +378,11 @@ class UserItem:
376
378
  def as_reference(id_) -> ResourceReference:
377
379
  return ResourceReference(id_, UserItem.tag_name)
378
380
 
381
+ def to_reference(self: Self) -> ResourceReference:
382
+ if self.id is None:
383
+ raise ValueError(f"{self.__class__.__qualname__} must have id to be converted to reference")
384
+ return ResourceReference(self.id, self.tag_name)
385
+
379
386
  @staticmethod
380
387
  def _parse_element(user_xml, ns):
381
388
  id = user_xml.get("id", None)
@@ -330,7 +330,7 @@ class WorkbookItem:
330
330
  return self._thumbnails_user_id
331
331
 
332
332
  @thumbnails_user_id.setter
333
- def thumbnails_user_id(self, value: str):
333
+ def thumbnails_user_id(self, value: Optional[str]):
334
334
  self._thumbnails_user_id = value
335
335
 
336
336
  @property
@@ -338,7 +338,7 @@ class WorkbookItem:
338
338
  return self._thumbnails_group_id
339
339
 
340
340
  @thumbnails_group_id.setter
341
- def thumbnails_group_id(self, value: str):
341
+ def thumbnails_group_id(self, value: Optional[str]):
342
342
  self._thumbnails_group_id = value
343
343
 
344
344
  @property
@@ -6,6 +6,7 @@ from tableauserverclient.server.endpoint.databases_endpoint import Databases
6
6
  from tableauserverclient.server.endpoint.datasources_endpoint import Datasources
7
7
  from tableauserverclient.server.endpoint.endpoint import Endpoint, QuerysetEndpoint
8
8
  from tableauserverclient.server.endpoint.exceptions import ServerResponseError, MissingRequiredFieldError
9
+ from tableauserverclient.server.endpoint.extensions_endpoint import Extensions
9
10
  from tableauserverclient.server.endpoint.favorites_endpoint import Favorites
10
11
  from tableauserverclient.server.endpoint.fileuploads_endpoint import Fileuploads
11
12
  from tableauserverclient.server.endpoint.flow_runs_endpoint import FlowRuns
@@ -17,6 +18,7 @@ from tableauserverclient.server.endpoint.jobs_endpoint import Jobs
17
18
  from tableauserverclient.server.endpoint.linked_tasks_endpoint import LinkedTasks
18
19
  from tableauserverclient.server.endpoint.metadata_endpoint import Metadata
19
20
  from tableauserverclient.server.endpoint.metrics_endpoint import Metrics
21
+ from tableauserverclient.server.endpoint.oidc_endpoint import OIDC
20
22
  from tableauserverclient.server.endpoint.projects_endpoint import Projects
21
23
  from tableauserverclient.server.endpoint.schedules_endpoint import Schedules
22
24
  from tableauserverclient.server.endpoint.server_info_endpoint import ServerInfo
@@ -41,6 +43,7 @@ __all__ = [
41
43
  "QuerysetEndpoint",
42
44
  "MissingRequiredFieldError",
43
45
  "Endpoint",
46
+ "Extensions",
44
47
  "Favorites",
45
48
  "Fileuploads",
46
49
  "FlowRuns",
@@ -52,6 +55,7 @@ __all__ = [
52
55
  "LinkedTasks",
53
56
  "Metadata",
54
57
  "Metrics",
58
+ "OIDC",
55
59
  "Projects",
56
60
  "Schedules",
57
61
  "ServerInfo",