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.
Files changed (54) hide show
  1. tableauserverclient/__init__.py +74 -4
  2. tableauserverclient/_version.py +3 -3
  3. tableauserverclient/config.py +16 -4
  4. tableauserverclient/models/__init__.py +100 -37
  5. tableauserverclient/models/connection_item.py +4 -2
  6. tableauserverclient/models/database_item.py +6 -0
  7. tableauserverclient/models/datasource_item.py +18 -6
  8. tableauserverclient/models/favorites_item.py +7 -7
  9. tableauserverclient/models/flow_item.py +6 -6
  10. tableauserverclient/models/groupset_item.py +53 -0
  11. tableauserverclient/models/interval_item.py +27 -14
  12. tableauserverclient/models/job_item.py +18 -2
  13. tableauserverclient/models/linked_tasks_item.py +102 -0
  14. tableauserverclient/models/permissions_item.py +53 -5
  15. tableauserverclient/models/project_item.py +4 -3
  16. tableauserverclient/models/reference_item.py +5 -0
  17. tableauserverclient/models/tableau_auth.py +22 -23
  18. tableauserverclient/models/tableau_types.py +9 -7
  19. tableauserverclient/models/virtual_connection_item.py +77 -0
  20. tableauserverclient/server/__init__.py +83 -8
  21. tableauserverclient/server/endpoint/__init__.py +69 -28
  22. tableauserverclient/server/endpoint/auth_endpoint.py +3 -3
  23. tableauserverclient/server/endpoint/custom_views_endpoint.py +66 -5
  24. tableauserverclient/server/endpoint/databases_endpoint.py +21 -18
  25. tableauserverclient/server/endpoint/datasources_endpoint.py +115 -35
  26. tableauserverclient/server/endpoint/endpoint.py +53 -20
  27. tableauserverclient/server/endpoint/favorites_endpoint.py +1 -1
  28. tableauserverclient/server/endpoint/fileuploads_endpoint.py +2 -2
  29. tableauserverclient/server/endpoint/flow_runs_endpoint.py +45 -5
  30. tableauserverclient/server/endpoint/flows_endpoint.py +43 -16
  31. tableauserverclient/server/endpoint/groups_endpoint.py +85 -31
  32. tableauserverclient/server/endpoint/groupsets_endpoint.py +127 -0
  33. tableauserverclient/server/endpoint/jobs_endpoint.py +74 -7
  34. tableauserverclient/server/endpoint/linked_tasks_endpoint.py +45 -0
  35. tableauserverclient/server/endpoint/metadata_endpoint.py +3 -3
  36. tableauserverclient/server/endpoint/metrics_endpoint.py +1 -1
  37. tableauserverclient/server/endpoint/projects_endpoint.py +50 -18
  38. tableauserverclient/server/endpoint/resource_tagger.py +135 -3
  39. tableauserverclient/server/endpoint/tables_endpoint.py +19 -16
  40. tableauserverclient/server/endpoint/users_endpoint.py +40 -1
  41. tableauserverclient/server/endpoint/views_endpoint.py +97 -10
  42. tableauserverclient/server/endpoint/virtual_connections_endpoint.py +173 -0
  43. tableauserverclient/server/endpoint/workbooks_endpoint.py +97 -45
  44. tableauserverclient/server/pager.py +46 -46
  45. tableauserverclient/server/query.py +62 -33
  46. tableauserverclient/server/request_factory.py +192 -49
  47. tableauserverclient/server/request_options.py +4 -2
  48. tableauserverclient/server/server.py +13 -6
  49. {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/METADATA +15 -16
  50. {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/RECORD +54 -48
  51. {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/WHEEL +1 -1
  52. {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/LICENSE +0 -0
  53. {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/LICENSE.versioneer +0 -0
  54. {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
- def _tsrequest_wrapped(func):
27
- def wrapper(self, *args, **kwargs) -> bytes:
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(self, group_item: GroupItem, default_site_role: Optional[str] = None) -> bytes:
422
- # (1/8/2021): Deprecated starting v0.15
423
- if default_site_role is not None:
424
- import warnings
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(self, xml_request, include_all=True, datasources=None):
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
- datasource_element.attrib["id"] = datasource_item.id
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
- connection_element.attrib["serverAddress"] = connection_item.server_address.lower()
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=100):
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 ..namespace import Namespace
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.31
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 >=0.7.1
41
- Requires-Dist: packaging >=23.1
42
- Requires-Dist: requests >=2.31
43
- Requires-Dist: urllib3 ==2.0.7
44
- Requires-Dist: typing-extensions >=4.0.1
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: argparse ; extra == 'test'
47
- Requires-Dist: black ==23.7 ; extra == 'test'
48
- Requires-Dist: mock ; extra == 'test'
49
- Requires-Dist: mypy ==1.4 ; extra == 'test'
50
- Requires-Dist: pytest >=7.0 ; extra == 'test'
51
- Requires-Dist: pytest-cov ; extra == 'test'
52
- Requires-Dist: pytest-subtests ; extra == 'test'
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 May 2022, Python versions 3.7 and up are supported.
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
  [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftableau%2Fserver-client-python.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftableau%2Fserver-client-python?ref=badge_large)