tableauserverclient 0.38__py3-none-any.whl → 0.39__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 (117) hide show
  1. tableauserverclient/bin/_version.py → _version.py +3 -3
  2. bin/__init__.py +3 -0
  3. bin/_version.py +21 -0
  4. {tableauserverclient/models → models}/__init__.py +9 -0
  5. models/collection_item.py +52 -0
  6. {tableauserverclient/models → models}/connection_item.py +16 -2
  7. {tableauserverclient/models → models}/custom_view_item.py +8 -0
  8. {tableauserverclient/models → models}/data_freshness_policy_item.py +3 -3
  9. {tableauserverclient/models → models}/datasource_item.py +3 -1
  10. models/extensions_item.py +186 -0
  11. {tableauserverclient/models → models}/favorites_item.py +21 -8
  12. {tableauserverclient/models → models}/flow_item.py +1 -1
  13. {tableauserverclient/models → models}/group_item.py +7 -1
  14. {tableauserverclient/models → models}/groupset_item.py +14 -0
  15. {tableauserverclient/models → models}/interval_item.py +2 -1
  16. models/oidc_item.py +82 -0
  17. {tableauserverclient/models → models}/permissions_item.py +2 -0
  18. {tableauserverclient/models → models}/project_item.py +3 -2
  19. {tableauserverclient/models → models}/property_decorators.py +2 -2
  20. {tableauserverclient/models → models}/reference_item.py +12 -6
  21. {tableauserverclient/models → models}/schedule_item.py +10 -1
  22. {tableauserverclient/models → models}/site_item.py +26 -0
  23. {tableauserverclient/models → models}/tableau_auth.py +13 -6
  24. {tableauserverclient/models → models}/user_item.py +10 -3
  25. {tableauserverclient/models → models}/workbook_item.py +2 -2
  26. {tableauserverclient/server → server}/endpoint/__init__.py +4 -0
  27. {tableauserverclient/server → server}/endpoint/datasources_endpoint.py +152 -22
  28. server/endpoint/extensions_endpoint.py +79 -0
  29. {tableauserverclient/server → server}/endpoint/flow_task_endpoint.py +1 -1
  30. {tableauserverclient/server → server}/endpoint/flows_endpoint.py +5 -4
  31. server/endpoint/oidc_endpoint.py +157 -0
  32. {tableauserverclient/server → server}/endpoint/projects_endpoint.py +12 -0
  33. {tableauserverclient/server → server}/endpoint/schedules_endpoint.py +48 -1
  34. {tableauserverclient/server → server}/endpoint/users_endpoint.py +274 -5
  35. {tableauserverclient/server → server}/endpoint/views_endpoint.py +23 -0
  36. {tableauserverclient/server → server}/endpoint/workbooks_endpoint.py +124 -9
  37. {tableauserverclient/server → server}/request_factory.py +281 -2
  38. {tableauserverclient/server → server}/request_options.py +12 -2
  39. {tableauserverclient/server → server}/server.py +4 -0
  40. {tableauserverclient-0.38.dist-info → tableauserverclient-0.39.dist-info}/METADATA +5 -26
  41. tableauserverclient-0.39.dist-info/RECORD +107 -0
  42. {tableauserverclient-0.38.dist-info → tableauserverclient-0.39.dist-info}/WHEEL +1 -1
  43. tableauserverclient-0.39.dist-info/top_level.txt +4 -0
  44. tableauserverclient/__init__.py +0 -145
  45. tableauserverclient/config.py +0 -27
  46. tableauserverclient/datetime_helpers.py +0 -45
  47. tableauserverclient/exponential_backoff.py +0 -30
  48. tableauserverclient/filesys_helpers.py +0 -63
  49. tableauserverclient/namespace.py +0 -37
  50. tableauserverclient/py.typed +0 -0
  51. tableauserverclient-0.38.dist-info/RECORD +0 -108
  52. tableauserverclient-0.38.dist-info/licenses/LICENSE.versioneer +0 -7
  53. tableauserverclient-0.38.dist-info/top_level.txt +0 -1
  54. {tableauserverclient/helpers → helpers}/__init__.py +0 -0
  55. {tableauserverclient/helpers → helpers}/headers.py +0 -0
  56. {tableauserverclient/helpers → helpers}/logging.py +0 -0
  57. {tableauserverclient/helpers → helpers}/strings.py +0 -0
  58. {tableauserverclient/models → models}/column_item.py +0 -0
  59. {tableauserverclient/models → models}/connection_credentials.py +0 -0
  60. {tableauserverclient/models → models}/data_acceleration_report_item.py +0 -0
  61. {tableauserverclient/models → models}/data_alert_item.py +0 -0
  62. {tableauserverclient/models → models}/database_item.py +0 -0
  63. {tableauserverclient/models → models}/dqw_item.py +0 -0
  64. {tableauserverclient/models → models}/exceptions.py +0 -0
  65. {tableauserverclient/models → models}/extract_item.py +0 -0
  66. {tableauserverclient/models → models}/fileupload_item.py +0 -0
  67. {tableauserverclient/models → models}/flow_run_item.py +0 -0
  68. {tableauserverclient/models → models}/job_item.py +0 -0
  69. {tableauserverclient/models → models}/linked_tasks_item.py +0 -0
  70. {tableauserverclient/models → models}/location_item.py +0 -0
  71. {tableauserverclient/models → models}/metric_item.py +0 -0
  72. {tableauserverclient/models → models}/pagination_item.py +0 -0
  73. {tableauserverclient/models → models}/revision_item.py +0 -0
  74. {tableauserverclient/models → models}/server_info_item.py +0 -0
  75. {tableauserverclient/models → models}/subscription_item.py +0 -0
  76. {tableauserverclient/models → models}/table_item.py +0 -0
  77. {tableauserverclient/models → models}/tableau_types.py +0 -0
  78. {tableauserverclient/models → models}/tag_item.py +0 -0
  79. {tableauserverclient/models → models}/target.py +0 -0
  80. {tableauserverclient/models → models}/task_item.py +0 -0
  81. {tableauserverclient/models → models}/view_item.py +0 -0
  82. {tableauserverclient/models → models}/virtual_connection_item.py +0 -0
  83. {tableauserverclient/models → models}/webhook_item.py +0 -0
  84. {tableauserverclient/server → server}/__init__.py +0 -0
  85. {tableauserverclient/server → server}/endpoint/auth_endpoint.py +0 -0
  86. {tableauserverclient/server → server}/endpoint/custom_views_endpoint.py +0 -0
  87. {tableauserverclient/server → server}/endpoint/data_acceleration_report_endpoint.py +0 -0
  88. {tableauserverclient/server → server}/endpoint/data_alert_endpoint.py +0 -0
  89. {tableauserverclient/server → server}/endpoint/databases_endpoint.py +0 -0
  90. {tableauserverclient/server → server}/endpoint/default_permissions_endpoint.py +0 -0
  91. {tableauserverclient/server → server}/endpoint/dqw_endpoint.py +0 -0
  92. {tableauserverclient/server → server}/endpoint/endpoint.py +0 -0
  93. {tableauserverclient/server → server}/endpoint/exceptions.py +0 -0
  94. {tableauserverclient/server → server}/endpoint/favorites_endpoint.py +0 -0
  95. {tableauserverclient/server → server}/endpoint/fileuploads_endpoint.py +0 -0
  96. {tableauserverclient/server → server}/endpoint/flow_runs_endpoint.py +0 -0
  97. {tableauserverclient/server → server}/endpoint/groups_endpoint.py +0 -0
  98. {tableauserverclient/server → server}/endpoint/groupsets_endpoint.py +0 -0
  99. {tableauserverclient/server → server}/endpoint/jobs_endpoint.py +0 -0
  100. {tableauserverclient/server → server}/endpoint/linked_tasks_endpoint.py +0 -0
  101. {tableauserverclient/server → server}/endpoint/metadata_endpoint.py +0 -0
  102. {tableauserverclient/server → server}/endpoint/metrics_endpoint.py +0 -0
  103. {tableauserverclient/server → server}/endpoint/permissions_endpoint.py +0 -0
  104. {tableauserverclient/server → server}/endpoint/resource_tagger.py +0 -0
  105. {tableauserverclient/server → server}/endpoint/server_info_endpoint.py +0 -0
  106. {tableauserverclient/server → server}/endpoint/sites_endpoint.py +0 -0
  107. {tableauserverclient/server → server}/endpoint/subscriptions_endpoint.py +0 -0
  108. {tableauserverclient/server → server}/endpoint/tables_endpoint.py +0 -0
  109. {tableauserverclient/server → server}/endpoint/tasks_endpoint.py +0 -0
  110. {tableauserverclient/server → server}/endpoint/virtual_connections_endpoint.py +0 -0
  111. {tableauserverclient/server → server}/endpoint/webhooks_endpoint.py +0 -0
  112. {tableauserverclient/server → server}/exceptions.py +0 -0
  113. {tableauserverclient/server → server}/filter.py +0 -0
  114. {tableauserverclient/server → server}/pager.py +0 -0
  115. {tableauserverclient/server → server}/query.py +0 -0
  116. {tableauserverclient/server → server}/sort.py +0 -0
  117. {tableauserverclient-0.38.dist-info → tableauserverclient-0.39.dist-info}/licenses/LICENSE +0 -0
