8000 Jorwoods/download excel files from views (#968) · tableau/server-client-python@5682b8e · GitHub
[go: up one dir, main page]

Skip to content

Commit 5682b8e

Browse files
jorwoodsjacalata
authored andcommitted
Jorwoods/download excel files from views (#968)
* Add support for downloading excel crosstabs from views
1 parent 84e7371 commit 5682b8e

File tree

4 files changed

+59
-13
lines changed

4 files changed

+59
-13
lines changed

tableauserverclient/models/view_item.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def __init__(self) -> None:
2525
self._project_id: Optional[str] = None
2626
self._pdf: Optional[Callable[[], bytes]] = None
2727
self._csv: Optional[Callable[[], Iterable[bytes]]] = None
28+
self._excel: Optional[Callable[[], Iterable[bytes]]] = None
2829
self._total_views: Optional[int] = None
2930
self._sheet_type: Optional[str] = None
3031
self._updated_at: Optional["datetime"] = None
@@ -44,6 +45,9 @@ def _set_pdf(self, pdf):
4445
def _set_csv(self, csv):
4546
self._csv = csv
4647

48+
def _set_excel(self, excel):
49+
self._excel = excel
50+
4751
@property
4852
def content_url(self) -> Optional[str]:
4953
return self._content_url
@@ -96,6 +100,13 @@ def csv(self) -> Iterable[bytes]:
96100
raise UnpopulatedPropertyError(error)
97101
return self._csv()
98102

103+
@property
104+
def excel(self) -> Iterable[bytes]:
105+
if self._excel is None:
106+
error = "View item must be populated with its excel first."
107+
raise UnpopulatedPropertyError(error)
108+
return self._excel()
109+
99110
@property
100111
def sheet_type(self) -> Optional[str]:
101112
return self._sheet_type

tableauserverclient/server/endpoint/views_endpoint.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,25 @@ def _get_view_csv(self, view_item: ViewItem, req_options: Optional["CSVRequestOp
126126
csv = server_response.iter_content(1024)
127127
return csv
128128

129+
@api(version="3.8")
130+
def populate_excel(self, view_item: ViewItem, req_options: Optional["CSVRequestOptions"] = None) -> None:
131+
if not view_item.id:
132+
error = "View item missing ID."
133+
raise MissingRequiredFieldError(error)
134+
135+
def excel_fetcher():
136+
return self._get_view_excel(view_item, req_options)
137+
138+
view_item._set_excel(excel_fetcher)
139+
logger.info("Populated excel for view (ID: {0})".format(view_item.id))
140+
141+
def _get_view_excel(self, view_item: ViewItem, req_options: Optional["CSVRequestOptions"]) -> Iterable[bytes]:
142+
url = "{0}/{1}/crosstab/excel".format(self.baseurl, view_item.id)
143+
144+
with closing(self.get_request(url, request_object=req_options, parameters={"stream": True})) as server_response:
145+
excel = server_response.iter_content(1024)
146+
return excel
147+
129148
@api(version="3.2")
130149
def populate_permissions(self, item: ViewItem) -> None:
131150
self._permissions.populate(item)

test/assets/populate_excel.xlsx

6.47 KB
Binary file not shown.

test/test_view.py

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
POPULATE_PREVIEW_IMAGE = os.path.join(TEST_ASSET_DIR, "Sample View Image.png")
1717
POPULATE_PDF = os.path.join(TEST_ASSET_DIR, "populate_pdf.pdf")
1818
POPULATE_CSV = os.path.join(TEST_ASSET_DIR, "populate_csv.csv")
19+
POPULATE_EXCEL = os.path.join(TEST_ASSET_DIR, "populate_excel.xlsx")
1920
POPULATE_PERMISSIONS_XML = os.path.join(TEST_ASSET_DIR, "view_populate_permissions.xml")
2021
UPDATE_PERMISSIONS = os.path.join(TEST_ASSET_DIR, "view_update_permissions.xml")
2122
UPDATE_XML = os.path.join(TEST_ASSET_DIR, "workbook_update.xml")
@@ -33,7 +34,7 @@ def setUp(self):
3334
self.baseurl = self.server.views.baseurl
3435
self.siteurl = self.server.views.siteurl
3536

36-
def test_get(self):
37+
def test_get(self) -> None:
3738
with open(GET_XML, "rb") as f:
3839
response_xml = f.read().decode("utf-8")
3940
with requests_mock.mock() as m:
@@ -62,7 +63,7 @@ def test_get(self):
6263
self.assertEqual("2002-06-05T08:00:59Z", format_datetime(all_views[1].updated_at))
6364
self.assertEqual("story", all_views[1].sheet_type)
6465

65-
def test_get_by_id(self):
66+
def test_get_by_id(self) -> None:
6667
with open(GET_XML_ID, "rb") as f:
6768
response_xml = f.read().decode("utf-8")
6869
with requests_mock.mock() as m:
@@ -83,7 +84,7 @@ def test_get_by_id(self):
8384
def test_get_by_id_missing_id(self) -> None:
8485
self.assertRaises(TSC.MissingRequiredFieldError, self.server.views.get_by_id, None)
8586

86-
def test_get_with_usage(self):
87+
def test_get_with_usage(self) -> None:
8788
with open(GET_XML_USAGE, "rb") as f:
8889
response_xml = f.read().decode("utf-8")
8990
with requests_mock.mock() as m:
@@ -102,7 +103,7 @@ def test_get_with_usage(self):
102103
self.assertEqual("2002-06-05T08:00:59Z", format_datetime(all_views[1].updated_at))
103104
self.assertEqual("story", all_views[1].sheet_type)
104105

105-
def test_get_with_usage_and_filter(self):
106+
def test_get_with_usage_and_filter(self) -> None:
106107
with open(GET_XML_USAGE, "rb") as f:
107108
response_xml = f.read().decode("utf-8")
108109
with requests_mock.mock() as m:
@@ -122,7 +123,7 @@ def test_get_before_signin(self) -> None:
122123
self.server._auth_token = None
123124
self.assertRaises(TSC.NotSignedInError, self.server.views.get)
124125

125-
def test_populate_preview_image(self):
126+
def test_populate_preview_image(self) -> None:
126127
with open(POPULATE_PREVIEW_IMAGE, "rb") as f:
127128
response = f.read()
128129
with requests_mock.mock() as m:
@@ -146,7 +147,7 @@ def test_populate_preview_image_missing_id(self) -> None:
146147
single_view._workbook_id = "3cc6cd06-89ce-4fdc-b935-5294135d6d42"
147148
self.assertRaises(TSC.MissingRequiredFieldError, self.server.views.populate_preview_image, single_view)
148149

149-
def test_populate_image(self):
150+
def test_populate_image(self) -> None:
150151
with open(POPULATE_PREVIEW_IMAGE, "rb") as f:
151152
response = f.read()
152153
with requests_mock.mock() as m:
@@ -156,7 +157,7 @@ def test_populate_image(self):
156157
self.server.views.populate_image(single_view)
157158
self.assertEqual(response, single_view.image)
158159

159-
def test_populate_image_with_options(self):
160+
def test_populate_image_with_options(self) -> None:
160161
with open(POPULATE_PREVIEW_IMAGE, "rb") as f:
161162
response = f.read()
162163
with requests_mock.mock() as m:
@@ -169,7 +170,7 @@ def test_populate_image_with_options(self):
169170
self.server.views.populate_image(single_view, req_option)
170171
self.assertEqual(response, single_view.image)
171172

172-
def test_populate_pdf(self):
173+
def test_populate_pdf(self) -> None:
173174
with open(POPULATE_PDF, "rb") as f:
174175
response = f.read()
175176
with requests_mock.mock() as m:
@@ -187,7 +188,7 @@ def test_populate_pdf(self):
187188
self.server.views.populate_pdf(single_view, req_option)
188189
self.assertEqual(response, single_view.pdf)
189190

190-
def test_populate_csv(self):
191+
def test_populate_csv(self) -> None:
191192
with open(POPULATE_CSV, "rb") as f:
192193
response = f.read()
193194
with requests_mock.mock() as m:
@@ -200,7 +201,7 @@ def test_populate_csv(self):
200201
csv_file = b"".join(single_view.csv)
201202
self.assertEqual(response, csv_file)
202203

203-
def test_populate_csv_default_maxage(self):
204+
def test_populate_csv_default_maxage(self) -> None:
204205
with open(POPULATE_CSV, "rb") as f:
205206
response = f.read()
206207
with requests_mock.mock() as m:
@@ -217,7 +218,7 @@ def test_populate_image_missing_id(self) -> None:
217218
single_view._id = None
218219
self.assertRaises(TSC.MissingRequiredFieldError, self.server.views.populate_image, single_view)
219220

220-
def test_populate_permissions(self):
221+
def test_populate_permissions(self) -> None:
221222
with open(POPULATE_PERMISSIONS_XML, "rb") as f:
222223
response_xml = f.read().decode("utf-8")
223224
with requests_mock.mock() as m:
@@ -241,7 +242,7 @@ def test_populate_permissions(self):
241242
},
242243
)
243244

244-
def test_add_permissions(self):
245+
def test_add_permissions(self) -> None:
245246
with open(UPDATE_PERMISSIONS, "rb") as f:
246247
response_xml = f.read().decode("utf-8")
247248

@@ -265,7 +266,7 @@ def test_add_permissions(self):
265266
self.assertEqual(permissions[1].grantee.id, "7c37ee24-c4b1-42b6-a154-eaeab7ee330a")
266267
self.assertDictEqual(permissions[1].capabilities, {TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow})
267268

268-
def test_update_tags(self):
269+
def test_update_tags(self) -> None:
269270
with open(ADD_TAGS_XML, "rb") as f:
270271
add_tags_xml = f.read().decode("utf-8")
271272
with open(UPDATE_XML, "rb") as f:
@@ -283,3 +284,18 @@ def test_update_tags(self):
283284

284285
self.assertEqual(single_view.tags, updated_view.tags)
285286
self.assertEqual(single_view._initial_tags, updated_view._initial_tags)
287+
288+
def test_populate_excel(self) -> None:
289+
self.server.version = "3.8"
290+
self.baseurl = self.server.views.baseurl
291+
with open(POPULATE_EXCEL, "rb") as f:
292+
response = f.read()
293+
with requests_mock.mock() as m:
294+
m.get(self.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/crosstab/excel?maxAge=1", content=response)
295+
single_view = TSC.ViewItem()
296+
single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
297+
request_option = TSC.CSVRequestOptions(maxage=1)
298+
self.server.views.populate_excel(single_view, request_option)
299+
300+
excel_file = b"".join(single_view.excel)
301+
self.assertEqual(response, excel_file)

0 commit comments

Comments
 (0)
0