tableauserverclient 0.37__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.
- tableauserverclient/bin/_version.py → _version.py +3 -3
- bin/__init__.py +3 -0
- bin/_version.py +21 -0
- {tableauserverclient/helpers → helpers}/strings.py +25 -1
- {tableauserverclient/models → models}/__init__.py +15 -1
- models/collection_item.py +52 -0
- {tableauserverclient/models → models}/connection_item.py +16 -2
- {tableauserverclient/models → models}/custom_view_item.py +8 -0
- {tableauserverclient/models → models}/data_freshness_policy_item.py +3 -3
- {tableauserverclient/models → models}/datasource_item.py +113 -3
- models/extensions_item.py +186 -0
- models/extract_item.py +82 -0
- {tableauserverclient/models → models}/favorites_item.py +21 -8
- {tableauserverclient/models → models}/flow_item.py +3 -3
- {tableauserverclient/models → models}/group_item.py +18 -1
- {tableauserverclient/models → models}/groupset_item.py +14 -0
- {tableauserverclient/models → models}/interval_item.py +42 -1
- models/location_item.py +53 -0
- models/oidc_item.py +82 -0
- {tableauserverclient/models → models}/permissions_item.py +2 -0
- {tableauserverclient/models → models}/project_item.py +141 -29
- {tableauserverclient/models → models}/property_decorators.py +2 -2
- {tableauserverclient/models → models}/reference_item.py +12 -6
- {tableauserverclient/models → models}/schedule_item.py +67 -1
- {tableauserverclient/models → models}/site_item.py +54 -0
- {tableauserverclient/models → models}/table_item.py +7 -3
- {tableauserverclient/models → models}/tableau_auth.py +13 -6
- {tableauserverclient/models → models}/tableau_types.py +13 -1
- {tableauserverclient/models → models}/user_item.py +111 -4
- {tableauserverclient/models → models}/view_item.py +79 -5
- {tableauserverclient/models → models}/workbook_item.py +153 -3
- {tableauserverclient/server → server}/endpoint/__init__.py +4 -0
- {tableauserverclient/server → server}/endpoint/databases_endpoint.py +101 -18
- {tableauserverclient/server → server}/endpoint/datasources_endpoint.py +155 -25
- {tableauserverclient/server → server}/endpoint/dqw_endpoint.py +16 -6
- {tableauserverclient/server → server}/endpoint/endpoint.py +39 -0
- server/endpoint/extensions_endpoint.py +79 -0
- {tableauserverclient/server → server}/endpoint/flow_task_endpoint.py +1 -1
- {tableauserverclient/server → server}/endpoint/flows_endpoint.py +5 -4
- server/endpoint/oidc_endpoint.py +157 -0
- {tableauserverclient/server → server}/endpoint/projects_endpoint.py +12 -0
- server/endpoint/schedules_endpoint.py +328 -0
- {tableauserverclient/server → server}/endpoint/sites_endpoint.py +18 -1
- {tableauserverclient/server → server}/endpoint/tables_endpoint.py +140 -17
- {tableauserverclient/server → server}/endpoint/users_endpoint.py +296 -10
- {tableauserverclient/server → server}/endpoint/views_endpoint.py +23 -0
- {tableauserverclient/server → server}/endpoint/workbooks_endpoint.py +124 -9
- {tableauserverclient/server → server}/query.py +36 -0
- {tableauserverclient/server → server}/request_factory.py +286 -2
- {tableauserverclient/server → server}/request_options.py +139 -3
- {tableauserverclient/server → server}/server.py +46 -0
- {tableauserverclient-0.37.dist-info → tableauserverclient-0.39.dist-info}/METADATA +5 -26
- tableauserverclient-0.39.dist-info/RECORD +107 -0
- {tableauserverclient-0.37.dist-info → tableauserverclient-0.39.dist-info}/WHEEL +1 -1
- tableauserverclient-0.39.dist-info/top_level.txt +4 -0
- tableauserverclient/__init__.py +0 -141
- tableauserverclient/config.py +0 -27
- tableauserverclient/datetime_helpers.py +0 -45
- tableauserverclient/exponential_backoff.py +0 -30
- tableauserverclient/filesys_helpers.py +0 -63
- tableauserverclient/namespace.py +0 -37
- tableauserverclient/py.typed +0 -0
- tableauserverclient/server/endpoint/schedules_endpoint.py +0 -151
- tableauserverclient-0.37.dist-info/RECORD +0 -106
- tableauserverclient-0.37.dist-info/licenses/LICENSE.versioneer +0 -7
- tableauserverclient-0.37.dist-info/top_level.txt +0 -1
- {tableauserverclient/helpers → helpers}/__init__.py +0 -0
- {tableauserverclient/helpers → helpers}/headers.py +0 -0
- {tableauserverclient/helpers → helpers}/logging.py +0 -0
- {tableauserverclient/models → models}/column_item.py +0 -0
- {tableauserverclient/models → models}/connection_credentials.py +0 -0
- {tableauserverclient/models → models}/data_acceleration_report_item.py +0 -0
- {tableauserverclient/models → models}/data_alert_item.py +0 -0
- {tableauserverclient/models → models}/database_item.py +0 -0
- {tableauserverclient/models → models}/dqw_item.py +0 -0
- {tableauserverclient/models → models}/exceptions.py +0 -0
- {tableauserverclient/models → models}/fileupload_item.py +0 -0
- {tableauserverclient/models → models}/flow_run_item.py +0 -0
- {tableauserverclient/models → models}/job_item.py +0 -0
- {tableauserverclient/models → models}/linked_tasks_item.py +0 -0
- {tableauserverclient/models → models}/metric_item.py +0 -0
- {tableauserverclient/models → models}/pagination_item.py +0 -0
- {tableauserverclient/models → models}/revision_item.py +0 -0
- {tableauserverclient/models → models}/server_info_item.py +0 -0
- {tableauserverclient/models → models}/subscription_item.py +0 -0
- {tableauserverclient/models → models}/tag_item.py +0 -0
- {tableauserverclient/models → models}/target.py +0 -0
- {tableauserverclient/models → models}/task_item.py +0 -0
- {tableauserverclient/models → models}/virtual_connection_item.py +0 -0
- {tableauserverclient/models → models}/webhook_item.py +0 -0
- {tableauserverclient/server → server}/__init__.py +0 -0
- {tableauserverclient/server → server}/endpoint/auth_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/custom_views_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/data_acceleration_report_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/data_alert_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/default_permissions_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/exceptions.py +0 -0
- {tableauserverclient/server → server}/endpoint/favorites_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/fileuploads_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/flow_runs_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/groups_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/groupsets_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/jobs_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/linked_tasks_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/metadata_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/metrics_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/permissions_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/resource_tagger.py +0 -0
- {tableauserverclient/server → server}/endpoint/server_info_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/subscriptions_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/tasks_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/virtual_connections_endpoint.py +0 -0
- {tableauserverclient/server → server}/endpoint/webhooks_endpoint.py +0 -0
- {tableauserverclient/server → server}/exceptions.py +0 -0
- {tableauserverclient/server → server}/filter.py +0 -0
- {tableauserverclient/server → server}/pager.py +0 -0
- {tableauserverclient/server → server}/sort.py +0 -0
- {tableauserverclient-0.37.dist-info → tableauserverclient-0.39.dist-info}/licenses/LICENSE +0 -0
|
@@ -30,9 +30,12 @@ from tableauserverclient.models import WorkbookItem, ConnectionItem, ViewItem, P
|
|
|
30
30
|
from tableauserverclient.server import RequestFactory
|
|
31
31
|
|
|
32
32
|
from typing import (
|
|
33
|
+
Literal,
|
|
33
34
|
Optional,
|
|
34
35
|
TYPE_CHECKING,
|
|
36
|
+
TypeVar,
|
|
35
37
|
Union,
|
|
38
|
+
overload,
|
|
36
39
|
)
|
|
37
40
|
from collections.abc import Iterable, Sequence
|
|
38
41
|
|
|
@@ -325,16 +328,92 @@ class Workbooks(QuerysetEndpoint[WorkbookItem], TaggingMixin[WorkbookItem]):
|
|
|
325
328
|
logger.info(f"Updated workbook item (ID: {workbook_item.id} & connection item {connection_item.id})")
|
|
326
329
|
return connection
|
|
327
330
|
|
|
331
|
+
# Update workbook_connections
|
|
332
|
+
@api(version="3.26")
|
|
333
|
+
def update_connections(
|
|
334
|
+
self,
|
|
335
|
+
workbook_item: WorkbookItem,
|
|
336
|
+
connection_luids: Iterable[str],
|
|
337
|
+
authentication_type: str,
|
|
338
|
+
username: Optional[str] = None,
|
|
339
|
+
password: Optional[str] = None,
|
|
340
|
+
embed_password: Optional[bool] = None,
|
|
341
|
+
) -> list[ConnectionItem]:
|
|
342
|
+
"""
|
|
343
|
+
Bulk updates one or more workbook connections by LUID, including authenticationType, username, password, and embedPassword.
|
|
344
|
+
|
|
345
|
+
Parameters
|
|
346
|
+
----------
|
|
347
|
+
workbook_item : WorkbookItem
|
|
348
|
+
The workbook item containing the connections.
|
|
349
|
+
|
|
350
|
+
connection_luids : Iterable of str
|
|
351
|
+
The connection LUIDs to update.
|
|
352
|
+
|
|
353
|
+
authentication_type : str
|
|
354
|
+
The authentication type to use (e.g., 'AD Service Principal').
|
|
355
|
+
|
|
356
|
+
username : str, optional
|
|
357
|
+
The username to set (e.g., client ID for keypair auth).
|
|
358
|
+
|
|
359
|
+
password : str, optional
|
|
360
|
+
The password or secret to set.
|
|
361
|
+
|
|
362
|
+
embed_password : bool, optional
|
|
363
|
+
Whether to embed the password.
|
|
364
|
+
|
|
365
|
+
Returns
|
|
366
|
+
-------
|
|
367
|
+
Iterable of str
|
|
368
|
+
The connection LUIDs that were updated.
|
|
369
|
+
"""
|
|
370
|
+
|
|
371
|
+
url = f"{self.baseurl}/{workbook_item.id}/connections"
|
|
372
|
+
|
|
373
|
+
request_body = RequestFactory.Workbook.update_connections_req(
|
|
374
|
+
connection_luids,
|
|
375
|
+
authentication_type,
|
|
376
|
+
username=username,
|
|
377
|
+
password=password,
|
|
378
|
+
embed_password=embed_password,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
# Send request
|
|
382
|
+
server_response = self.put_request(url, request_body)
|
|
383
|
+
connection_items = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
384
|
+
updated_ids: list[str] = [conn.id for conn in connection_items]
|
|
385
|
+
|
|
386
|
+
logger.info(f"Updated connections for workbook {workbook_item.id}: {', '.join(updated_ids)}")
|
|
387
|
+
return connection_items
|
|
388
|
+
|
|
389
|
+
T = TypeVar("T", bound=FileObjectW)
|
|
390
|
+
|
|
391
|
+
@overload
|
|
392
|
+
def download(
|
|
393
|
+
self,
|
|
394
|
+
workbook_id: str,
|
|
395
|
+
filepath: T,
|
|
396
|
+
include_extract: bool = True,
|
|
397
|
+
) -> T: ...
|
|
398
|
+
|
|
399
|
+
@overload
|
|
400
|
+
def download(
|
|
401
|
+
self,
|
|
402
|
+
workbook_id: str,
|
|
403
|
+
filepath: Optional[FilePath] = None,
|
|
404
|
+
include_extract: bool = True,
|
|
405
|
+
) -> str: ...
|
|
406
|
+
|
|
328
407
|
# Download workbook contents with option of passing in filepath
|
|
329
408
|
@api(version="2.0")
|
|
330
409
|
@parameter_added_in(no_extract="2.5")
|
|
331
410
|
@parameter_added_in(include_extract="2.5")
|
|
332
411
|
def download(
|
|
333
412
|
self,
|
|
334
|
-
workbook_id
|
|
335
|
-
filepath
|
|
336
|
-
include_extract
|
|
337
|
-
)
|
|
413
|
+
workbook_id,
|
|
414
|
+
filepath=None,
|
|
415
|
+
include_extract=True,
|
|
416
|
+
):
|
|
338
417
|
"""
|
|
339
418
|
Downloads a workbook to the specified directory (optional).
|
|
340
419
|
|
|
@@ -683,6 +762,30 @@ class Workbooks(QuerysetEndpoint[WorkbookItem], TaggingMixin[WorkbookItem]):
|
|
|
683
762
|
"""
|
|
684
763
|
return self._permissions.delete(item, capability_item)
|
|
685
764
|
|
|
765
|
+
@overload
|
|
766
|
+
def publish(
|
|
767
|
+
self,
|
|
768
|
+
workbook_item: WorkbookItem,
|
|
769
|
+
file: PathOrFileR,
|
|
770
|
+
mode: str,
|
|
771
|
+
connections: Optional[Sequence[ConnectionItem]],
|
|
772
|
+
as_job: Literal[False],
|
|
773
|
+
skip_connection_check: bool,
|
|
774
|
+
parameters=None,
|
|
775
|
+
) -> WorkbookItem: ...
|
|
776
|
+
|
|
777
|
+
@overload
|
|
778
|
+
def publish(
|
|
779
|
+
self,
|
|
780
|
+
workbook_item: WorkbookItem,
|
|
781
|
+
file: PathOrFileR,
|
|
782
|
+
mode: str,
|
|
783
|
+
connections: Optional[Sequence[ConnectionItem]],
|
|
784
|
+
as_job: Literal[True],
|
|
785
|
+
skip_connection_check: bool,
|
|
786
|
+
parameters=None,
|
|
787
|
+
) -> JobItem: ...
|
|
788
|
+
|
|
686
789
|
@api(version="2.0")
|
|
687
790
|
@parameter_added_in(as_job="3.0")
|
|
688
791
|
@parameter_added_in(connections="2.8")
|
|
@@ -919,15 +1022,27 @@ class Workbooks(QuerysetEndpoint[WorkbookItem], TaggingMixin[WorkbookItem]):
|
|
|
919
1022
|
revisions = RevisionItem.from_response(server_response.content, self.parent_srv.namespace, workbook_item)
|
|
920
1023
|
return revisions
|
|
921
1024
|
|
|
1025
|
+
T = TypeVar("T", bound=FileObjectW)
|
|
1026
|
+
|
|
1027
|
+
@overload
|
|
1028
|
+
def download_revision(
|
|
1029
|
+
self, workbook_id: str, revision_number: Optional[str], filepath: T, include_extract: bool
|
|
1030
|
+
) -> T: ...
|
|
1031
|
+
|
|
1032
|
+
@overload
|
|
1033
|
+
def download_revision(
|
|
1034
|
+
self, workbook_id: str, revision_number: Optional[str], filepath: Optional[FilePath], include_extract: bool
|
|
1035
|
+
) -> str: ...
|
|
1036
|
+
|
|
922
1037
|
# Download 1 workbook revision by revision number
|
|
923
1038
|
@api(version="2.3")
|
|
924
1039
|
def download_revision(
|
|
925
1040
|
self,
|
|
926
|
-
workbook_id
|
|
927
|
-
revision_number
|
|
928
|
-
filepath
|
|
929
|
-
include_extract
|
|
930
|
-
)
|
|
1041
|
+
workbook_id,
|
|
1042
|
+
revision_number,
|
|
1043
|
+
filepath,
|
|
1044
|
+
include_extract=True,
|
|
1045
|
+
):
|
|
931
1046
|
"""
|
|
932
1047
|
Downloads a workbook revision to the specified directory (optional).
|
|
933
1048
|
|
|
@@ -208,6 +208,42 @@ class QuerySet(Iterable[T], Sized):
|
|
|
208
208
|
self.request_options.pagesize = kwargs["page_size"]
|
|
209
209
|
return self
|
|
210
210
|
|
|
211
|
+
def fields(self: Self, *fields: str) -> Self:
|
|
212
|
+
"""
|
|
213
|
+
Add fields to the request options. If no fields are provided, the
|
|
214
|
+
default fields will be used. If fields are provided, the default fields
|
|
215
|
+
will be used in addition to the provided fields.
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
fields : str
|
|
220
|
+
The fields to include in the request options.
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
QuerySet
|
|
225
|
+
"""
|
|
226
|
+
self.request_options.fields |= set(fields) | set(("_default_"))
|
|
227
|
+
return self
|
|
228
|
+
|
|
229
|
+
def only_fields(self: Self, *fields: str) -> Self:
|
|
230
|
+
"""
|
|
231
|
+
Add fields to the request options. If no fields are provided, the
|
|
232
|
+
default fields will be used. If fields are provided, the default fields
|
|
233
|
+
will be replaced by the provided fields.
|
|
234
|
+
|
|
235
|
+
Parameters
|
|
236
|
+
----------
|
|
237
|
+
fields : str
|
|
238
|
+
The fields to include in the request options.
|
|
239
|
+
|
|
240
|
+
Returns
|
|
241
|
+
-------
|
|
242
|
+
QuerySet
|
|
243
|
+
"""
|
|
244
|
+
self.request_options.fields |= set(fields)
|
|
245
|
+
return self
|
|
246
|
+
|
|
211
247
|
@staticmethod
|
|
212
248
|
def _parse_shorthand_filter(key: str) -> tuple[str, str]:
|
|
213
249
|
tokens = key.split("__", 1)
|
|
@@ -184,6 +184,9 @@ class DatasourceRequest:
|
|
|
184
184
|
project_element = ET.SubElement(datasource_element, "project")
|
|
185
185
|
project_element.attrib["id"] = datasource_item.project_id
|
|
186
186
|
|
|
187
|
+
if datasource_item.description is not None:
|
|
188
|
+
datasource_element.attrib["description"] = datasource_item.description
|
|
189
|
+
|
|
187
190
|
if connection_credentials is not None and connections is not None:
|
|
188
191
|
raise RuntimeError("You cannot set both `connections` and `connection_credentials`")
|
|
189
192
|
|
|
@@ -196,7 +199,7 @@ class DatasourceRequest:
|
|
|
196
199
|
_add_connections_element(connections_element, connection)
|
|
197
200
|
return ET.tostring(xml_request)
|
|
198
201
|
|
|
199
|
-
def update_req(self, datasource_item):
|
|
202
|
+
def update_req(self, datasource_item: DatasourceItem) -> bytes:
|
|
200
203
|
xml_request = ET.Element("tsRequest")
|
|
201
204
|
datasource_element = ET.SubElement(xml_request, "datasource")
|
|
202
205
|
if datasource_item.name:
|
|
@@ -219,6 +222,8 @@ class DatasourceRequest:
|
|
|
219
222
|
datasource_element.attrib["certificationNote"] = str(datasource_item.certification_note)
|
|
220
223
|
if datasource_item.encrypt_extracts is not None:
|
|
221
224
|
datasource_element.attrib["encryptExtracts"] = str(datasource_item.encrypt_extracts).lower()
|
|
225
|
+
if datasource_item.description is not None:
|
|
226
|
+
datasource_element.attrib["description"] = datasource_item.description
|
|
222
227
|
|
|
223
228
|
return ET.tostring(xml_request)
|
|
224
229
|
|
|
@@ -244,6 +249,32 @@ class DatasourceRequest:
|
|
|
244
249
|
parts = {"request_payload": ("", xml_request, "text/xml")}
|
|
245
250
|
return _add_multipart(parts)
|
|
246
251
|
|
|
252
|
+
@_tsrequest_wrapped
|
|
253
|
+
def update_connections_req(
|
|
254
|
+
self,
|
|
255
|
+
element: ET.Element,
|
|
256
|
+
connection_luids: Iterable[str],
|
|
257
|
+
authentication_type: str,
|
|
258
|
+
username: Optional[str] = None,
|
|
259
|
+
password: Optional[str] = None,
|
|
260
|
+
embed_password: Optional[bool] = None,
|
|
261
|
+
):
|
|
262
|
+
conn_luids_elem = ET.SubElement(element, "connectionLUIDs")
|
|
263
|
+
for luid in connection_luids:
|
|
264
|
+
ET.SubElement(conn_luids_elem, "connectionLUID").text = luid
|
|
265
|
+
|
|
266
|
+
connection_elem = ET.SubElement(element, "connection")
|
|
267
|
+
connection_elem.set("authenticationType", authentication_type)
|
|
268
|
+
|
|
269
|
+
if username is not None:
|
|
270
|
+
connection_elem.set("userName", username)
|
|
271
|
+
|
|
272
|
+
if password is not None:
|
|
273
|
+
connection_elem.set("password", password)
|
|
274
|
+
|
|
275
|
+
if embed_password is not None:
|
|
276
|
+
connection_elem.set("embedPassword", str(embed_password).lower())
|
|
277
|
+
|
|
247
278
|
|
|
248
279
|
class DQWRequest:
|
|
249
280
|
def add_req(self, dqw_item):
|
|
@@ -486,7 +517,10 @@ class PermissionRequest:
|
|
|
486
517
|
for rule in rules:
|
|
487
518
|
grantee_capabilities_element = ET.SubElement(permissions_element, "granteeCapabilities")
|
|
488
519
|
grantee_element = ET.SubElement(grantee_capabilities_element, rule.grantee.tag_name)
|
|
489
|
-
|
|
520
|
+
if rule.grantee.id is not None:
|
|
521
|
+
grantee_element.attrib["id"] = rule.grantee.id
|
|
522
|
+
else:
|
|
523
|
+
raise ValueError("Grantee must have an ID")
|
|
490
524
|
|
|
491
525
|
capabilities_element = ET.SubElement(grantee_capabilities_element, "capabilities")
|
|
492
526
|
self._add_all_capabilities(capabilities_element, rule.capabilities)
|
|
@@ -609,6 +643,16 @@ class ScheduleRequest:
|
|
|
609
643
|
def add_flow_req(self, id_: Optional[str], task_type: str = TaskItem.Type.RunFlow) -> bytes:
|
|
610
644
|
return self._add_to_req(id_, "flow", task_type)
|
|
611
645
|
|
|
646
|
+
@_tsrequest_wrapped
|
|
647
|
+
def batch_update_state(self, xml: ET.Element, schedules: Iterable[ScheduleItem | str]) -> None:
|
|
648
|
+
luids = ET.SubElement(xml, "scheduleLuids")
|
|
649
|
+
for schedule in schedules:
|
|
650
|
+
luid = getattr(schedule, "id", schedule)
|
|
651
|
+
if not isinstance(luid, str):
|
|
652
|
+
continue
|
|
653
|
+
luid_tag = ET.SubElement(luids, "scheduleLuid")
|
|
654
|
+
luid_tag.text = luid
|
|
655
|
+
|
|
612
656
|
|
|
613
657
|
class SiteRequest:
|
|
614
658
|
def update_req(self, site_item: "SiteItem", parent_srv: Optional["Server"] = None):
|
|
@@ -715,6 +759,8 @@ class SiteRequest:
|
|
|
715
759
|
site_element.attrib["autoSuspendRefreshInactivityWindow"] = str(
|
|
716
760
|
site_item.auto_suspend_refresh_inactivity_window
|
|
717
761
|
)
|
|
762
|
+
if site_item.attribute_capture_enabled is not None:
|
|
763
|
+
site_element.attrib["attributeCaptureEnabled"] = str(site_item.attribute_capture_enabled).lower()
|
|
718
764
|
|
|
719
765
|
return ET.tostring(xml_request)
|
|
720
766
|
|
|
@@ -819,6 +865,8 @@ class SiteRequest:
|
|
|
819
865
|
site_element.attrib["autoSuspendRefreshInactivityWindow"] = str(
|
|
820
866
|
site_item.auto_suspend_refresh_inactivity_window
|
|
821
867
|
)
|
|
868
|
+
if site_item.attribute_capture_enabled is not None:
|
|
869
|
+
site_element.attrib["attributeCaptureEnabled"] = str(site_item.attribute_capture_enabled).lower()
|
|
822
870
|
|
|
823
871
|
return ET.tostring(xml_request)
|
|
824
872
|
|
|
@@ -894,6 +942,7 @@ class TagRequest:
|
|
|
894
942
|
if item.id is None:
|
|
895
943
|
raise ValueError(f"Item {item} must have an ID to be tagged.")
|
|
896
944
|
content_element.attrib["id"] = item.id
|
|
945
|
+
content_element.attrib["contentType"] = item.__class__.__name__.replace("Item", "")
|
|
897
946
|
|
|
898
947
|
return ET.tostring(element)
|
|
899
948
|
|
|
@@ -913,6 +962,8 @@ class UserRequest:
|
|
|
913
962
|
user_element.attrib["authSetting"] = user_item.auth_setting
|
|
914
963
|
if password:
|
|
915
964
|
user_element.attrib["password"] = password
|
|
965
|
+
if user_item.idp_configuration_id is not None:
|
|
966
|
+
user_element.attrib["idpConfigurationId"] = user_item.idp_configuration_id
|
|
916
967
|
return ET.tostring(xml_request)
|
|
917
968
|
|
|
918
969
|
def add_req(self, user_item: UserItem) -> bytes:
|
|
@@ -929,8 +980,37 @@ class UserRequest:
|
|
|
929
980
|
|
|
930
981
|
if user_item.auth_setting:
|
|
931
982
|
user_element.attrib["authSetting"] = user_item.auth_setting
|
|
983
|
+
|
|
984
|
+
if user_item.idp_configuration_id is not None:
|
|
985
|
+
user_element.attrib["idpConfigurationId"] = user_item.idp_configuration_id
|
|
932
986
|
return ET.tostring(xml_request)
|
|
933
987
|
|
|
988
|
+
def import_from_csv_req(self, csv_content: bytes, users: Iterable[UserItem]):
|
|
989
|
+
xml_request = ET.Element("tsRequest")
|
|
990
|
+
for user in users:
|
|
991
|
+
if user.name is None:
|
|
992
|
+
raise ValueError("User name must be populated.")
|
|
993
|
+
user_element = ET.SubElement(xml_request, "user")
|
|
994
|
+
user_element.attrib["name"] = user.name
|
|
995
|
+
if user.auth_setting is not None and user.idp_configuration_id is not None:
|
|
996
|
+
raise ValueError("User cannot have both authSetting and idpConfigurationId.")
|
|
997
|
+
elif user.idp_configuration_id is not None:
|
|
998
|
+
user_element.attrib["idpConfigurationId"] = user.idp_configuration_id
|
|
999
|
+
else:
|
|
1000
|
+
user_element.attrib["authSetting"] = user.auth_setting or "ServerDefault"
|
|
1001
|
+
|
|
1002
|
+
parts = {
|
|
1003
|
+
"tableau_user_import": ("tsc_users_file.csv", csv_content, "file"),
|
|
1004
|
+
"request_payload": ("", ET.tostring(xml_request), "text/xml"),
|
|
1005
|
+
}
|
|
1006
|
+
return _add_multipart(parts)
|
|
1007
|
+
|
|
1008
|
+
def delete_csv_req(self, csv_content: bytes):
|
|
1009
|
+
parts = {
|
|
1010
|
+
"tableau_user_delete": ("tsc_users_file.csv", csv_content, "file"),
|
|
1011
|
+
}
|
|
1012
|
+
return _add_multipart(parts)
|
|
1013
|
+
|
|
934
1014
|
|
|
935
1015
|
class WorkbookRequest:
|
|
936
1016
|
def _generate_xml(
|
|
@@ -1087,6 +1167,32 @@ class WorkbookRequest:
|
|
|
1087
1167
|
if (id_ := datasource_item.id) is not None:
|
|
1088
1168
|
datasource_element.attrib["id"] = id_
|
|
1089
1169
|
|
|
1170
|
+
@_tsrequest_wrapped
|
|
1171
|
+
def update_connections_req(
|
|
1172
|
+
self,
|
|
1173
|
+
element: ET.Element,
|
|
1174
|
+
connection_luids: Iterable[str],
|
|
1175
|
+
authentication_type: str,
|
|
1176
|
+
username: Optional[str] = None,
|
|
1177
|
+
password: Optional[str] = None,
|
|
1178
|
+
embed_password: Optional[bool] = None,
|
|
1179
|
+
):
|
|
1180
|
+
conn_luids_elem = ET.SubElement(element, "connectionLUIDs")
|
|
1181
|
+
for luid in connection_luids:
|
|
1182
|
+
ET.SubElement(conn_luids_elem, "connectionLUID").text = luid
|
|
1183
|
+
|
|
1184
|
+
connection_elem = ET.SubElement(element, "connection")
|
|
1185
|
+
connection_elem.set("authenticationType", authentication_type)
|
|
1186
|
+
|
|
1187
|
+
if username is not None:
|
|
1188
|
+
connection_elem.set("userName", username)
|
|
1189
|
+
|
|
1190
|
+
if password is not None:
|
|
1191
|
+
connection_elem.set("password", password)
|
|
1192
|
+
|
|
1193
|
+
if embed_password is not None:
|
|
1194
|
+
connection_elem.set("embedPassword", str(embed_password).lower())
|
|
1195
|
+
|
|
1090
1196
|
|
|
1091
1197
|
class Connection:
|
|
1092
1198
|
@_tsrequest_wrapped
|
|
@@ -1105,6 +1211,8 @@ class Connection:
|
|
|
1105
1211
|
connection_element.attrib["userName"] = connection_item.username
|
|
1106
1212
|
if connection_item.password is not None:
|
|
1107
1213
|
connection_element.attrib["password"] = connection_item.password
|
|
1214
|
+
if connection_item.auth_type is not None:
|
|
1215
|
+
connection_element.attrib["authenticationType"] = connection_item.auth_type
|
|
1108
1216
|
if connection_item.embed_password is not None:
|
|
1109
1217
|
connection_element.attrib["embedPassword"] = str(connection_item.embed_password).lower()
|
|
1110
1218
|
if connection_item.query_tagging is not None:
|
|
@@ -1441,6 +1549,180 @@ class VirtualConnectionRequest:
|
|
|
1441
1549
|
return ET.tostring(xml_request)
|
|
1442
1550
|
|
|
1443
1551
|
|
|
1552
|
+
class OIDCRequest:
|
|
1553
|
+
@_tsrequest_wrapped
|
|
1554
|
+
def create_req(self, xml_request: ET.Element, oidc_item: SiteOIDCConfiguration) -> bytes:
|
|
1555
|
+
oidc_element = ET.SubElement(xml_request, "siteOIDCConfiguration")
|
|
1556
|
+
|
|
1557
|
+
# Check required attributes first
|
|
1558
|
+
|
|
1559
|
+
if oidc_item.idp_configuration_name is None:
|
|
1560
|
+
raise ValueError(f"OIDC Item missing idp_configuration_name: {oidc_item}")
|
|
1561
|
+
if oidc_item.client_id is None:
|
|
1562
|
+
raise ValueError(f"OIDC Item missing client_id: {oidc_item}")
|
|
1563
|
+
if oidc_item.client_secret is None:
|
|
1564
|
+
raise ValueError(f"OIDC Item missing client_secret: {oidc_item}")
|
|
1565
|
+
if oidc_item.authorization_endpoint is None:
|
|
1566
|
+
raise ValueError(f"OIDC Item missing authorization_endpoint: {oidc_item}")
|
|
1567
|
+
if oidc_item.token_endpoint is None:
|
|
1568
|
+
raise ValueError(f"OIDC Item missing token_endpoint: {oidc_item}")
|
|
1569
|
+
if oidc_item.userinfo_endpoint is None:
|
|
1570
|
+
raise ValueError(f"OIDC Item missing userinfo_endpoint: {oidc_item}")
|
|
1571
|
+
if not isinstance(oidc_item.enabled, bool):
|
|
1572
|
+
raise ValueError(f"OIDC Item missing enabled: {oidc_item}")
|
|
1573
|
+
if oidc_item.jwks_uri is None:
|
|
1574
|
+
raise ValueError(f"OIDC Item missing jwks_uri: {oidc_item}")
|
|
1575
|
+
|
|
1576
|
+
oidc_element.attrib["name"] = oidc_item.idp_configuration_name
|
|
1577
|
+
oidc_element.attrib["clientId"] = oidc_item.client_id
|
|
1578
|
+
oidc_element.attrib["clientSecret"] = oidc_item.client_secret
|
|
1579
|
+
oidc_element.attrib["authorizationEndpoint"] = oidc_item.authorization_endpoint
|
|
1580
|
+
oidc_element.attrib["tokenEndpoint"] = oidc_item.token_endpoint
|
|
1581
|
+
oidc_element.attrib["userInfoEndpoint"] = oidc_item.userinfo_endpoint
|
|
1582
|
+
oidc_element.attrib["enabled"] = str(oidc_item.enabled).lower()
|
|
1583
|
+
oidc_element.attrib["jwksUri"] = oidc_item.jwks_uri
|
|
1584
|
+
|
|
1585
|
+
if oidc_item.allow_embedded_authentication is not None:
|
|
1586
|
+
oidc_element.attrib["allowEmbeddedAuthentication"] = str(oidc_item.allow_embedded_authentication).lower()
|
|
1587
|
+
if oidc_item.custom_scope is not None:
|
|
1588
|
+
oidc_element.attrib["customScope"] = oidc_item.custom_scope
|
|
1589
|
+
if oidc_item.prompt is not None:
|
|
1590
|
+
oidc_element.attrib["prompt"] = oidc_item.prompt
|
|
1591
|
+
if oidc_item.client_authentication is not None:
|
|
1592
|
+
oidc_element.attrib["clientAuthentication"] = oidc_item.client_authentication
|
|
1593
|
+
if oidc_item.essential_acr_values is not None:
|
|
1594
|
+
oidc_element.attrib["essentialAcrValues"] = oidc_item.essential_acr_values
|
|
1595
|
+
if oidc_item.voluntary_acr_values is not None:
|
|
1596
|
+
oidc_element.attrib["voluntaryAcrValues"] = oidc_item.voluntary_acr_values
|
|
1597
|
+
if oidc_item.email_mapping is not None:
|
|
1598
|
+
oidc_element.attrib["emailMapping"] = oidc_item.email_mapping
|
|
1599
|
+
if oidc_item.first_name_mapping is not None:
|
|
1600
|
+
oidc_element.attrib["firstNameMapping"] = oidc_item.first_name_mapping
|
|
1601
|
+
if oidc_item.last_name_mapping is not None:
|
|
1602
|
+
oidc_element.attrib["lastNameMapping"] = oidc_item.last_name_mapping
|
|
1603
|
+
if oidc_item.full_name_mapping is not None:
|
|
1604
|
+
oidc_element.attrib["fullNameMapping"] = oidc_item.full_name_mapping
|
|
1605
|
+
if oidc_item.use_full_name is not None:
|
|
1606
|
+
oidc_element.attrib["useFullName"] = str(oidc_item.use_full_name).lower()
|
|
1607
|
+
|
|
1608
|
+
return ET.tostring(xml_request)
|
|
1609
|
+
|
|
1610
|
+
@_tsrequest_wrapped
|
|
1611
|
+
def update_req(self, xml_request: ET.Element, oidc_item: SiteOIDCConfiguration) -> bytes:
|
|
1612
|
+
oidc_element = ET.SubElement(xml_request, "siteOIDCConfiguration")
|
|
1613
|
+
|
|
1614
|
+
# Check required attributes first
|
|
1615
|
+
|
|
1616
|
+
if oidc_item.idp_configuration_name is None:
|
|
1617
|
+
raise ValueError(f"OIDC Item missing idp_configuration_name: {oidc_item}")
|
|
1618
|
+
if oidc_item.client_id is None:
|
|
1619
|
+
raise ValueError(f"OIDC Item missing client_id: {oidc_item}")
|
|
1620
|
+
if oidc_item.client_secret is None:
|
|
1621
|
+
raise ValueError(f"OIDC Item missing client_secret: {oidc_item}")
|
|
1622
|
+
if oidc_item.authorization_endpoint is None:
|
|
1623
|
+
raise ValueError(f"OIDC Item missing authorization_endpoint: {oidc_item}")
|
|
1624
|
+
if oidc_item.token_endpoint is None:
|
|
1625
|
+
raise ValueError(f"OIDC Item missing token_endpoint: {oidc_item}")
|
|
1626
|
+
if oidc_item.userinfo_endpoint is None:
|
|
1627
|
+
raise ValueError(f"OIDC Item missing userinfo_endpoint: {oidc_item}")
|
|
1628
|
+
if not isinstance(oidc_item.enabled, bool):
|
|
1629
|
+
raise ValueError(f"OIDC Item missing enabled: {oidc_item}")
|
|
1630
|
+
if oidc_item.jwks_uri is None:
|
|
1631
|
+
raise ValueError(f"OIDC Item missing jwks_uri: {oidc_item}")
|
|
1632
|
+
|
|
1633
|
+
oidc_element.attrib["name"] = oidc_item.idp_configuration_name
|
|
1634
|
+
oidc_element.attrib["clientId"] = oidc_item.client_id
|
|
1635
|
+
oidc_element.attrib["clientSecret"] = oidc_item.client_secret
|
|
1636
|
+
oidc_element.attrib["authorizationEndpoint"] = oidc_item.authorization_endpoint
|
|
1637
|
+
oidc_element.attrib["tokenEndpoint"] = oidc_item.token_endpoint
|
|
1638
|
+
oidc_element.attrib["userInfoEndpoint"] = oidc_item.userinfo_endpoint
|
|
1639
|
+
oidc_element.attrib["enabled"] = str(oidc_item.enabled).lower()
|
|
1640
|
+
oidc_element.attrib["jwksUri"] = oidc_item.jwks_uri
|
|
1641
|
+
|
|
1642
|
+
if oidc_item.allow_embedded_authentication is not None:
|
|
1643
|
+
oidc_element.attrib["allowEmbeddedAuthentication"] = str(oidc_item.allow_embedded_authentication).lower()
|
|
1644
|
+
if oidc_item.custom_scope is not None:
|
|
1645
|
+
oidc_element.attrib["customScope"] = oidc_item.custom_scope
|
|
1646
|
+
if oidc_item.prompt is not None:
|
|
1647
|
+
oidc_element.attrib["prompt"] = oidc_item.prompt
|
|
1648
|
+
if oidc_item.client_authentication is not None:
|
|
1649
|
+
oidc_element.attrib["clientAuthentication"] = oidc_item.client_authentication
|
|
1650
|
+
if oidc_item.essential_acr_values is not None:
|
|
1651
|
+
oidc_element.attrib["essentialAcrValues"] = oidc_item.essential_acr_values
|
|
1652
|
+
if oidc_item.voluntary_acr_values is not None:
|
|
1653
|
+
oidc_element.attrib["voluntaryAcrValues"] = oidc_item.voluntary_acr_values
|
|
1654
|
+
if oidc_item.email_mapping is not None:
|
|
1655
|
+
oidc_element.attrib["emailMapping"] = oidc_item.email_mapping
|
|
1656
|
+
if oidc_item.first_name_mapping is not None:
|
|
1657
|
+
oidc_element.attrib["firstNameMapping"] = oidc_item.first_name_mapping
|
|
1658
|
+
if oidc_item.last_name_mapping is not None:
|
|
1659
|
+
oidc_element.attrib["lastNameMapping"] = oidc_item.last_name_mapping
|
|
1660
|
+
if oidc_item.full_name_mapping is not None:
|
|
1661
|
+
oidc_element.attrib["fullNameMapping"] = oidc_item.full_name_mapping
|
|
1662
|
+
if oidc_item.use_full_name is not None:
|
|
1663
|
+
oidc_element.attrib["useFullName"] = str(oidc_item.use_full_name).lower()
|
|
1664
|
+
|
|
1665
|
+
return ET.tostring(xml_request)
|
|
1666
|
+
|
|
1667
|
+
|
|
1668
|
+
class ExtensionsRequest:
|
|
1669
|
+
@_tsrequest_wrapped
|
|
1670
|
+
def update_server_extensions(self, xml_request: ET.Element, extensions_server: "ExtensionsServer") -> None:
|
|
1671
|
+
extensions_element = ET.SubElement(xml_request, "extensionsServerSettings")
|
|
1672
|
+
if not isinstance(extensions_server.enabled, bool):
|
|
1673
|
+
raise ValueError(f"Extensions Server missing enabled: {extensions_server}")
|
|
1674
|
+
enabled_element = ET.SubElement(extensions_element, "extensionsGloballyEnabled")
|
|
1675
|
+
enabled_element.text = str(extensions_server.enabled).lower()
|
|
1676
|
+
|
|
1677
|
+
if extensions_server.block_list is None:
|
|
1678
|
+
return
|
|
1679
|
+
for blocked in extensions_server.block_list:
|
|
1680
|
+
blocked_element = ET.SubElement(extensions_element, "blockList")
|
|
1681
|
+
blocked_element.text = blocked
|
|
1682
|
+
return
|
|
1683
|
+
|
|
1684
|
+
@_tsrequest_wrapped
|
|
1685
|
+
def update_site_extensions(self, xml_request: ET.Element, extensions_site_settings: ExtensionsSiteSettings) -> None:
|
|
1686
|
+
ext_element = ET.SubElement(xml_request, "extensionsSiteSettings")
|
|
1687
|
+
if not isinstance(extensions_site_settings.enabled, bool):
|
|
1688
|
+
raise ValueError(f"Extensions Site Settings missing enabled: {extensions_site_settings}")
|
|
1689
|
+
enabled_element = ET.SubElement(ext_element, "extensionsEnabled")
|
|
1690
|
+
enabled_element.text = str(extensions_site_settings.enabled).lower()
|
|
1691
|
+
if not isinstance(extensions_site_settings.use_default_setting, bool):
|
|
1692
|
+
raise ValueError(
|
|
1693
|
+
f"Extensions Site Settings missing use_default_setting: {extensions_site_settings.use_default_setting}"
|
|
1694
|
+
)
|
|
1695
|
+
default_element = ET.SubElement(ext_element, "useDefaultSetting")
|
|
1696
|
+
default_element.text = str(extensions_site_settings.use_default_setting).lower()
|
|
1697
|
+
if extensions_site_settings.allow_trusted is not None:
|
|
1698
|
+
allow_trusted_element = ET.SubElement(ext_element, "allowTrusted")
|
|
1699
|
+
allow_trusted_element.text = str(extensions_site_settings.allow_trusted).lower()
|
|
1700
|
+
if extensions_site_settings.include_sandboxed is not None:
|
|
1701
|
+
include_sandboxed_element = ET.SubElement(ext_element, "includeSandboxed")
|
|
1702
|
+
include_sandboxed_element.text = str(extensions_site_settings.include_sandboxed).lower()
|
|
1703
|
+
if extensions_site_settings.include_tableau_built is not None:
|
|
1704
|
+
include_tableau_built_element = ET.SubElement(ext_element, "includeTableauBuilt")
|
|
1705
|
+
include_tableau_built_element.text = str(extensions_site_settings.include_tableau_built).lower()
|
|
1706
|
+
if extensions_site_settings.include_partner_built is not None:
|
|
1707
|
+
include_partner_built_element = ET.SubElement(ext_element, "includePartnerBuilt")
|
|
1708
|
+
include_partner_built_element.text = str(extensions_site_settings.include_partner_built).lower()
|
|
1709
|
+
|
|
1710
|
+
if extensions_site_settings.safe_list is None:
|
|
1711
|
+
return
|
|
1712
|
+
|
|
1713
|
+
safe_element = ET.SubElement(ext_element, "safeList")
|
|
1714
|
+
for safe in extensions_site_settings.safe_list:
|
|
1715
|
+
if safe.url is not None:
|
|
1716
|
+
url_element = ET.SubElement(safe_element, "url")
|
|
1717
|
+
url_element.text = safe.url
|
|
1718
|
+
if safe.full_data_allowed is not None:
|
|
1719
|
+
full_data_element = ET.SubElement(safe_element, "fullDataAllowed")
|
|
1720
|
+
full_data_element.text = str(safe.full_data_allowed).lower()
|
|
1721
|
+
if safe.prompt_needed is not None:
|
|
1722
|
+
prompt_element = ET.SubElement(safe_element, "promptNeeded")
|
|
1723
|
+
prompt_element.text = str(safe.prompt_needed).lower()
|
|
1724
|
+
|
|
1725
|
+
|
|
1444
1726
|
class RequestFactory:
|
|
1445
1727
|
Auth = AuthRequest()
|
|
1446
1728
|
Connection = Connection()
|
|
@@ -1451,6 +1733,7 @@ class RequestFactory:
|
|
|
1451
1733
|
Database = DatabaseRequest()
|
|
1452
1734
|
DQW = DQWRequest()
|
|
1453
1735
|
Empty = EmptyRequest()
|
|
1736
|
+
Extensions = ExtensionsRequest()
|
|
1454
1737
|
Favorite = FavoriteRequest()
|
|
1455
1738
|
Fileupload = FileuploadRequest()
|
|
1456
1739
|
Flow = FlowRequest()
|
|
@@ -1458,6 +1741,7 @@ class RequestFactory:
|
|
|
1458
1741
|
Group = GroupRequest()
|
|
1459
1742
|
GroupSet = GroupSetRequest()
|
|
1460
1743
|
Metric = MetricRequest()
|
|
1744
|
+
OIDC = OIDCRequest()
|
|
1461
1745
|
Permission = PermissionRequest()
|
|
1462
1746
|
Project = ProjectRequest()
|
|
1463
1747
|
Schedule = ScheduleRequest()
|