@@ -6,8 +6,8 @@ import os
6
6
 
7
7
  from contextlib import closing
8
8
  from pathlib import Path
9
- from typing import Literal, Optional, TYPE_CHECKING, Union, overload
10
- from collections.abc import Iterable, Mapping, Sequence
9
+ from typing import Literal, Optional, TYPE_CHECKING, TypedDict, TypeVar, Union, overload
10
+ from collections.abc import Iterable, Sequence
11
11
 
12
12
  from tableauserverclient.helpers.headers import fix_filename
13
13
  from tableauserverclient.models.dqw_item import DQWItem
@@ -50,13 +50,50 @@ FilePath = Union[str, os.PathLike]
50
50
  FileObject = Union[io.BufferedReader, io.BytesIO]
51
51
  PathOrFile = Union[FilePath, FileObject]
52
52
 
53
- FilePath = Union[str, os.PathLike]
54
53
  FileObjectR = Union[io.BufferedReader, io.BytesIO]
55
54
  FileObjectW = Union[io.BufferedWriter, io.BytesIO]
56
55
  PathOrFileR = Union[FilePath, FileObjectR]
57
56
  PathOrFileW = Union[FilePath, FileObjectW]
58
57
 
59
58
 
59
+ HyperActionCondition = TypedDict(
60
+ "HyperActionCondition",
61
+ {
62
+ "op": str,
63
+ "target-col": str,
64
+ "source-col": str,
65
+ },
66
+ )
67
+
68
+ HyperActionRow = TypedDict(
69
+ "HyperActionRow",
70
+ {
71
+ "action": Literal[
72
+ "update",
73
+ "upsert",
74
+ "delete",
75
+ ],
76
+ "source-table": str,
77
+ "target-table": str,
78
+ "condition": HyperActionCondition,
79
+ },
80
+ )
81
+
82
+ HyperActionTable = TypedDict(
83
+ "HyperActionTable",
84
+ {
85
+ "action": Literal[
86
+ "insert",
87
+ "replace",
88
+ ],
89
+ "source-table": str,
90
+ "target-table": str,
91
+ },
92
+ )
93
+
94
+ HyperAction = Union[HyperActionTable, HyperActionRow]
95
+
96
+
60
97
  class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]):
