8000 add support for custom schedules in TOL (#1273) · jorwoods/server-client-python@5a5772c · GitHub
[go: up one dir, main page]

Skip to content

Commit 5a5772c

Browse files
authored
add support for custom schedules in TOL (tableau#1273)
* add support for custom schedules in TOL
1 parent 01e0372 commit 5a5772c

File tree

6 files changed

+169
-1
lines changed

6 files changed

+169
-1
lines changed

samples/create_extract_task.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
####
2+
# This script demonstrates how to create extract tasks in Tableau Cloud
3+
# using the Tableau 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+
12+
from datetime import time
13+
14+
import tableauserverclient as TSC
15+
16+
17+
def main():
18+
parser = argparse.ArgumentParser(description="Creates sample extract refresh task.")
19+
# Common options; please keep those in sync across all samples
20+
parser.add_argument("--server", "-s", help="server address")
21+
parser.add_argument("--site", "-S", help="site name")
22+
parser.add_argument("--token-name", "-p", help="name of the personal access token used to sign into the server")
23+
parser.add_argument("--token-value", "-v", help="value of the personal access token used to sign into the server")
24+
parser.add_argument(
25+
"--logging-level",
26+
"-l",
27+
choices=["debug", "info", "error"],
28+
default="error",
29+
help="desired logging level (set to error by default)",
30+
)
31+
# Options specific to this sample:
32+
# This sample has no additional options, yet. If you add some, please add them here
33+
34+
args = parser.parse_args()
35+
36+
# Set logging level based on user input, or error by default
37+
logging_level = getattr(logging, args.logging_level.upper())
38+
logging.basicConfig(level=logging_level)
39+
40+
tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site)
41+
server = TSC.Server(args.server, use_server_version=False)
42+
server.add_http_options({"verify": False})
43+
server.use_server_version()
44+
with server.auth.sign_in(tableau_auth):
45+
# Monthly Schedule
46+
# This schedule will run on the 15th of every month at 11:30PM
47+
monthly_interval = TSC.MonthlyInterval(start_time=time(23, 30), interval_value=15)
48+
monthly_schedule = TSC.ScheduleItem(
49+
None,
50+
None,
51+
None,
52+
None,
53+
monthly_interval,
54+
)
55+
56+
# Default to using first workbook found in server
57+
all_workbook_items, pagination_item = server.workbooks.get()
58+
my_workbook: TSC.WorkbookItem = all_workbook_items[0]
59+
60+
target_item = TSC.Target(
61+
my_workbook.id, # the id of the workbook or datasource
62+
"workbook", # alternatively can be "datasource"
63+
)
64+
65+
extract_item = TSC.TaskItem(
66+
None,
67+
"FullRefresh",
68+
None,
69+
None,
70+
None,
71+
monthly_schedule,
72+
None,
73+
target_item,
74+
)
75+
76+
try:
77+
response = server.tasks.create(extract_item)
78+
print(response)
79+
except Exception as e:
80+
print(e)
81+
82+
83+
if __name__ == "__main__":
84+
main()

tableauserverclient/models/subscription_item.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from .property_decorators import property_is_boolean
66
from .target import Target
7+
from tableauserverclient.models import ScheduleItem
78

89
if TYPE_CHECKING:
910
from .target import Target
@@ -23,6 +24,7 @@ def __init__(self, subject: str, schedule_id: str, user_id: str, target: "Target
2324
self.suspended = False
2425
self.target = target
2526
self.user_id = user_id
27+
self.schedule = None
2628

2729
def __repr__(self) -> str:
2830
if self.id is not None:
@@ -92,9 +94,14 @@ def _parse_element(cls, element, ns):
9294

9395
# Schedule element
9496
schedule_id = None
97+
schedule = None
9598
if schedule_element is not None:
9699
schedule_id = schedule_element.get("id", None)
97100

101+
# If schedule id is not provided, then TOL with full schedule provided
102+
if schedule_id is None:
103+
schedule = ScheduleItem.from_element(element, ns)
104+
98105
# Content element
99106
target = None
100107
send_if_view_empty = None
@@ -127,6 +134,7 @@ def _parse_element(cls, element, ns):
127134
sub.page_size_option = page_size_option
128135
sub.send_if_view_empty = send_if_view_empty
129136
sub.suspended = suspended
137+
sub.schedule = schedule
130138

131139
return sub
132140

tableauserverclient/server/endpoint/tasks_endpoint.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@ def get_by_id(self, task_id):
5151
server_response = self.get_request(url)
5252
return TaskItem.from_response(server_response.content, self.parent_srv.namespace)[0]
5353

54+
@api(version="3.19")
55+
def create(self, extract_item: TaskItem) -> TaskItem:
56+
if not extract_item:
57+
error = "No extract refresh provided"
58+
raise ValueError(error)
59+
logger.info("Creating an extract refresh ({})".format(extract_item))
60+
url = "{0}/{1}".format(self.baseurl, self.__normalize_task_type(TaskItem.Type.ExtractRefresh))
61+
create_req = RequestFactory.Task.create_extract_req(extract_item)
62+
server_response = self.post_request(url, create_req)
63+
return server_response.content
64+
5465
@api(version="2.6")
5566
def run(self, task_item):
5667
if not task_item.id:
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,34 @@ def run_req(self, xml_request, task_item):
10281028
# Send an empty tsRequest
10291029
pass
10301030

1031+
@_tsrequest_wrapped
1032+
def create_extract_req(self, xml_request: ET.Element, extract_item: "TaskItem") -> bytes:
1033+
extract_element = ET.SubElement(xml_request, "extractRefresh")
1034+
1035+
# Schedule attributes
1036+
schedule_element = ET.SubElement(xml_request, "schedule")
1037+
1038+
interval_item = extract_item.schedule_item.interval_item
1039+
schedule_element.attrib["frequency"] = interval_item._frequency
1040+
frequency_element = ET.SubElement(schedule_element, "frequencyDetails")
1041+
frequency_element.attrib["start"] = str(interval_item.start_time)
1042+
if hasattr(interval_item, "end_time") and interval_item.end_time is not None:
1043+
frequency_element.attrib["end"] = str(interval_item.end_time)
1044+
if hasattr(interval_item, "interval") and interval_item.interval:
1045+
intervals_element = ET.SubElement(frequency_element, "intervals")
1046+
for interval in interval_item._interval_type_pairs():
1047+
expression, value = interval
1048+
single_interval_element = ET.SubElement(intervals_element, "interval")
1049+
single_interval_element.attrib[expression] = value
1050+
1051+
# Main attributes
1052+
extract_element.attrib["type"] = extract_item.task_type
1053+
1054+
target_element = ET.SubElement(extract_element, extract_item.target.type)
1055+
target_element.attrib["id"] = extract_item.target.id
1056+
1057+
return ET.tostring(xml_request)
1058+
10311059

10321060
class SubscriptionRequest(object):
10331061
@_tsrequest_wrapped
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<tsResponse xmlns="http://tableau.com/api" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api https://help.tableau.com/samples/en-us/rest_api/ts-api_3_19.xsd">
2+
<extractRefresh id="task_id" type="FullRefresh">
3+
<workbook id="workbook_id"/>
4+
</extractRefresh>
5+
<schedule createdAt="2023-08-17T15:36:37-0700" updatedAt="2023-08-17T15:36:37-0700" frequency="Monthly" nextRunAt="2023-09-15T23:30:00-0700">
6+
<frequencyDetails start="23:30:00">
7+
<intervals>
8+
<interval monthDay="15"/>
9+
</intervals>
10+
</frequencyDetails>
11+
</schedule>
12+
</tsResponse>

test/test_task.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import unittest
3+
from datetime import time
34

45
import requests_mock
56

@@ -15,12 +16,13 @@
1516
GET_XML_WITH_WORKBOOK_AND_DATASOURCE = os.path.join(TEST_ASSET_DIR, "tasks_with_workbook_and_datasource.xml")
1617
GET_XML_DATAACCELERATION_TASK = os.path.join(TEST_ASSET_DIR, "tasks_with_dataacceleration_task.xml")
1718
GET_XML_RUN_NOW_RESPONSE = os.path.join(TEST_ASSET_DIR, "tasks_run_now_response.xml")
19+
GET_XML_CREATE_TASK_RESPONSE = os.path.join(TEST_ASSET_DIR, "tasks_create_extract_task.xml")
1820

1921

2022
class TaskTests(unittest.TestCase):
2123
def setUp(self):
2224
self.server = TSC.Server("http://test", False)
23-
self.server.version = "3.8"
25+
self.server.version = "3.19"
2426

2527
# Fake Signin
2628
self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
@@ -141,3 +143,26 @@ def test_run_now(self):
141143

142144
self.assertTrue("7b6b59a8-ac3c-4d1d-2e9e-0b5b4ba8a7b6" in job_response_content)
143145
self.assertTrue("RefreshExtract" in job_response_content)
146+
147+
def test_create_extract_task(self):
148+
monthly_interval = TSC.MonthlyInterval(start_time=time(23, 30), interval_value=15)
149+
monthly_schedule = TSC.ScheduleItem(
150+
None,
151+
None,
152+
None,
153+
None,
154+
monthly_interval,
155+
)
156+
target_item = TSC.Target("workbook_id", "workbook")
157+
158+
task = TaskItem(None, "FullRefresh", None, schedule_item=monthly_schedule, target=target_item)
159+
160+
with open(GET_XML_CREATE_TASK_RESPONSE, "rb") as f:
161+
response_xml = f.read().decode("utf-8")
162+
with requests_mock.mock() as m:
163+
m.post("{}".format(self.baseurl), text=response_xml)
164+
create_response_content = self.server.tasks.create(task).decode("utf-8")
165+
166+
self.assertTrue("task_id" in create_response_content)
167+
self.assertTrue("workbook_id" in create_response_content)
168+
self.assertTrue("FullRefresh" in create_response_content)

0 commit comments

Comments
 (0)
0