Skip to content

Commit 71bcb4f

Browse files
committed
Merge branch 'development' into bcantoni/1365-monthly-schedule
2 parents 630eaed + d61ff8f commit 71bcb4f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2630
-2242
lines changed

CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#ECCN:Open Source
2+
#GUSINFO:Open Source,Open Source Workflow

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ repository = "https://github.com/tableau/server-client-python"
3333

3434
[project.optional-dependencies]
3535
test = ["black==24.8", "build", "mypy==1.4", "pytest>=7.0", "pytest-cov", "pytest-subtests",
36-
"requests-mock>=1.0,<2.0"]
37-
36+
"requests-mock>=1.0,<2.0", "types-requests>=2.32.4.20250913"]
3837
[tool.black]
3938
line-length = 120
4039
target-version = ['py39', 'py310', 'py311', 'py312', 'py313']

samples/create_user.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
####
2+
# This script demonstrates how to create a user using the Tableau
3+
# Server Client.
4+
#
5+
# To run the script, you must have installed Python 3.7 or later.
6+
####
7+
8+
9+
import argparse
10+
import logging
11+
import os
12+
import sys
13+
from typing import Sequence
14+
15+
import tableauserverclient as TSC
16+
17+
18+
def parse_args(args: Sequence[str] | None) -> argparse.Namespace:
19+
"""
20+
Parse command line parameters
21+
"""
22+
if args is None:
23+
args = sys.argv[1:]
24+
parser = argparse.ArgumentParser(description="Creates a sample user group.")
25+
# Common options; please keep those in sync across all samples
26+
parser.add_argument("--server", "-s", help="server address")
27+
parser.add_argument("--site", "-S", help="site name")
28+
parser.add_argument("--token-name", "-p", help="name of the personal access token used to sign into the server")
29+
parser.add_argument("--token-value", "-v", help="value of the personal access token used to sign into the server")
30+
parser.add_argument(
31+
"--logging-level",
32+
"-l",
33+
choices=["debug", "info", "error"],
34+
default="error",
35+
help="desired logging level (set to error by default)",
36+
)
37+
# Options specific to this sample
38+
# This sample has no additional options, yet. If you add some, please add them here
39+
parser.add_argument("--role", "-r", help="Site Role for the new user", default="Unlicensed")
40+
parser.add_argument(
41+
"--user",
42+
"-u",
43+
help="Username for the new user. If using active directory, it should be in the format of SAMAccountName@FullyQualifiedDomainName",
44+
)
45+
parser.add_argument(
46+
"--email", "-e", help="Email address of the new user. If using active directory, this field is optional."
47+
)
48+
49+
return parser.parse_args(args)
50+
51+
52+
def main():
53+
args = parse_args(None)
54+
55+
# Set logging level based on user input, or error by default
56+
logging_level = getattr(logging, args.logging_level.upper())
57+
logging.basicConfig(level=logging_level)
58+
59+
tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site)
60+
server = TSC.Server(args.server, use_server_version=True, http_options={"verify": False})
61+
with server.auth.sign_in(tableau_auth):
62+
# this code shows 2 different error codes for common mistakes
63+
# 400013: Invalid site role
64+
# 409000: user already exists on site
65+
66+
user = TSC.UserItem(args.user, args.role)
67+
if args.email:
68+
user.email = args.email
69+
user = server.users.add(user)
70+
71+
72+
if __name__ == "__main__":
73+
main()
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
####
2+
# This script demonstrates how to use the metadata API to query information on a published data source
3+
#
4+
# To run the script, you must have installed Python 3.7 or later.
5+
####
6+
7+
import argparse
8+
import logging
9+
from pprint import pprint
10+
11+
import tableauserverclient as TSC
12+
13+
14+
def main():
15+
parser = argparse.ArgumentParser(description="Use the metadata API to get information on a published data source.")
16+
# Common options; please keep those in sync across all samples
17+
parser.add_argument("--server", "-s", help="server address")
18+
parser.add_argument("--site", "-S", help="site name")
19+
parser.add_argument("--token-name", "-n", help="name of the personal access token used to sign into the server")
20+
parser.add_argument("--token-value", "-v", help="value of the personal access token used to sign into the server")
21+
parser.add_argument(
22+
"--logging-level",
23+
"-l",
24+
choices=["debug", "info", "error"],
25+
default="error",
26+
help="desired logging level (set to error by default)",
27+
)
28+
# Options specific to this sample
29+
parser.add_argument(
30+
"datasource_name",
31+
nargs="?",
32+
help="The name of the published datasource. If not present, we query all data sources.",
33+
)
34+
35+
args = parser.parse_args()
36+
37+
# Set logging level based on user input, or error by default
38+
logging_level = getattr(logging, args.logging_level.upper())
39+
logging.basicConfig(level=logging_level)
40+
41+
# Sign in to server
42+
tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site)
43+
server = TSC.Server(args.server, use_server_version=True)
44+
with server.auth.sign_in(tableau_auth):
45+
# Execute the query
46+
result = server.metadata.query(
47+
"""
48+
# Query must declare that it accepts first and afterToken variables
49+
query paged($first:Int, $afterToken:String) {
50+
workbooksConnection(first: $first, after:$afterToken) {
51+
nodes {
52+
luid
53+
name
54+
projectName
55+
description
56+
}
57+
totalCount
58+
pageInfo {
59+
endCursor
60+
hasNextPage
61+
}
62+
}
63+
}
64+
""",
65+
# "first" adjusts the page size. Here we set it to 5 to demonstrate pagination.
66+
# Set it to a higher number to reduce the number of pages. Including
67+
# first and afterToken is optional, and if not included, TSC will
68+
# use its default page size of 100.
69+
variables={"first": 5, "afterToken": None},
70+
)
71+
72+
# Multiple pages are captured in result["pages"]. Each page contains
73+
# the result of one execution of the query above.
74+
for page in result["pages"]:
75+
# Display warnings/errors (if any)
76+
if page.get("errors"):
77+
print("### Errors/Warnings:")
78+
pprint(result["errors"])
79+
80+
# Print the results
81+
if result.get("data"):
82+
print("### Results:")
83+
pprint(result["data"]["workbooksConnection"]["nodes"])
84+
85+
86+
if __name__ == "__main__":
87+
main()