61
98
  def __init__(self, parent_srv: "Server") -> None:
62
99
  super().__init__(parent_srv)
@@ -191,16 +228,34 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
191
228
  self.delete_request(url)
192
229
  logger.info(f"Deleted single datasource (ID: {datasource_id})")
193
230
 
231
+ T = TypeVar("T", bound=FileObjectW)
232
+
233
+ @overload
234
+ def download(
235
+ self,
236
+ datasource_id: str,
237
+ filepath: T,
238
+ include_extract: bool = True,
239
+ ) -> T: ...
240
+
241
+ @overload
242
+ def download(
243
+ self,
244
+ datasource_id: str,
245
+ filepath: Optional[FilePath] = None,
246
+ include_extract: bool = True,
247
+ ) -> str: ...
248
+
194
249
  # Download 1 datasource by id
195
250
  @api(version="2.0")
196
251
  @parameter_added_in(no_extract="2.5")
197
252
  @parameter_added_in(include_extract="2.5")
198
253
  def download(
199
254
  self,
200
- datasource_id: str,
201
- filepath: Optional[PathOrFileW] = None,
202
- include_extract: bool = True,
203
- ) -> PathOrFileW:
255
+ datasource_id,
256
+ filepath=None,
257
+ include_extract=True,
258
+ ):
204
259
  """
205
260
  Downloads the specified data source from a site. The data source is
206
261
  downloaded as a .tdsx file.
@@ -319,8 +374,63 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
319
374
  logger.info(f"Updated datasource item (ID: {datasource_item.id} & connection item {connection_item.id}")
320
375
  return connection
321
376
 
377
+ @api(version="3.26")
378
+ def update_connections(
379
+ self,
380
+ datasource_item: DatasourceItem,
381
+ connection_luids: Iterable[str],
382
+ authentication_type: str,
383
+ username: Optional[str] = None,
384
+ password: Optional[str] = None,
385
+ embed_password: Optional[bool] = None,
386
+ ) -> list[ConnectionItem]:
387
+ """
388
+ Bulk updates one or more datasource connections by LUID.
389
+
390
+ Parameters
391
+ ----------
392
+ datasource_item : DatasourceItem
393
+ The datasource item containing the connections.
394
+
395
+ connection_luids : Iterable of str
396
+ The connection LUIDs to update.
397
+
398
+ authentication_type : str
399
+ The authentication type to use (e.g., 'auth-keypair').
400
+
401
+ username : str, optional
402
+ The username to set.
403
+
404
+ password : str, optional
405
+ The password or secret to set.
406
+
407
+ embed_password : bool, optional
408
+ Whether to embed the password.
409
+
410
+ Returns
411
+ -------
412
+ Iterable of str
413
+ The connection LUIDs that were updated.
414
+ """
415
+
416
+ url = f"{self.baseurl}/{datasource_item.id}/connections"
417
+
418
+ request_body = RequestFactory.Datasource.update_connections_req(
419
+ connection_luids=connection_luids,
420
+ authentication_type=authentication_type,
421
+ username=username,
422
+ password=password,
423
+ embed_password=embed_password,
424
+ )
425
+ server_response = self.put_request(url, request_body)
426
+ connection_items = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)
427
+ updated_ids: list[str] = [conn.id for conn in connection_items]
428
+
429
+ logger.info(f"Updated connections for datasource {datasource_item.id}: {', '.join(updated_ids)}")
430
+ return connection_items
431
+
322
432
  @api(version="2.8")
323
- def refresh(self, datasource_item: DatasourceItem, incremental: bool = False) -> JobItem:
433
+ def refresh(self, datasource_item: Union[DatasourceItem, str], incremental: bool = False) -> JobItem:
324
434
  """
