8000 1580 list extracts on schedule (#1604) · tableau/server-client-python@f9bc99b · GitHub
[go: up one dir, main page]

Skip to content

Commit f9bc99b

Browse files
1580 list extracts on schedule (#1604)
* determine what datasources or workbooks are associated with a schedule
1 parent dbf80a7 commit f9bc99b

File tree

7 files changed

+136
-3
lines changed

7 files changed

+136
-3
lines changed

samples/extracts.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ def main():
4242
8000 server.add_http_options({"verify": False})
4343
server.use_server_version()
4444
with server.auth.sign_in(tableau_auth):
45-
4645
wb = None
4746
ds = None
4847
if args.workbook:

tableauserverclient/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
from tableauserverclient.models.virtual_connection_item import VirtualConnectionItem
5050
from tableauserverclient.models.webhook_item import WebhookItem
5151
from tableauserverclient.models.workbook_item import WorkbookItem
52+
from tableauserverclient.models.extract_item import ExtractItem
5253

5354
__all__ = [
5455
"ColumnItem",
@@ -106,4 +107,5 @@
106107
"LinkedTaskItem",
107108
"LinkedTaskStepItem",
108109
"LinkedTaskFlowRunItem",
110+
"ExtractItem",
109111
]
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from typing import Optional, List
2+
from defusedxml.ElementTree import fromstring
3+
import xml.etree.ElementTree as ET
4+
5+
6+
class ExtractItem:
7+
"""
8+
An extract refresh task item.
9+
10+
Attributes
11+
----------
12+
id : str
13+
The ID of the extract refresh task
14+
priority : int
15+
The priority of the task
16+
type : str
17+
The type of extract refresh (incremental or full)
18+
workbook_id : str, optional
19+
The ID of the workbook if this is a workbook extract
20+
datasource_id : str, optional
21+
The ID of the datasource if this is a datasource extract
22+
"""
23+
24+
def __init__(
25+
self, priority: int, refresh_type: str, workbook_id: Optional[str] = None, datasource_id: Optional[str] = None
26+
):
27+
self._id: Optional[str] = None
28+
self._priority = priority
29+
self._type = refresh_type
30+
self._workbook_id = workbook_id
31+
self._datasource_id = datasource_id
32+
33+
@property
34+
def id(self) -> Optional[str]:
35+
return self._id
36+
37+
@property
38+
def priority(self) -> int:
39+
return self._priority
40+
41+
@property
42+
def type(self) -> str:
43+
return self._type
44+
45+
@property
46+
def workbook_id(self) -> Optional[str]:
47+
return self._workbook_id
48+
49+
@property
50+
def datasource_id(self) -> Optional[str]:
51+
return self._datasource_id
52+
53+
@classmethod
54+
def from_response(cls, resp: str, ns: dict) -> List["ExtractItem"]:
55+
"""Create ExtractItem objects from XML response."""
56+
parsed_response = fromstring(resp)
57+
return cls.from_xml_element(parsed_response, ns)
58+
59+
@classmethod
60+
def from_xml_element(cls, parsed_response: ET.Element, ns: dict) -> List["ExtractItem"]:
61+
"""Create ExtractItem objects from XML element."""
62+
all_extract_items = []
63+
all_extract_xml = parsed_response.findall(".//t:extract", namespaces=ns)
64+
65+
for extract_xml in all_extract_xml:
66+
extract_id = extract_xml.get("id", None)
67+
priority = int(extract_xml.get("priority", 0))
68+
refresh_type = extract_xml.get("type", "")
69+
70+
# Check for workbook or datasource
71+
workbook_elem = extract_xml.find(".//t:workbook", namespaces=ns)
72+
datasource_elem = extract_xml.find(".//t:datasource", namespaces=ns)
73+
74+
workbook_id = workbook_elem.get("id", None) if workbook_elem is not None else None
75+
datasource_id = datasource_elem.get("id", None) if datasource_elem is not None else None
76+
77+
extract_item = cls(priority, refresh_type, workbook_id, datasource_id)
78+
extract_item._id = extract_id
79+
80+
all_extract_items.append(extract_item)
81+
82+
return all_extract_items

tableauserverclient/server/endpoint/schedules_endpoint.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from .endpoint import Endpoint, api, parameter_added_in
88
from .exceptions import MissingRequiredFieldError
99
from tableauserverclient.server import RequestFactory
10-
from tableauserverclient.models import PaginationItem, ScheduleItem, TaskItem
10+
from tableauserverclient.models import PaginationItem, ScheduleItem, TaskItem, ExtractItem
1111

1212
from tableauserverclient.helpers.logging import logger
1313

@@ -261,3 +261,21 @@ def _add_to(
261261
)
262262
else:
263263
return OK
264+
265+
@api(version="2.3")
266+
def get_extract_refresh_tasks(
267+
self, schedule_id: str, req_options: Optional["RequestOptions"] = None
268+
) -> tuple[list["ExtractItem"], "PaginationItem"]:
269+
"""Get all extract refresh tasks for the specified schedule."""
270+
if not schedule_id:
271+
error = "Schedule ID undefined"
272+
raise ValueError(error)
273+
274+
logger.info(f"Querying extract refresh tasks for schedule (ID: {schedule_id})")
275+
url = f"{self.siteurl}/{schedule_id}/extracts"
276+
server_response = self.get_request(url, req_options)
277+
278+
pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
279+
extract_items = ExtractItem.from_response(server_response.content, self.parent_srv.namespace)
280+
281+
return extract_items, pagination_item
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<tsResponse xmlns="http://tableau.com/api" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-2.3.xsd">
3+
<extracts>
4+
<extract id="task1"
5+
priority="1"
6+
type="IncrementalRefresh" >
7+
<workbook id="workbook-id" />
8+
</extract>
9+
<extract id="task2"
10+
priority="2"
11+
type="IncrementalRefresh" >
12+
<datasource id="datasource-id" />
13+
</extract>
14+
</extracts>
15+
</tsResponse>

test/request_factory/test_task_requests.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66

77
class TestTaskRequest(unittest.TestCase):
8-
98
def setUp(self):
109
self.task_request = TaskRequest()
1110
self.xml_request = ET.Element("tsRequest")

test/test_schedule.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
ADD_WORKBOOK_TO_SCHEDULE_WITH_WARNINGS = os.path.join(TEST_ASSET_DIR, "schedule_add_workbook_with_warnings.xml")
2626
ADD_DATASOURCE_TO_SCHEDULE = os.path.join(TEST_ASSET_DIR, "schedule_add_datasource.xml")
2727
ADD_FLOW_TO_SCHEDULE = os.path.join(TEST_ASSET_DIR, "schedule_add_flow.xml")
28+
GET_EXTRACT_TASKS_XML = os.path.join(TEST_ASSET_DIR, "schedule_get_extract_refresh_tasks.xml")
2829

2930
WORKBOOK_GET_BY_ID_XML = os.path.join(TEST_ASSET_DIR, "workbook_get_by_id.xml")
3031
DATASOURCE_GET_BY_ID_XML = os.path.join(TEST_ASSET_DIR, "datasource_get_by_id.xml")
@@ -405,3 +406,20 @@ def test_add_flow(self) -> None:
405406
flow = self.server.flows.get_by_id("bar")
406407
result = self.server.schedules.add_to_schedule("foo", flow=flow)
407408
self.assertEqual(0, len(result), "Added properly")
409+
410+
def test_get_extract_refresh_tasks(self) -> None:
411+
self.server.version = "2.3"
412+
413+
with open(GET_EXTRACT_TASKS_XML, "rb") as f:
414+
response_xml = f.read().decode("utf-8")
415+
with requests_mock.mock() as m:
416+
schedule_id = "c9cff7f9-309c-4361-99ff-d4ba8c9f5467"
417+
baseurl = f"{self.server.baseurl}/sites/{self.server.site_id}/schedules/{schedule_id}/extracts"
418+
m.get(baseurl, text=response_xml)
419+
420+
extracts = self.server.schedules.get_extract_refresh_tasks(schedule_id)
421+
422+
self.assertIsNotNone(extracts)
423+
self.assertIsInstance(extracts[0], list)
424+
self.assertEqual(2, len(extracts[0]))
425+
self.assertEqual("task1", extracts[0][0].id)

0 commit comments

Comments
 (0)
0