tableauserverclient/models/data_freshness_policy_item.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def interval_item(self) -> Optional[list[str]]:
6666
return self._interval_item
6767

6868
@interval_item.setter
69-
def interval_item(self, value: list[str]):
69+
def interval_item(self, value: Optional[list[str]]):
7070
self._interval_item = value
7171

7272
@property
@@ -127,15 +127,15 @@ def fresh_every_schedule(self) -> Optional[FreshEvery]:
127127
return self._fresh_every_schedule
128128

129129
@fresh_every_schedule.setter
130-
def fresh_every_schedule(self, value: FreshEvery):
130+
def fresh_every_schedule(self, value: Optional[FreshEvery]):
131131
self._fresh_every_schedule = value
132132

133133
@property
134134
def fresh_at_schedule(self) -> Optional[FreshAt]:
135135
return self._fresh_at_schedule
136136

137137
@fresh_at_schedule.setter
138-
def fresh_at_schedule(self, value: FreshAt):
138+
def fresh_at_schedule(self, value: Optional[FreshAt]):
139139
self._fresh_at_schedule = value
140140

141141
@classmethod

tableauserverclient/models/datasource_item.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,13 +535,15 @@ def _parse_element(datasource_xml: ET.Element, ns: dict) -> tuple:
535535

536536
project_id = None
537537
project_name = None
538+
project = None
538539
project_elem = datasource_xml.find(".//t:project", namespaces=ns)
539540
if project_elem is not None:
540541
project = ProjectItem.from_xml(project_elem, ns)
541542
project_id = project_elem.get("id", None)
542543
project_name = project_elem.get("name", None)
543544

544545
owner_id = None
546+
owner = None
545547
owner_elem = datasource_xml.find(".//t:owner", namespaces=ns)
546548
if owner_elem is not None:
547549
owner = UserItem.from_xml(owner_elem, ns)

tableauserverclient/models/flow_item.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def description(self) -> Optional[str]:
129129
return self._description
130130

131131
@description.setter
132-
def description(self, value: str) -> None:
132+
def description(self, value: Optional[str]) -> None:
133133
self._description = value
134134

135135
@property

tableauserverclient/models/group_item.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Callable, Optional, TYPE_CHECKING
22

33
from defusedxml.ElementTree import fromstring
4+
from typing_extensions import Self
45

56
from .exceptions import UnpopulatedPropertyError
67
from .property_decorators import property_not_empty, property_is_enum
@@ -92,7 +93,7 @@ def name(self) -> Optional[str]:
9293
return self._name
9394

9495
@name.setter
95-
def name(self, value: str) -> None:
96+
def name(self, value: Optional[str]) -> None:
9697
self._name = value
9798

9899
@property
@@ -157,3 +158,8 @@ def from_response(cls, resp, ns) -> list["GroupItem"]:
157158
@staticmethod
158159
def as_reference(id_: str) -> ResourceReference:
159160
return ResourceReference(id_, GroupItem.tag_name)
161+
162+
def to_reference(self: Self) -> ResourceReference:
163+
if self.id is None:
164+
raise ValueError(f"{self.__class__.__qualname__} must have id to be converted to reference")
165+
return ResourceReference(self.id, self.tag_name)

tableauserverclient/models/groupset_item.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import xml.etree.ElementTree as ET
33

44
from defusedxml.ElementTree import fromstring
5+
from typing_extensions import Self
56

67
from tableauserverclient.models.group_item import GroupItem
78
from tableauserverclient.models.reference_item import ResourceReference
@@ -24,6 +25,14 @@ def __str__(self) -> str:
2425
def __repr__(self) -> str:
2526
return self.__str__()
2627

28+
@property
29+
def name(self) -> Optional[str]:
30+
return self._name
31+
32+
@name.setter
33+
def name(self, value: Optional[str]) -> None:
34+
self._name = value
35+
2736
@classmethod
2837
def from_response(cls, response: bytes, ns: dict[str, str]) -> list["GroupSetItem"]:
2938
parsed_response = fromstring(response)
@@ -51,3 +60,8 @@ def get_group(group_xml: ET.Element) -> GroupItem:
5160
@staticmethod
5261
def as_reference(id_: str) -> ResourceReference:
5362
return ResourceReference(id_, GroupSetItem.tag_name)
63+
64+
def to_reference(self: Self) -> ResourceReference:
65+
if self.id is None:
66+
raise ValueError(f"{self.__class__.__qualname__} must have id to be converted to reference")
67+
return ResourceReference(self.id, self.tag_name)

tableauserverclient/models/permissions_item.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class Capability:
4343
CreateRefreshMetrics = "CreateRefreshMetrics"
4444
SaveAs = "SaveAs"
4545
PulseMetricDefine = "PulseMetricDefine"
46+
ExtractRefresh = "ExtractRefresh"
4647
WebAuthoringForFlows = "WebAuthoringForFlows"
4748

4849
def __repr__(self):

0 commit comments

Comments
 (0)