325
435
  Refreshes the extract of an existing workbook.
326
436
 
@@ -328,8 +438,8 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
328
438
 
329
439
  Parameters
330
440
  ----------
331
- workbook_item : WorkbookItem | str
332
- The workbook item or workbook ID.
441
+ workbook_item : DatasourceItem | str
442
+ The datasource item or datasource ID.
333
443
  incremental: bool
334
444
  Whether to do a full refresh or incremental refresh of the extract data
335
445
 
@@ -424,13 +534,13 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
424
534
  @parameter_added_in(as_job="3.0")
425
535
  def publish(
426
536
  self,
427
- datasource_item: DatasourceItem,
428
- file: PathOrFileR,
429
- mode: str,
430
- connection_credentials: Optional[ConnectionCredentials] = None,
431
- connections: Optional[Sequence[ConnectionItem]] = None,
432
- as_job: bool = False,
433
- ) -> Union[DatasourceItem, JobItem]:
537
+ datasource_item,
538
+ file,
539
+ mode,
540
+ connection_credentials=None,
541
+ connections=None,
542
+ as_job=False,
543
+ ):
434
544
  """
435
545
  Publishes a data source to a server, or appends data to an existing
436
546
  data source.
@@ -576,7 +686,7 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
576
686
  datasource_or_connection_item: Union[DatasourceItem, ConnectionItem, str],
577
687
  *,
578
688
  request_id: str,
579
- actions: Sequence[Mapping],
689
+ actions: Sequence[HyperAction],
580
690
  payload: Optional[FilePath] = None,
581
691
  ) -> JobItem:
582
692
  """
