tableauserverclient 0.31__py3-none-any.whl → 0.33__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tableauserverclient/__init__.py +74 -4
- tableauserverclient/_version.py +3 -3
- tableauserverclient/config.py +16 -4
- tableauserverclient/models/__init__.py +100 -37
- tableauserverclient/models/connection_item.py +4 -2
- tableauserverclient/models/database_item.py +6 -0
- tableauserverclient/models/datasource_item.py +18 -6
- tableauserverclient/models/favorites_item.py +7 -7
- tableauserverclient/models/flow_item.py +6 -6
- tableauserverclient/models/groupset_item.py +53 -0
- tableauserverclient/models/interval_item.py +27 -14
- tableauserverclient/models/job_item.py +18 -2
- tableauserverclient/models/linked_tasks_item.py +102 -0
- tableauserverclient/models/permissions_item.py +53 -5
- tableauserverclient/models/project_item.py +4 -3
- tableauserverclient/models/reference_item.py +5 -0
- tableauserverclient/models/tableau_auth.py +22 -23
- tableauserverclient/models/tableau_types.py +9 -7
- tableauserverclient/models/virtual_connection_item.py +77 -0
- tableauserverclient/server/__init__.py +83 -8
- tableauserverclient/server/endpoint/__init__.py +69 -28
- tableauserverclient/server/endpoint/auth_endpoint.py +3 -3
- tableauserverclient/server/endpoint/custom_views_endpoint.py +66 -5
- tableauserverclient/server/endpoint/databases_endpoint.py +21 -18
- tableauserverclient/server/endpoint/datasources_endpoint.py +115 -35
- tableauserverclient/server/endpoint/endpoint.py +53 -20
- tableauserverclient/server/endpoint/favorites_endpoint.py +1 -1
- tableauserverclient/server/endpoint/fileuploads_endpoint.py +2 -2
- tableauserverclient/server/endpoint/flow_runs_endpoint.py +45 -5
- tableauserverclient/server/endpoint/flows_endpoint.py +43 -16
- tableauserverclient/server/endpoint/groups_endpoint.py +85 -31
- tableauserverclient/server/endpoint/groupsets_endpoint.py +127 -0
- tableauserverclient/server/endpoint/jobs_endpoint.py +74 -7
- tableauserverclient/server/endpoint/linked_tasks_endpoint.py +45 -0
- tableauserverclient/server/endpoint/metadata_endpoint.py +3 -3
- tableauserverclient/server/endpoint/metrics_endpoint.py +1 -1
- tableauserverclient/server/endpoint/projects_endpoint.py +50 -18
- tableauserverclient/server/endpoint/resource_tagger.py +135 -3
- tableauserverclient/server/endpoint/tables_endpoint.py +19 -16
- tableauserverclient/server/endpoint/users_endpoint.py +40 -1
- tableauserverclient/server/endpoint/views_endpoint.py +97 -10
- tableauserverclient/server/endpoint/virtual_connections_endpoint.py +173 -0
- tableauserverclient/server/endpoint/workbooks_endpoint.py +97 -45
- tableauserverclient/server/pager.py +46 -46
- tableauserverclient/server/query.py +62 -33
- tableauserverclient/server/request_factory.py +192 -49
- tableauserverclient/server/request_options.py +4 -2
- tableauserverclient/server/server.py +13 -6
- {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/METADATA +15 -16
- {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/RECORD +54 -48
- {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/WHEEL +1 -1
- {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/LICENSE +0 -0
- {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/LICENSE.versioneer +0 -0
- {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import xml.etree.ElementTree as ET
|
|
2
|
-
from typing import Any, Dict, Iterable, List, Optional, Tuple, TYPE_CHECKING
|
|
2
|
+
from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, TypeVar, TYPE_CHECKING, Union
|
|
3
|
+
|
|
4
|
+
from typing_extensions import ParamSpec
|
|
3
5
|
|
|
4
6
|
from requests.packages.urllib3.fields import RequestField
|
|
5
7
|
from requests.packages.urllib3.filepost import encode_multipart_formdata
|
|
8
|
+
from typing_extensions import Concatenate
|
|
6
9
|
|
|
7
10
|
from tableauserverclient.models import *
|
|
8
11
|
|
|
@@ -23,8 +26,12 @@ def _add_multipart(parts: Dict) -> Tuple[Any, str]:
|
|
|
23
26
|
return xml_request, content_type
|
|
24
27
|
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
T = TypeVar("T")
|
|
30
|
+
P = ParamSpec("P")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _tsrequest_wrapped(func: Callable[Concatenate[T, ET.Element, P], Any]) -> Callable[Concatenate[T, P], bytes]:
|
|
34
|
+
def wrapper(self: T, *args: P.args, **kwargs: P.kwargs) -> bytes:
|
|
28
35
|
xml_request = ET.Element("tsRequest")
|
|
29
36
|
func(self, xml_request, *args, **kwargs)
|
|
30
37
|
return ET.tostring(xml_request)
|
|
@@ -387,6 +394,28 @@ class GroupRequest(object):
|
|
|
387
394
|
user_element.attrib["id"] = user_id
|
|
388
395
|
return ET.tostring(xml_request)
|
|
389
396
|
|
|
397
|
+
@_tsrequest_wrapped
|
|
398
|
+
def add_users_req(self, xml_request: ET.Element, users: Iterable[Union[str, UserItem]]) -> bytes:
|
|
399
|
+
users_element = ET.SubElement(xml_request, "users")
|
|
400
|
+
for user in users:
|
|
401
|
+
user_element = ET.SubElement(users_element, "user")
|
|
402
|
+
if not (user_id := user.id if isinstance(user, UserItem) else user):
|
|
403
|
+
raise ValueError("User ID must be populated")
|
|
404
|
+
user_element.attrib["id"] = user_id
|
|
405
|
+
|
|
406
|
+
return ET.tostring(xml_request)
|
|
407
|
+
|
|
408
|
+
@_tsrequest_wrapped
|
|
409
|
+
def remove_users_req(self, xml_request: ET.Element, users: Iterable[Union[str, UserItem]]) -> bytes:
|
|
410
|
+
users_element = ET.SubElement(xml_request, "users")
|
|
411
|
+
for user in users:
|
|
412
|
+
user_element = ET.SubElement(users_element, "user")
|
|
413
|
+
if not (user_id := user.id if isinstance(user, UserItem) else user):
|
|
414
|
+
raise ValueError("User ID must be populated")
|
|
415
|
+
user_element.attrib["id"] = user_id
|
|
416
|
+
|
|
417
|
+
return ET.tostring(xml_request)
|
|
418
|
+
|
|
390
419
|
def create_local_req(self, group_item: GroupItem) -> bytes:
|
|
391
420
|
xml_request = ET.Element("tsRequest")
|
|
392
421
|
group_element = ET.SubElement(xml_request, "group")
|
|
@@ -418,19 +447,10 @@ class GroupRequest(object):
|
|
|
418
447
|
import_element.attrib["siteRole"] = group_item.minimum_site_role
|
|
419
448
|
return ET.tostring(xml_request)
|
|
420
449
|
|
|
421
|
-
def update_req(
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
warnings.simplefilter("always", DeprecationWarning)
|
|
427
|
-
warnings.warn(
|
|
428
|
-
'RequestFactory.Group.update_req(...default_site_role="") is deprecated, '
|
|
429
|
-
"please set the minimum_site_role field of GroupItem",
|
|
430
|
-
DeprecationWarning,
|
|
431
|
-
)
|
|
432
|
-
group_item.minimum_site_role = default_site_role
|
|
433
|
-
|
|
450
|
+
def update_req(
|
|
451
|
+
self,
|
|
452
|
+
group_item: GroupItem,
|
|
453
|
+
) -> bytes:
|
|
434
454
|
xml_request = ET.Element("tsRequest")
|
|
435
455
|
group_element = ET.SubElement(xml_request, "group")
|
|
436
456
|
|
|
@@ -491,6 +511,9 @@ class ProjectRequest(object):
|
|
|
491
511
|
project_element.attrib["contentPermissions"] = project_item.content_permissions
|
|
492
512
|
if project_item.parent_id is not None:
|
|
493
513
|
project_element.attrib["parentProjectId"] = project_item.parent_id
|
|
514
|
+
if (owner := project_item.owner_id) is not None:
|
|
515
|
+
owner_element = ET.SubElement(project_element, "owner")
|
|
516
|
+
owner_element.attrib["id"] = owner
|
|
494
517
|
return ET.tostring(xml_request)
|
|
495
518
|
|
|
496
519
|
def create_req(self, project_item: "ProjectItem") -> bytes:
|
|
@@ -845,6 +868,9 @@ class TableRequest(object):
|
|
|
845
868
|
return ET.tostring(xml_request)
|
|
846
869
|
|
|
847
870
|
|
|
871
|
+
content_types = Iterable[Union["ColumnItem", "DatabaseItem", "DatasourceItem", "FlowItem", "TableItem", "WorkbookItem"]]
|
|
872
|
+
|
|
873
|
+
|
|
848
874
|
class TagRequest(object):
|
|
849
875
|
def add_req(self, tag_set):
|
|
850
876
|
xml_request = ET.Element("tsRequest")
|
|
@@ -854,6 +880,22 @@ class TagRequest(object):
|
|
|
854
880
|
tag_element.attrib["label"] = tag
|
|
855
881
|
return ET.tostring(xml_request)
|
|
856
882
|
|
|
883
|
+
@_tsrequest_wrapped
|
|
884
|
+
def batch_create(self, element: ET.Element, tags: Set[str], content: content_types) -> bytes:
|
|
885
|
+
tag_batch = ET.SubElement(element, "tagBatch")
|
|
886
|
+
tags_element = ET.SubElement(tag_batch, "tags")
|
|
887
|
+
for tag in tags:
|
|
888
|
+
tag_element = ET.SubElement(tags_element, "tag")
|
|
889
|
+
tag_element.attrib["label"] = tag
|
|
890
|
+
contents_element = ET.SubElement(tag_batch, "contents")
|
|
891
|
+
for item in content:
|
|
892
|
+
content_element = ET.SubElement(contents_element, "content")
|
|
893
|
+
if item.id is None:
|
|
894
|
+
raise ValueError(f"Item {item} must have an ID to be tagged.")
|
|
895
|
+
content_element.attrib["id"] = item.id
|
|
896
|
+
|
|
897
|
+
return ET.tostring(element)
|
|
898
|
+
|
|
857
899
|
|
|
858
900
|
class UserRequest(object):
|
|
859
901
|
def update_req(self, user_item: UserItem, password: Optional[str]) -> bytes:
|
|
@@ -893,9 +935,7 @@ class WorkbookRequest(object):
|
|
|
893
935
|
def _generate_xml(
|
|
894
936
|
self,
|
|
895
937
|
workbook_item,
|
|
896
|
-
connection_credentials=None,
|
|
897
938
|
connections=None,
|
|
898
|
-
hidden_views=None,
|
|
899
939
|
):
|
|
900
940
|
xml_request = ET.Element("tsRequest")
|
|
901
941
|
workbook_element = ET.SubElement(xml_request, "workbook")
|
|
@@ -905,12 +945,6 @@ class WorkbookRequest(object):
|
|
|
905
945
|
project_element = ET.SubElement(workbook_element, "project")
|
|
906
946
|
project_element.attrib["id"] = str(workbook_item.project_id)
|
|
907
947
|
|
|
908
|
-
if connection_credentials is not None and connections is not None:
|
|
909
|
-
raise RuntimeError("You cannot set both `connections` and `connection_credentials`")
|
|
910
|
-
|
|
911
|
-
if connection_credentials is not None and connection_credentials != False:
|
|
912
|
-
_add_credentials_element(workbook_element, connection_credentials)
|
|
913
|
-
|
|
914
948
|
if connections is not None and connections != False and len(connections) > 0:
|
|
915
949
|
connections_element = ET.SubElement(workbook_element, "connections")
|
|
916
950
|
for connection in connections:
|
|
@@ -919,17 +953,6 @@ class WorkbookRequest(object):
|
|
|
919
953
|
if workbook_item.description is not None:
|
|
920
954
|
workbook_element.attrib["description"] = workbook_item.description
|
|
921
955
|
|
|
922
|
-
if hidden_views is not None:
|
|
923
|
-
import warnings
|
|
924
|
-
|
|
925
|
-
warnings.simplefilter("always", DeprecationWarning)
|
|
926
|
-
warnings.warn(
|
|
927
|
-
"the hidden_views parameter should now be set on the workbook directly",
|
|
928
|
-
DeprecationWarning,
|
|
929
|
-
)
|
|
930
|
-
if workbook_item.hidden_views is None:
|
|
931
|
-
workbook_item.hidden_views = hidden_views
|
|
932
|
-
|
|
933
956
|
if workbook_item.hidden_views is not None:
|
|
934
957
|
views_element = ET.SubElement(workbook_element, "views")
|
|
935
958
|
for view_name in workbook_item.hidden_views:
|
|
@@ -1012,15 +1035,11 @@ class WorkbookRequest(object):
|
|
|
1012
1035
|
workbook_item,
|
|
1013
1036
|
filename,
|
|
1014
1037
|
file_contents,
|
|
1015
|
-
connection_credentials=None,
|
|
1016
1038
|
connections=None,
|
|
1017
|
-
hidden_views=None,
|
|
1018
1039
|
):
|
|
1019
1040
|
xml_request = self._generate_xml(
|
|
1020
1041
|
workbook_item,
|
|
1021
|
-
connection_credentials=connection_credentials,
|
|
1022
1042
|
connections=connections,
|
|
1023
|
-
hidden_views=hidden_views,
|
|
1024
1043
|
)
|
|
1025
1044
|
|
|
1026
1045
|
parts = {
|
|
@@ -1032,37 +1051,41 @@ class WorkbookRequest(object):
|
|
|
1032
1051
|
def publish_req_chunked(
|
|
1033
1052
|
self,
|
|
1034
1053
|
workbook_item,
|
|
1035
|
-
connection_credentials=None,
|
|
1036
1054
|
connections=None,
|
|
1037
|
-
hidden_views=None,
|
|
1038
1055
|
):
|
|
1039
1056
|
xml_request = self._generate_xml(
|
|
1040
1057
|
workbook_item,
|
|
1041
|
-
connection_credentials=connection_credentials,
|
|
1042
1058
|
connections=connections,
|
|
1043
|
-
hidden_views=hidden_views,
|
|
1044
1059
|
)
|
|
1045
1060
|
|
|
1046
1061
|
parts = {"request_payload": ("", xml_request, "text/xml")}
|
|
1047
1062
|
return _add_multipart(parts)
|
|
1048
1063
|
|
|
1049
1064
|
@_tsrequest_wrapped
|
|
1050
|
-
def embedded_extract_req(
|
|
1065
|
+
def embedded_extract_req(
|
|
1066
|
+
self, xml_request: ET.Element, include_all: bool = True, datasources: Optional[Iterable[DatasourceItem]] = None
|
|
1067
|
+
) -> None:
|
|
1051
1068
|
list_element = ET.SubElement(xml_request, "datasources")
|
|
1052
1069
|
if include_all:
|
|
1053
1070
|
list_element.attrib["includeAll"] = "true"
|
|
1054
1071
|
elif datasources:
|
|
1055
1072
|
for datasource_item in datasources:
|
|
1056
1073
|
datasource_element = ET.SubElement(list_element, "datasource")
|
|
1057
|
-
|
|
1074
|
+
if (id_ := datasource_item.id) is not None:
|
|
1075
|
+
datasource_element.attrib["id"] = id_
|
|
1058
1076
|
|
|
1059
1077
|
|
|
1060
1078
|
class Connection(object):
|
|
1061
1079
|
@_tsrequest_wrapped
|
|
1062
1080
|
def update_req(self, xml_request: ET.Element, connection_item: "ConnectionItem") -> None:
|
|
1063
1081
|
connection_element = ET.SubElement(xml_request, "connection")
|
|
1064
|
-
if connection_item.server_address is not None:
|
|
1065
|
-
|
|
1082
|
+
if (server_address := connection_item.server_address) is not None:
|
|
1083
|
+
if (conn_type := connection_item.connection_type) is not None:
|
|
1084
|
+
if conn_type.casefold() != "odata".casefold():
|
|
1085
|
+
server_address = server_address.lower()
|
|
1086
|
+
else:
|
|
1087
|
+
server_address = server_address.lower()
|
|
1088
|
+
connection_element.attrib["serverAddress"] = server_address
|
|
1066
1089
|
if connection_item.server_port is not None:
|
|
1067
1090
|
connection_element.attrib["serverPort"] = str(connection_item.server_port)
|
|
1068
1091
|
if connection_item.username is not None:
|
|
@@ -1077,7 +1100,7 @@ class Connection(object):
|
|
|
1077
1100
|
|
|
1078
1101
|
class TaskRequest(object):
|
|
1079
1102
|
@_tsrequest_wrapped
|
|
1080
|
-
def run_req(self, xml_request, task_item):
|
|
1103
|
+
def run_req(self, xml_request: ET.Element, task_item: Any) -> None:
|
|
1081
1104
|
# Send an empty tsRequest
|
|
1082
1105
|
pass
|
|
1083
1106
|
|
|
@@ -1214,7 +1237,7 @@ class SubscriptionRequest(object):
|
|
|
1214
1237
|
|
|
1215
1238
|
class EmptyRequest(object):
|
|
1216
1239
|
@_tsrequest_wrapped
|
|
1217
|
-
def empty_req(self, xml_request):
|
|
1240
|
+
def empty_req(self, xml_request: ET.Element) -> None:
|
|
1218
1241
|
pass
|
|
1219
1242
|
|
|
1220
1243
|
|
|
@@ -1273,6 +1296,124 @@ class CustomViewRequest(object):
|
|
|
1273
1296
|
if custom_view_item.name is not None:
|
|
1274
1297
|
updating_element.attrib["name"] = custom_view_item.name
|
|
1275
1298
|
|
|
1299
|
+
@_tsrequest_wrapped
|
|
1300
|
+
def _publish_xml(self, xml_request: ET.Element, custom_view_item: CustomViewItem) -> bytes:
|
|
1301
|
+
custom_view_element = ET.SubElement(xml_request, "customView")
|
|
1302
|
+
if (name := custom_view_item.name) is not None:
|
|
1303
|
+
custom_view_element.attrib["name"] = name
|
|
1304
|
+
else:
|
|
1305
|
+
raise ValueError(f"Custom View Item missing name: {custom_view_item}")
|
|
1306
|
+
if (shared := custom_view_item.shared) is not None:
|
|
1307
|
+
custom_view_element.attrib["shared"] = str(shared).lower()
|
|
1308
|
+
else:
|
|
1309
|
+
raise ValueError(f"Custom View Item missing shared: {custom_view_item}")
|
|
1310
|
+
if (owner := custom_view_item.owner) is not None:
|
|
1311
|
+
owner_element = ET.SubElement(custom_view_element, "owner")
|
|
1312
|
+
if (owner_id := owner.id) is not None:
|
|
1313
|
+
owner_element.attrib["id"] = owner_id
|
|
1314
|
+
else:
|
|
1315
|
+
raise ValueError(f"Custom View Item owner missing id: {owner}")
|
|
1316
|
+
else:
|
|
1317
|
+
raise ValueError(f"Custom View Item missing owner: {custom_view_item}")
|
|
1318
|
+
if (workbook := custom_view_item.workbook) is not None:
|
|
1319
|
+
workbook_element = ET.SubElement(custom_view_element, "workbook")
|
|
1320
|
+
if (workbook_id := workbook.id) is not None:
|
|
1321
|
+
workbook_element.attrib["id"] = workbook_id
|
|
1322
|
+
else:
|
|
1323
|
+
raise ValueError(f"Custom View Item workbook missing id: {workbook}")
|
|
1324
|
+
else:
|
|
1325
|
+
raise ValueError(f"Custom View Item missing workbook: {custom_view_item}")
|
|
1326
|
+
|
|
1327
|
+
return ET.tostring(xml_request)
|
|
1328
|
+
|
|
1329
|
+
def publish_req_chunked(self, custom_view_item: CustomViewItem):
|
|
1330
|
+
xml_request = self._publish_xml(custom_view_item)
|
|
1331
|
+
parts = {"request_payload": ("", xml_request, "text/xml")}
|
|
1332
|
+
return _add_multipart(parts)
|
|
1333
|
+
|
|
1334
|
+
def publish_req(self, custom_view_item: CustomViewItem, filename: str, file_contents: bytes):
|
|
1335
|
+
xml_request = self._publish_xml(custom_view_item)
|
|
1336
|
+
parts = {
|
|
1337
|
+
"request_payload": ("", xml_request, "text/xml"),
|
|
1338
|
+
"tableau_customview": (filename, file_contents, "application/octet-stream"),
|
|
1339
|
+
}
|
|
1340
|
+
return _add_multipart(parts)
|
|
1341
|
+
|
|
1342
|
+
|
|
1343
|
+
class GroupSetRequest:
|
|
1344
|
+
@_tsrequest_wrapped
|
|
1345
|
+
def create_request(self, xml_request: ET.Element, group_set_item: "GroupSetItem") -> bytes:
|
|
1346
|
+
group_set_element = ET.SubElement(xml_request, "groupSet")
|
|
1347
|
+
if group_set_item.name is not None:
|
|
1348
|
+
group_set_element.attrib["name"] = group_set_item.name
|
|
1349
|
+
return ET.tostring(xml_request)
|
|
1350
|
+
|
|
1351
|
+
@_tsrequest_wrapped
|
|
1352
|
+
def update_request(self, xml_request: ET.Element, group_set_item: "GroupSetItem") -> bytes:
|
|
1353
|
+
group_set_element = ET.SubElement(xml_request, "groupSet")
|
|
1354
|
+
if group_set_item.name is not None:
|
|
1355
|
+
group_set_element.attrib["name"] = group_set_item.name
|
|
1356
|
+
return ET.tostring(xml_request)
|
|
1357
|
+
|
|
1358
|
+
|
|
1359
|
+
class VirtualConnectionRequest:
|
|
1360
|
+
@_tsrequest_wrapped
|
|
1361
|
+
def update_db_connection(self, xml_request: ET.Element, connection_item: ConnectionItem) -> bytes:
|
|
1362
|
+
connection_element = ET.SubElement(xml_request, "connection")
|
|
1363
|
+
if connection_item.server_address is not None:
|
|
1364
|
+
connection_element.attrib["serverAddress"] = connection_item.server_address
|
|
1365
|
+
if connection_item.server_port is not None:
|
|
1366
|
+
connection_element.attrib["serverPort"] = str(connection_item.server_port)
|
|
1367
|
+
if connection_item.username is not None:
|
|
1368
|
+
connection_element.attrib["userName"] = connection_item.username
|
|
1369
|
+
if connection_item.password is not None:
|
|
1370
|
+
connection_element.attrib["password"] = connection_item.password
|
|
1371
|
+
|
|
1372
|
+
return ET.tostring(xml_request)
|
|
1373
|
+
|
|
1374
|
+
@_tsrequest_wrapped
|
|
1375
|
+
def update(self, xml_request: ET.Element, virtual_connection: VirtualConnectionItem) -> bytes:
|
|
1376
|
+
vc_element = ET.SubElement(xml_request, "virtualConnection")
|
|
1377
|
+
if virtual_connection.name is not None:
|
|
1378
|
+
vc_element.attrib["name"] = virtual_connection.name
|
|
1379
|
+
if virtual_connection.is_certified is not None:
|
|
1380
|
+
vc_element.attrib["isCertified"] = str(virtual_connection.is_certified).lower()
|
|
1381
|
+
if virtual_connection.certification_note is not None:
|
|
1382
|
+
vc_element.attrib["certificationNote"] = virtual_connection.certification_note
|
|
1383
|
+
if virtual_connection.project_id is not None:
|
|
1384
|
+
project_element = ET.SubElement(vc_element, "project")
|
|
1385
|
+
project_element.attrib["id"] = virtual_connection.project_id
|
|
1386
|
+
if virtual_connection.owner_id is not None:
|
|
1387
|
+
owner_element = ET.SubElement(vc_element, "owner")
|
|
1388
|
+
owner_element.attrib["id"] = virtual_connection.owner_id
|
|
1389
|
+
|
|
1390
|
+
return ET.tostring(xml_request)
|
|
1391
|
+
|
|
1392
|
+
@_tsrequest_wrapped
|
|
1393
|
+
def publish(self, xml_request: ET.Element, virtual_connection: VirtualConnectionItem, content: str) -> bytes:
|
|
1394
|
+
vc_element = ET.SubElement(xml_request, "virtualConnection")
|
|
1395
|
+
if virtual_connection.name is not None:
|
|
1396
|
+
vc_element.attrib["name"] = virtual_connection.name
|
|
1397
|
+
else:
|
|
1398
|
+
raise ValueError("Virtual Connection must have a name.")
|
|
1399
|
+
if virtual_connection.project_id is not None:
|
|
1400
|
+
project_element = ET.SubElement(vc_element, "project")
|
|
1401
|
+
project_element.attrib["id"] = virtual_connection.project_id
|
|
1402
|
+
else:
|
|
1403
|
+
raise ValueError("Virtual Connection must have a project id.")
|
|
1404
|
+
if virtual_connection.owner_id is not None:
|
|
1405
|
+
owner_element = ET.SubElement(vc_element, "owner")
|
|
1406
|
+
owner_element.attrib["id"] = virtual_connection.owner_id
|
|
1407
|
+
else:
|
|
1408
|
+
raise ValueError("Virtual Connection must have an owner id.")
|
|
1409
|
+
if content is not None:
|
|
1410
|
+
content_element = ET.SubElement(vc_element, "content")
|
|
1411
|
+
content_element.text = content
|
|
1412
|
+
else:
|
|
1413
|
+
raise ValueError("Virtual Connection must have content.")
|
|
1414
|
+
|
|
1415
|
+
return ET.tostring(xml_request)
|
|
1416
|
+
|
|
1276
1417
|
|
|
1277
1418
|
class RequestFactory(object):
|
|
1278
1419
|
Auth = AuthRequest()
|
|
@@ -1289,6 +1430,7 @@ class RequestFactory(object):
|
|
|
1289
1430
|
Flow = FlowRequest()
|
|
1290
1431
|
FlowTask = FlowTaskRequest()
|
|
1291
1432
|
Group = GroupRequest()
|
|
1433
|
+
GroupSet = GroupSetRequest()
|
|
1292
1434
|
Metric = MetricRequest()
|
|
1293
1435
|
Permission = PermissionRequest()
|
|
1294
1436
|
Project = ProjectRequest()
|
|
@@ -1299,5 +1441,6 @@ class RequestFactory(object):
|
|
|
1299
1441
|
Tag = TagRequest()
|
|
1300
1442
|
Task = TaskRequest()
|
|
1301
1443
|
User = UserRequest()
|
|
1444
|
+
VirtualConnection = VirtualConnectionRequest()
|
|
1302
1445
|
Workbook = WorkbookRequest()
|
|
1303
1446
|
Webhook = WebhookRequest()
|
|
@@ -2,6 +2,7 @@ import sys
|
|
|
2
2
|
|
|
3
3
|
from typing_extensions import Self
|
|
4
4
|
|
|
5
|
+
from tableauserverclient.config import config
|
|
5
6
|
from tableauserverclient.models.property_decorators import property_is_int
|
|
6
7
|
import logging
|
|
7
8
|
|
|
@@ -38,6 +39,7 @@ class RequestOptions(RequestOptionsBase):
|
|
|
38
39
|
LessThanOrEqual = "lte"
|
|
39
40
|
In = "in"
|
|
40
41
|
Has = "has"
|
|
42
|
+
CaseInsensitiveEquals = "cieq"
|
|
41
43
|
|
|
42
44
|
class Field:
|
|
43
45
|
Args = "args"
|
|
@@ -115,9 +117,9 @@ class RequestOptions(RequestOptionsBase):
|
|
|
115
117
|
Desc = "desc"
|
|
116
118
|
Asc = "asc"
|
|
117
119
|
|
|
118
|
-
def __init__(self, pagenumber=1, pagesize=
|
|
120
|
+
def __init__(self, pagenumber=1, pagesize=None):
|
|
119
121
|
self.pagenumber = pagenumber
|
|
120
|
-
self.pagesize = pagesize
|
|
122
|
+
self.pagesize = pagesize or config.PAGE_SIZE
|
|
121
123
|
self.sort = set()
|
|
122
124
|
self.filter = set()
|
|
123
125
|
|
|
@@ -5,9 +5,7 @@ import urllib3
|
|
|
5
5
|
|
|
6
6
|
from defusedxml.ElementTree import fromstring, ParseError
|
|
7
7
|
from packaging.version import Version
|
|
8
|
-
|
|
9
|
-
from . import CustomViews
|
|
10
|
-
from .endpoint import (
|
|
8
|
+
from tableauserverclient.server.endpoint import (
|
|
11
9
|
Sites,
|
|
12
10
|
Views,
|
|
13
11
|
Users,
|
|
@@ -34,13 +32,18 @@ from .endpoint import (
|
|
|
34
32
|
FlowRuns,
|
|
35
33
|
Metrics,
|
|
36
34
|
Endpoint,
|
|
35
|
+
CustomViews,
|
|
36
|
+
LinkedTasks,
|
|
37
|
+
GroupSets,
|
|
38
|
+
Tags,
|
|
39
|
+
VirtualConnections,
|
|
37
40
|
)
|
|
38
|
-
from .exceptions import (
|
|
41
|
+
from tableauserverclient.server.exceptions import (
|
|
39
42
|
ServerInfoEndpointNotFoundError,
|
|
40
43
|
EndpointUnavailableError,
|
|
41
44
|
)
|
|
42
|
-
from .endpoint.exceptions import NotSignedInError
|
|
43
|
-
from
|
|
45
|
+
from tableauserverclient.server.endpoint.exceptions import NotSignedInError
|
|
46
|
+
from tableauserverclient.namespace import Namespace
|
|
44
47
|
|
|
45
48
|
|
|
46
49
|
_PRODUCT_TO_REST_VERSION = {
|
|
@@ -100,6 +103,10 @@ class Server(object):
|
|
|
100
103
|
self.flow_runs = FlowRuns(self)
|
|
101
104
|
self.metrics = Metrics(self)
|
|
102
105
|
self.custom_views = CustomViews(self)
|
|
106
|
+
self.linked_tasks = LinkedTasks(self)
|
|
107
|
+
self.group_sets = GroupSets(self)
|
|
108
|
+
self.tags = Tags(self)
|
|
109
|
+
self.virtual_connections = VirtualConnections(self)
|
|
103
110
|
|
|
104
111
|
self._session = self._session_factory()
|
|
105
112
|
self._http_options = dict() # must set this before making a server call
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tableauserverclient
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.33
|
|
4
4
|
Summary: A Python module for working with the Tableau Server REST API.
|
|
5
5
|
Author-email: Tableau <github@tableau.com>
|
|
6
6
|
License: The MIT License (MIT)
|
|
@@ -37,20 +37,19 @@ Requires-Python: >=3.7
|
|
|
37
37
|
Description-Content-Type: text/markdown
|
|
38
38
|
License-File: LICENSE
|
|
39
39
|
License-File: LICENSE.versioneer
|
|
40
|
-
Requires-Dist: defusedxml
|
|
41
|
-
Requires-Dist: packaging
|
|
42
|
-
Requires-Dist: requests
|
|
43
|
-
Requires-Dist: urllib3
|
|
44
|
-
Requires-Dist: typing-extensions
|
|
40
|
+
Requires-Dist: defusedxml>=0.7.1
|
|
41
|
+
Requires-Dist: packaging>=23.1
|
|
42
|
+
Requires-Dist: requests>=2.31
|
|
43
|
+
Requires-Dist: urllib3==2.2.2
|
|
44
|
+
Requires-Dist: typing-extensions>=4.0.1
|
|
45
45
|
Provides-Extra: test
|
|
46
|
-
Requires-Dist:
|
|
47
|
-
Requires-Dist:
|
|
48
|
-
Requires-Dist:
|
|
49
|
-
Requires-Dist:
|
|
50
|
-
Requires-Dist: pytest
|
|
51
|
-
Requires-Dist: pytest-
|
|
52
|
-
Requires-Dist:
|
|
53
|
-
Requires-Dist: requests-mock <2.0,>=1.0 ; extra == 'test'
|
|
46
|
+
Requires-Dist: black==23.7; extra == "test"
|
|
47
|
+
Requires-Dist: build; extra == "test"
|
|
48
|
+
Requires-Dist: mypy==1.4; extra == "test"
|
|
49
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
50
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
51
|
+
Requires-Dist: pytest-subtests; extra == "test"
|
|
52
|
+
Requires-Dist: requests-mock<2.0,>=1.0; extra == "test"
|
|
54
53
|
|
|
55
54
|
# Tableau Server Client (Python)
|
|
56
55
|
|
|
@@ -63,14 +62,14 @@ Use the Tableau Server Client (TSC) library to increase your productivity as you
|
|
|
63
62
|
* Create users and groups.
|
|
64
63
|
* Query projects, sites, and more.
|
|
65
64
|
|
|
66
|
-
This repository contains Python source code for the library and sample files showing how to use it. As of
|
|
65
|
+
This repository contains Python source code for the library and sample files showing how to use it. As of September 2024, support for Python 3.7 and 3.8 will be dropped - support for older versions of Python aims to match https://devguide.python.org/versions/
|
|
67
66
|
|
|
68
67
|
To see sample code that works directly with the REST API (in Java, Python, or Postman), visit the [REST API Samples](https://github.com/tableau/rest-api-samples) repo.
|
|
69
68
|
|
|
70
69
|
For more information on installing and using TSC, see the documentation:
|
|
71
70
|
<https://tableau.github.io/server-client-python/docs/>
|
|
72
71
|
|
|
73
|
-
|
|
72
|
+
To contribute, see our [Developer Guide](https://tableau.github.io/server-client-python/docs/dev-guide). A list of all our contributors to date is in [CONTRIBUTORS.md].
|
|
74
73
|
|
|
75
74
|
## License
|
|
76
75
|
[](https://app.fossa.com/projects/git%2Bgithub.com%2Ftableau%2Fserver-client-python?ref=badge_large)
|