10000 Download with extract_only and parameter checking (#143) · gmiretti/server-client-python@c967cef · GitHub
[go: up one dir, main page]

Skip to content

Commit c967cef

Browse files
authored
Download with extract_only and parameter checking (tableau#143)
* Add a new decorator that checks parameters against the version. Used with the @api decorator. * Add extract_only flags to workbooks and data sources, protected behind v2.5 flag
1 parent 8d54ac8 commit c967cef

File tree

5 files changed

+74
-4
lines changed

5 files changed

+74
-4
lines changed

tableauserverclient/server/endpoint/datasources_endpoint.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .endpoint import Endpoint, api
1+
from .endpoint import Endpoint, api, parameter_added_in
22
from .exceptions import MissingRequiredFieldError
33
from .fileuploads_endpoint import Fileuploads
44
from .. import RequestFactory, DatasourceItem, PaginationItem, ConnectionItem
@@ -65,11 +65,16 @@ def delete(self, datasource_id):
6565

6666
# Download 1 datasource by id
6767
@api(version="2.0")
68-
def download(self, datasource_id, filepath=None):
68+
@parameter_added_in(version="2.5", parameters=['extract_only'])
69+
def download(self, datasource_id, filepath=None, extract_only=False):
6970
if not datasource_id:
7071
error = "Datasource ID undefined."
7172
raise ValueError(error)
7273
url = "{0}/{1}/content".format(self.baseurl, datasource_id)
74+
75+
if extract_only:
76+
url += "?includeExtract=False"
77+
7378
with closing(self.get_request(url, parameters={'stream': True})) as server_response:
7479
_, params = cgi.parse_header(server_response.headers['Content-Disposition'])
7580
filename = os.path.basename(params['filename'])

tableauserverclient/server/endpoint/endpoint.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,37 @@ def wrapper(self, *args, **kwargs):
107107
return func(self, *args, **kwargs)
108108
return wrapper
109109
return _decorator
110+
111+
112+
def parameter_added_in(version, parameters):
113+
'''Annotate minimum versions for new parameters or request options on an endpoint.
114+
115+
The api decorator documents when an endpoint was added, this decorator annotates
116+
keyword arguments on endpoints that may control functionality added after an endpoint was introduced.
117+
118+
The REST API will ignore invalid parameters in most cases, so this raises a warning instead of throwing
119+
an exception
120+
121+
Example:
122+
>>> @api(version="2.0")
123+
>>> @parameter_added_in(version="2.5", parameters=['extract_only'])
124+
>>> def download(self, workbook_id, filepath=None, extract_only=False):
125+
>>> ...
126+
'''
127+
def _decorator(func):
128+
@wraps(func)
129+
def wrapper(self, *args, **kwargs):
130+
params = set(parameters)
131+
invalid_params = params & set(kwargs)
132+
133+
if invalid_params:
134+
import warnings
135+
server_version = Version(self.parent_srv.version or "0.0")
136+
minimum_supported = Version(version)
137+
if server_version < minimum_supported:
138+
error = "The parameter(s) {!r} are not available in {} and will be ignored. Added in {}".format(
139+
invalid_params, server_version, minimum_supported)
140+
warnings.warn(error)
141+
return func(self, *args, **kwargs)
142+
return wrapper
143+
return _decorator

tableauserverclient/server/endpoint/workbooks_endpoint.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .endpoint import Endpoint, api
1+
from .endpoint import Endpoint, api, parameter_added_in
22
from .exceptions import MissingRequiredFieldError
33
from .fileuploads_endpoint import Fileuploads
44
from .. import RequestFactory, WorkbookItem, ConnectionItem, ViewItem, PaginationItem
@@ -93,12 +93,16 @@ def update(self, workbook_item):
9393

9494
# Download workbook contents with option of passing in filepath
9595
@api(version="2.0")
96-
def download(self, workbook_id, filepath=None):
96+
@parameter_added_in(version="2.5", parameters=['extract_only'])
97+
def download(self, workbook_id, filepath=None, extract_only=False):
9798
if not workbook_id:
9899
error = "Workbook ID undefined."
99100
raise ValueError(error)
100101
url = "{0}/{1}/content".format(self.baseurl, workbook_id)
101102

103+
if extract_only:
104+
url += "?includeExtract=False"
105+
102106
with closing(self.get_request(url, parameters={"stream": True})) as server_response:
103107
_, params = cgi.parse_header(server_response.headers['Content-Disposition'])
104108
filename = os.path.basename(params['filename'])

test/test_datasource.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,19 @@ def test_download(self):
145145
self.assertTrue(os.path.exists(file_path))
146146
os.remove(file_path)
147147

148+
def test_download_extract_only(self):
149+
# Pretend we're 2.5 for 'extract_only'
150+
self.server.version = "2.5"
151+
self.baseurl = self.server.datasources.baseurl
152+
153+
with requests_mock.mock() as m:
154+
m.get(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/content?includeExtract=False',
155+
headers={'Content-Disposition': 'name="tableau_datasource"; filename="Sample datasource.tds"'},
156+
complete_qs=True)
157+
file_path = self.server.datasources.download('9dbd2263-16b5-46e1-9c43-a76bb8ab65fb', extract_only=True)
158+
self.assertTrue(os.path.exists(file_path))
159+
os.remove(file_path)
160+
148161
def test_update_missing_id(self):
149162
single_datasource = TSC.DatasourceItem('test', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760')
150163
self.assertRaises(TSC.MissingRequiredFieldError, self.server.datasources.update, single_datasource)

test/test_workbook.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,20 @@ def test_download(self):
170170
self.assertTrue(os.path.exists(file_path))
171171
os.remove(file_path)
172172

173+
def test_download_extract_only(self):
174+
# Pretend we're 2.5 for 'extract_only'
175+
self.server.version = "2.5"
176+
self.baseurl = self.server.workbooks.baseurl
177+
178+
with requests_mock.mock() as m:
179+
m.get(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2/content?includeExtract=False',
180+
headers={'Content-Disposition': 'name="tableau_workbook"; filename="RESTAPISample.twbx"'},
181+
complete_qs=True)
182+
# Technically this shouldn't download a twbx, but we are interested in the qs, not the file
183+
file_path = self.server.workbooks.download('1f951daf-4061-451a-9df1-69a8062664f2', extract_only=True)
184+
self.assertTrue(os.path.exists(file_path))
185+
os.remove(file_path)
186+
173187
def test_download_missing_id(self):
174188
self.assertRaises(ValueError, self.server.workbooks.download, '')
175189

0 commit comments

Comments
 (0)
0