@@ -843,15 +953,35 @@ class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]
843
953
  revisions = RevisionItem.from_response(server_response.content, self.parent_srv.namespace, datasource_item)
844
954
  return revisions
845
955
 
846
- # Download 1 datasource revision by revision number
847
- @api(version="2.3")
956
+ T = TypeVar("T", bound=FileObjectW)
957
+
958
+ @overload
848
959
  def download_revision(
849
960
  self,
850
961
  datasource_id: str,
851
962
  revision_number: Optional[str],
852
- filepath: Optional[PathOrFileW] = None,
963
+ filepath: T,
853
964
  include_extract: bool = True,
854
- ) -> PathOrFileW:
965
+ ) -> T: ...
966
+
967
+ @overload
968
+ def download_revision(
969
+ self,
970
+ datasource_id: str,
971
+ revision_number: Optional[str],
972
+ filepath: Optional[FilePath] = None,
973
+ include_extract: bool = True,
974
+ ) -> str: ...
975
+
976
+ # Download 1 datasource revision by revision number
977
+ @api(version="2.3")
978
+ def download_revision(
979
+ self,
980
+ datasource_id,
981
+ revision_number,
982
+ filepath=None,
983
+ include_extract=True,
984
+ ):
855
985
  """
856
986
  Downloads a specific version of a data source prior to the current one
857
987
  in .tdsx format. To download the current version of a data source set
@@ -0,0 +1,79 @@
1
+ from tableauserverclient.models.extensions_item import ExtensionsServer, ExtensionsSiteSettings
2
+ from tableauserverclient.server.endpoint.endpoint import Endpoint
3
+ from tableauserverclient.server.endpoint.endpoint import api
4
+ from tableauserverclient.server.request_factory import RequestFactory
5
+
6
+
7
+ class Extensions(Endpoint):
8
+ def __init__(self, parent_srv):
9
+ super().__init__(parent_srv)
10
+
11
+ @property
12
+ def _server_baseurl(self) -> str:
13
+ return f"{self.parent_srv.baseurl}/settings/extensions"
14
+
15
+ @property
16
+ def baseurl(self) -> str:
17
+ return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/settings/extensions"
18
+
19
+ @api(version="3.21")
20
+ def get_server_settings(self) -> ExtensionsServer:
21
+ """Lists the settings for extensions of a server
22
+
23
+ Returns
24
+ -------
25
+ ExtensionsServer
26
+ The server extensions settings
27
+ """
28
+ response = self.get_request(self._server_baseurl)
29
+ return ExtensionsServer.from_response(response.content, self.parent_srv.namespace)
30
+
31
+ @api(version="3.21")
32
+ def update_server_settings(self, extensions_server: ExtensionsServer) -> ExtensionsServer:
33
+ """Updates the settings for extensions of a server. Overwrites all existing settings. Any
34
+ sites omitted from the block list will be unblocked.
35
+
36
+ Parameters
37
+ ----------
38
+ extensions_server : ExtensionsServer
39
+ The server extensions settings to update
40
+
41
+ Returns
42
+ -------
43
+ ExtensionsServer
44
+ The updated server extensions settings
45
+ """
46
+ req = RequestFactory.Extensions.update_server_extensions(extensions_server)
47
+ response = self.put_request(self._server_baseurl, req)
48
+ return ExtensionsServer.from_response(response.content, self.parent_srv.namespace)
49
+
50
+ @api(version="3.21")
51
+ def get(self) -> ExtensionsSiteSettings:
52
+ """Lists the extensions settings for the site
53
+
54
+ Returns
55
+ -------
56
+ ExtensionsSiteSettings
57
+ The site extensions settings
58
+ """
59
+ response = self.get_request(self.baseurl)
60
+ return ExtensionsSiteSettings.from_response(response.content, self.parent_srv.namespace)
61
+
62
+ @api(version="3.21")
63
+ def update(self, extensions_site_settings: ExtensionsSiteSettings) -> ExtensionsSiteSettings:
64
+ """Updates the extensions settings for the site. Any extensions omitted
65
+ from the safe extensions list will be removed.
66
+
67
+ Parameters
68
+ ----------
69
+ extensions_site_settings : ExtensionsSiteSettings
70
+ The site extensions settings to update
71
+
72
+ Returns
73
+ -------
74
+ ExtensionsSiteSettings
75
+ The updated site extensions settings
76
+ """
77
+ req = RequestFactory.Extensions.update_site_extensions(extensions_site_settings)
78
+ response = self.put_request(self.baseurl, req)
79
+ return ExtensionsSiteSettings.from_response(response.content, self.parent_srv.namespace)
@@ -18,7 +18,7 @@ class FlowTasks(Endpoint):
18
18
  return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/tasks/flows"
19
19
 
20
20
  @api(version="3.22")
21
- def create(self, flow_item: TaskItem) -> TaskItem:
21
+ def create(self, flow_item: TaskItem) -> bytes:
22
22
  if not flow_item:
23
23
  error = "No flow provided"
24
24
  raise ValueError(error)
@@ -308,7 +308,7 @@ class Flows(QuerysetEndpoint[FlowItem], TaggingMixin[FlowItem]):
308
308
  return connection
309
309
 
310
310
  @api(version="3.3")
311
- def refresh(self, flow_item: FlowItem) -> JobItem:
311
+ def refresh(self, flow_item: Union[FlowItem, str]) -> JobItem:
312
312
  """
313
313
  Runs the flow to refresh the data.
314
314
 
@@ -316,15 +316,16 @@ class Flows(QuerysetEndpoint[FlowItem], TaggingMixin[FlowItem]):
316
316
 
317
317
  Parameters
318
318
  ----------
319
- flow_item: FlowItem
320
- The flow item to refresh.
319
+ flow_item: FlowItem | str
320
+ The FlowItem or str of the flow id to refresh.
321
321
 
322
322
  Returns
323
323
  -------
324
324
  JobItem
325
325
  The job item that was created to refresh the flow.
326
326
  """
327
- url = f"{self.baseurl}/{flow_item.id}/run"
327
+ flow_id = getattr(flow_item, "id", flow_item)
328
+ url = f"{self.baseurl}/{flow_id}/run"
328
329
  empty_req = RequestFactory.Empty.empty_req()
329
330
  server_response = self.post_request(url, empty_req)
330
331
  new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
@@ -0,0 +1,157 @@
1
+ from typing import Protocol, Union, TYPE_CHECKING
2
+ from tableauserverclient.models.oidc_item import SiteOIDCConfiguration
3
+ from tableauserverclient.server.endpoint import Endpoint
4
+ from tableauserverclient.server.request_factory import RequestFactory
5
+ from tableauserverclient.server.endpoint.endpoint import api
6
+
7
+ if TYPE_CHECKING:
8
+ from tableauserverclient.models.site_item import SiteAuthConfiguration
9
+ from tableauserverclient.server.server import Server
10
+
11
+
12
+ class IDPAttributes(Protocol):
13
+ idp_configuration_id: str
14
+
15
+
16
+ class IDPProperty(Protocol):
17
+ @property
18
+ def idp_configuration_id(self) -> str: ...
19
+
20
+
21
+ HasIdpConfigurationID = Union[str, IDPAttributes]
22
+
23
+
24
+ class OIDC(Endpoint):
25
+ def __init__(self, server: "Server") -> None:
26
+ self.parent_srv = server
27
+
28
+ @property
29
+ def baseurl(self) -> str:
30
+ return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/site-oidc-configuration"
31
+
32
+ @api(version="3.24")
33
+ def get(self) -> list["SiteAuthConfiguration"]:
34
+ """
35
+ Get all OpenID Connect (OIDC) configurations for the currently
36
+ authenticated Tableau Cloud site. To get all of the configuration
37
+ details, use the get_by_id method.
38
+
39
+ REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_identity_pools.htm#AuthnService_ListAuthConfigurations
40
+
41
+ Returns
42
+ -------
43
+ list[SiteAuthConfiguration]
44
+ """
45
+ return self.parent_srv.sites.list_auth_configurations()
46
+
47
+ @api(version="3.24")
48
+ def get_by_id(self, id: Union[str, HasIdpConfigurationID]) -> SiteOIDCConfiguration:
49
+ """
50
+ Get details about a specific OpenID Connect (OIDC) configuration on the
51
+ current Tableau Cloud site. Only retrieves configurations for the
52
+ currently authenticated site.
53
+
54
+ REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_openid_connect.htm#get_openid_connect_configuration
55
+
56
+ Parameters
57
+ ----------
58
+ id : Union[str, HasID]
59
+ The ID of the OIDC configuration to retrieve. Can be either the
60
+ ID string or an object with an id attribute.
61
+
62
+ Returns
63
+ -------
64
+ SiteOIDCConfiguration
65
+ The OIDC configuration for the specified site.
66
+ """
67
+ target = getattr(id, "idp_configuration_id", id)
68
+ url = f"{self.baseurl}/{target}"
69
+ response = self.get_request(url)
70
+ return SiteOIDCConfiguration.from_response(response.content, self.parent_srv.namespace)
71
+
72
+ @api(version="3.22")
73
+ def create(self, config_item: SiteOIDCConfiguration) -> SiteOIDCConfiguration:
74
+ """
75
+ Create the OpenID Connect (OIDC) configuration for the currently
76
+ authenticated Tableau Cloud site. The config_item must have the
77
+ following attributes set, others are optional:
78
+
79
+ idp_configuration_name
80
+ client_id
81
+ client_secret
82
+ authorization_endpoint
83
+ token_endpoint
84
+ userinfo_endpoint
85
+ enabled
86
+ jwks_uri
87
+
88
+ The secret in the returned config will be masked.
89
+
90
+ REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_openid_connect.htm#create_openid_connect_configuration
91
+
92
+ Parameters
93
+ ----------
94
+ config : SiteOIDCConfiguration
95
+ The OIDC configuration to create.
96
+
97
+ Returns
98
+ -------
99
+ SiteOIDCConfiguration
100
+ The created OIDC configuration.
101
+ """
102
+ url = self.baseurl
103
+ create_req = RequestFactory.OIDC.create_req(config_item)
104
+ response = self.put_request(url, create_req)
105
+ return SiteOIDCConfiguration.from_response(response.content, self.parent_srv.namespace)
106
+
107
+ @api(version="3.24")
108
+ def delete_configuration(self, config: Union[str, HasIdpConfigurationID]) -> None:
109
+ """
110
+ Delete the OpenID Connect (OIDC) configuration for the currently
111
+ authenticated Tableau Cloud site. The config parameter can be either
112
+ the ID of the configuration or the configuration object itself.
113
+
114
+ **Important**: Before removing the OIDC configuration, make sure that
115
+ users who are set to authenticate with OIDC are set to use a different
116
+ authentication type. Users who are not set with a different
117
+ authentication type before removing the OIDC configuration will not be
118
+ able to sign in to Tableau Cloud.
119
+
120
+
121
+ REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_openid_connect.htm#remove_openid_connect_configuration
122
+
123
+ Parameters
124
+ ----------
125
+ config : Union[str, HasID]
126
+ The OIDC configuration to delete. Can be either the ID of the
127
+ configuration or the configuration object itself.
128
+ """
129
+
130
+ target = getattr(config, "idp_configuration_id", config)
131
+
132
+ url = f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/disable-site-oidc-configuration?idpConfigurationId={target}"
133
+ _ = self.put_request(url)
134
+ return None
135
+
136
+ @api(version="3.22")
137
+ def update(self, config: SiteOIDCConfiguration) -> SiteOIDCConfiguration:
138
+ """
139
+ Update the Tableau Cloud site's OpenID Connect (OIDC) configuration. The
140
+ secret in the returned config will be masked.
141
+
142
+ REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_openid_connect.htm#update_openid_connect_configuration
143
+
144
+ Parameters
145
+ ----------
146
+ config : SiteOIDCConfiguration
147
+ The OIDC configuration to update. Must have the id attribute set.
148
+
149
+ Returns
150
+ -------
151
+ SiteOIDCConfiguration
152
+ The updated OIDC configuration.
153
+ """
154
+ url = f"{self.baseurl}/{config.idp_configuration_id}"
155
+ update_req = RequestFactory.OIDC.update_req(config)
156
+ response = self.put_request(url, update_req)
157
+ return SiteOIDCConfiguration.from_response(response.content, self.parent_srv.namespace)
@@ -89,6 +89,18 @@ class Projects(QuerysetEndpoint[ProjectItem]):
89
89
  self.delete_request(url)
90
90
  logger.info(f"Deleted single project (ID: {project_id})")
91
91
 
92
+ @api(version="2.0")
93
+ def get_by_id(self, project_id: str) -> ProjectItem:
94
+ """
95
+ Fetch a project by ID. This is a convenience method making up for a gap in the server API.
96
+ It uses the same endpoint as the update method, but without the ability to update the project.
97
+ """
98
+ if not project_id:
99
+ error = "Project ID undefined."
100
+ raise ValueError(error)
101
+ project = ProjectItem(id=project_id)
102
+ return self.update(project, samples=False)
103
+
92
104
  @api(version="2.0")
93
105
  def update(self, project_item: ProjectItem, samples: bool = False) -> ProjectItem:
94
106
  """
@@ -1,13 +1,15 @@
1
+ from collections.abc import Iterable
1
2
  import copy
2
3
  import logging
3
4
  import warnings
4
5
  from collections import namedtuple
5
- from typing import TYPE_CHECKING, Callable, Optional, Union
6
+ from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Union, overload
6
7
 
7
8
  from .endpoint import Endpoint, api, parameter_added_in
8
9
  from .exceptions import MissingRequiredFieldError
9
10
  from tableauserverclient.server import RequestFactory
10
11
  from tableauserverclient.models import PaginationItem, ScheduleItem, TaskItem, ExtractItem
12
+ from tableauserverclient.models.schedule_item import parse_batch_schedule_state
11
13
 
12
14
  from tableauserverclient.helpers.logging import logger
13
15
 
@@ -279,3 +281,48 @@ class Schedules(Endpoint):
279
281
  extract_items = ExtractItem.from_response(server_response.content, self.parent_srv.namespace)
280
282
 
281
283
  return extract_items, pagination_item
284
+
285
+ @overload
286
+ def batch_update_state(
287
+ self,
288
+ schedules: Iterable[ScheduleItem | str],
289
+ state: Literal["active", "suspended"],
290
+ update_all: Literal[False] = False,
291
+ ) -> list[str]: ...
292
+
293
+ @overload
294
+ def batch_update_state(
295
+ self, schedules: Any, state: Literal["active", "suspended"], update_all: Literal[True]
296
+ ) -> list[str]: ...
297
+
298
+ @api(version="3.27")
299
+ def batch_update_state(self, schedules, state, update_all=False) -> list[str]:
300
+ """
301
+ Batch update the status of one or more scheudles. If update_all is set,
302
+ all schedules on the Tableau Server are affected.
303
+
304
+ Parameters
305
+ ----------
306
+ schedules: Iterable[ScheudleItem | str] | Any
307
+ The schedules to be updated. If update_all=True, this is ignored.
308
+
309
+ state: Literal["active", "suspended"]
310
+ The state of the schedules, whether active or suspended.
311
+
312
+ update_all: bool
313
+ Whether or not to apply the status to all schedules.
314
+
315
+ Returns
316
+ -------
317
+ List[str]
318
+ The IDs of the affected schedules.
319
+ """
320
+ params = {"state": state}
321
+ if update_all:
322
+ params["updateAll"] = "true"
323
+ payload = RequestFactory.Empty.empty_req()
324
+ else:
325
+ payload = RequestFactory.Schedule.batch_update_state(schedules)
326
+
327
+ response = self.put_request(self.baseurl, payload, parameters={"params": params})
328
+ return parse_batch_schedule_state(response, self.parent_srv.namespace)