Skip to content

Commit a5b9252

Browse files
author
Chris Shin
authored
Merge pull request #759 from tableau/0.14.0-patch
v0.14.1 patch release * Fixed filter query issue for server version below 2020.1 (#745) * Fixed large workbook/datasource publish issue (#757)
2 parents bcb881c + 1fc349c commit a5b9252

10 files changed

+160
-73
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.14.1 (9 Dec 2020)
2+
* Fixed filter query issue for server version below 2020.1 (#745)
3+
* Fixed large workbook/datasource publish issue (#757)
4+
15
## 0.14.0 (6 Nov 2020)
26
* Added django-style filtering and sorting (#615)
37
* Added encoding tag-name before deleting (#687)

tableauserverclient/server/endpoint/endpoint.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .exceptions import ServerResponseError, InternalServerError, NonXMLResponseError
1+
from .exceptions import ServerResponseError, InternalServerError, NonXMLResponseError, EndpointUnavailableError
22
from functools import wraps
33
from xml.etree.ElementTree import ParseError
44
from ..query import QuerySet
@@ -39,11 +39,9 @@ def _safe_to_log(server_response):
3939
else:
4040
return server_response.content
4141

42-
def _make_request(self, method, url, content=None, request_object=None,
43-
auth_token=None, content_type=None, parameters=None):
42+
def _make_request(self, method, url, content=None, auth_token=None,
43+
content_type=None, parameters=None):
4444
parameters = parameters or {}
45-
if request_object is not None:
46-
parameters["params"] = request_object.get_query_params()
4745
parameters.update(self.parent_srv.http_options)
4846
parameters['headers'] = Endpoint._make_common_headers(auth_token, content_type)
4947

@@ -78,12 +76,22 @@ def _check_status(self, server_response):
7876
# anything else re-raise here
7977
raise
8078

81-
def get_unauthenticated_request(self, url, request_object=None):
82-
return self._make_request(self.parent_srv.session.get, url, request_object=request_object)
79+
def get_unauthenticated_request(self, url):
80+
return self._make_request(self.parent_srv.session.get, url)
8381

8482
def get_request(self, url, request_object=None, parameters=None):
85-
return self._make_request(self.parent_srv.session.get, url, auth_token=self.parent_srv.auth_token,
86-
request_object=request_object, parameters=parameters)
83+
if request_object is not None:
84+
try:
85+
# Query param delimiters don't need to be encoded for versions before 3.7 (2020.1)
86+
self.parent_srv.assert_at_least_version("3.7")
87+
parameters = parameters or {}
88+
parameters["params"] = request_object.get_query_params()
89+
except EndpointUnavailableError:
90+
url = request_object.apply_query_params(url)
91+
92+
return self._make_request(self.parent_srv.session.get, url,
93+
auth_token=self.parent_srv.auth_token,
94+
parameters=parameters)
8795

8896
def delete_request(self, url):
8997
# We don't return anything for a delete

tableauserverclient/server/endpoint/fileuploads_endpoint.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,18 @@ def append(self, xml_request, content_type):
4040
return FileuploadItem.from_response(server_response.content, self.parent_srv.namespace)
4141

4242
def read_chunks(self, file):
43+
file_opened = False
44+
try:
45+
file_content = open(file, 'rb')
46+
file_opened = True
47+
except TypeError:
48+
file_content = file
4349

4450
while True:
45-
chunked_content = file.read(CHUNK_SIZE)
51+
chunked_content = file_content.read(CHUNK_SIZE)
4652
if not chunked_content:
53+
if file_opened:
54+
file_content.close()
4755
break
4856
yield chunked_content
4957

@@ -52,11 +60,7 @@ def upload_chunks(cls, parent_srv, file):
5260
file_uploader = cls(parent_srv)
5361
upload_id = file_uploader.initiate()
5462

55-
try:
56-
with open(file, 'rb') as f:
57-
chunks = file_uploader.read_chunks(f)
58-
except TypeError:
59-
chunks = file_uploader.read_chunks(file)
63+
chunks = file_uploader.read_chunks(file)
6064
for chunk in chunks:
6165
xml_request, content_type = RequestFactory.Fileupload.chunk_req(chunk)
6266
fileupload_item = file_uploader.append(xml_request, content_type)

tableauserverclient/server/request_options.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22

33

44
class RequestOptionsBase(object):
5+
# This method is used if server api version is below 3.7 (2020.1)
56
def apply_query_params(self, url):
6-
import warnings
7-
warnings.simplefilter('always', DeprecationWarning)
8-
warnings.warn('apply_query_params is deprecated, please use get_query_params instead.', DeprecationWarning)
97
try:
108
params = self.get_query_params()
119
params_list = ["{}={}".format(k, v) for (k, v) in params.items()]

test/assets/fileupload_append.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?xml version='1.0' encoding='UTF-8'?><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-3.11.xsd">
2+
<fileUpload uploadSessionId="3877:63f8579222de403ea574673e22dafdca-1:0" fileSize="5"/>
3+
</tsResponse>

test/assets/fileupload_initialize.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<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-3.11.xsd">
2+
<fileUpload uploadSessionId="7720:170fe6b1c1c7422dadff20f944d58a52-1:0" fileSize="0"/>
3+
</tsResponse>

test/test_fileuploads.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import os
2+
import requests_mock
3+
import unittest
4+
5+
from ._utils import asset
6+
from tableauserverclient.server import Server
7+
from tableauserverclient.server.endpoint.fileuploads_endpoint import Fileuploads
8+
9+
TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets')
10+
FILEUPLOAD_INITIALIZE = os.path.join(TEST_ASSET_DIR, 'fileupload_initialize.xml')
11+
FILEUPLOAD_APPEND = os.path.join(TEST_ASSET_DIR, 'fileupload_append.xml')
12+
13+
14+
class FileuploadsTests(unittest.TestCase):
15+
def setUp(self):
16+
self.server = Server('http://test')
17+
18+
# Fake sign in
19+
self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67'
20+
self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM'
21+
22+
self.baseurl = '{}/sites/{}/fileUploads'.format(self.server.baseurl, self.server.site_id)
23+
24+
def test_read_chunks_file_path(self):
25+
fileuploads = Fileuploads(self.server)
26+
27+
file_path = asset('SampleWB.twbx')
28+
chunks = fileuploads.read_chunks(file_path)
29+
for chunk in chunks:
30+
self.assertIsNotNone(chunk)
31+
32+
def test_read_chunks_file_object(self):
33+
fileuploads = Fileuploads(self.server)
34+
35+
with open(asset('SampleWB.twbx'), 'rb') as f:
36+
chunks = fileuploads.read_chunks(f)
37+
for chunk in chunks:
38+
self.assertIsNotNone(chunk)
39+
40+
def test_upload_chunks_file_path(self):
41+
fileuploads = Fileuploads(self.server)
42+
file_path = asset('SampleWB.twbx')
43+
upload_id = '7720:170fe6b1c1c7422dadff20f944d58a52-1:0'
44+
45+
with open(FILEUPLOAD_INITIALIZE, 'rb') as f:
46+
initialize_response_xml = f.read().decode('utf-8')
47+
with open(FILEUPLOAD_APPEND, 'rb') as f:
48+
append_response_xml = f.read().decode('utf-8')
49+
with requests_mock.mock() as m:
50+
m.post(self.baseurl, text=initialize_response_xml)
51+
m.put(self.baseurl + '/' + upload_id, text=append_response_xml)
52+
actual = fileuploads.upload_chunks(self.server, file_path)
53+
54+
self.assertEqual(upload_id, actual)
55+
56+
def test_upload_chunks_file_object(self):
57+
fileuploads = Fileuploads(self.server)
58+
upload_id = '7720:170fe6b1c1c7422dadff20f944d58a52-1:0'
59+
60+
with open(asset('SampleWB.twbx'), 'rb') as file_content:
61+
with open(FILEUPLOAD_INITIALIZE, 'rb') as f:
62+
initialize_response_xml = f.read().decode('utf-8')
63+
with open(FILEUPLOAD_APPEND, 'rb') as f:
64+
append_response_xml = f.read().decode('utf-8')
65+
with requests_mock.mock() as m:
66+
m.post(self.baseurl, text=initialize_response_xml)
67+
m.put(self.baseurl + '/' + upload_id, text=append_response_xml)
68+
actual = fileuploads.upload_chunks(self.server, file_content)
69+
70+
self.assertEqual(upload_id, actual)

test/test_request_option.py

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def setUp(self):
2020
self.server = TSC.Server('http://test')
2121

2222
# Fake signin
23+
self.server.version = "3.10"
2324
self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67'
2425
self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM'
2526

@@ -141,54 +142,77 @@ def test_multiple_filter_options(self):
141142
def test_double_query_params(self):
142143
with requests_mock.mock() as m:
143144
m.get(requests_mock.ANY)
144-
url = "http://test/api/2.3/sites/12345/views?queryParamExists=true"
145+
url = self.baseurl + "/views?queryParamExists=true"
145146
opts = TSC.RequestOptions()
146147

147148
opts.filter.add(TSC.Filter(TSC.RequestOptions.Field.Tags,
148149
TSC.RequestOptions.Operator.In,
149150
['stocks', 'market']))
151+
opts.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name,
152+
TSC.RequestOptions.Direction.Asc))
150153

151-
resp = self.server.workbooks._make_request(requests.get,
152-
url,
153-
content=None,
154-
request_object=opts,
155-
auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM',
156-
content_type='text/xml')
154+
resp = self.server.workbooks.get_request(url, request_object=opts)
157155
self.assertTrue(re.search('queryparamexists=true', resp.request.query))
158156
self.assertTrue(re.search('filter=tags%3ain%3a%5bstocks%2cmarket%5d', resp.request.query))
157+
self.assertTrue(re.search('sort=name%3aasc', resp.request.query))
158+
159+
# Test req_options for versions below 3.7
160+
def test_filter_sort_legacy(self):
161+
self.server.version = "3.6"
162+
with requests_mock.mock() as m:
163+
m.get(requests_mock.ANY)
164+
url = self.baseurl + "/views?queryParamExists=true"
165+
opts = TSC.RequestOptions()
166+
167+
opts.filter.add(TSC.Filter(TSC.RequestOptions.Field.Tags,
168+
TSC.RequestOptions.Operator.In,
169+
['stocks', 'market']))
170+
opts.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name,
171+
TSC.RequestOptions.Direction.Asc))
172+
173+
resp = self.server.workbooks.get_request(url, request_object=opts)
174+
self.assertTrue(re.search('queryparamexists=true', resp.request.query))
175+
self.assertTrue(re.search('filter=tags:in:%5bstocks,market%5d', resp.request.query))
176+
self.assertTrue(re.search('sort=name:asc', resp.request.query))
159177

160178
def test_vf(self):
161179
with requests_mock.mock() as m:
162180
m.get(requests_mock.ANY)
163-
url = "http://test/api/2.3/sites/123/views/456/data"
181+
url = self.baseurl + "/views/456/data"
164182
opts = TSC.PDFRequestOptions()
165183
opts.vf("name1#", "value1")
166184
opts.vf("name2$", "value2")
167185
opts.page_type = TSC.PDFRequestOptions.PageType.Tabloid
168186

169-
resp = self.server.workbooks._make_request(requests.get,
170-
url,
171-
content=None,
172-
request_object=opts,
173-
auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM',
174-
content_type='text/xml')
187+
resp = self.server.workbooks.get_request(url, request_object=opts)
175188
self.assertTrue(re.search('vf_name1%23=value1', resp.request.query))
176189
self.assertTrue(re.search('vf_name2%24=value2', resp.request.query))
177190
self.assertTrue(re.search('type=tabloid', resp.request.query))
178191

192+
# Test req_options for versions beloe 3.7
193+
def test_vf_legacy(self):
194+
self.server.version = "3.6"
195+
with requests_mock.mock() as m:
196+
m.get(requests_mock.ANY)
197+
url = self.baseurl + "/views/456/data"
198+
opts = TSC.PDFRequestOptions()
199+
opts.vf("name1@", "value1")
200+
opts.vf("name2$", "value2")
201+
opts.page_type = TSC.PDFRequestOptions.PageType.Tabloid
202+
203+
resp = self.server.workbooks.get_request(url, request_object=opts)
204+
self.assertTrue(re.search('vf_name1@=value1', resp.request.query))
205+
self.assertTrue(re.search('vf_name2\\$=value2', resp.request.query))
206+
self.assertTrue(re.search('type=tabloid', resp.request.query))
207+
179208
def test_all_fields(self):
180209
with requests_mock.mock() as m:
181210
m.get(requests_mock.ANY)
182-
url = "http://test/api/2.3/sites/123/views/456/data"
211+
url = self.baseurl + "/views/456/data"
183212
opts = TSC.RequestOptions()
184213
opts._all_fields = True
185214

186-
resp = self.server.users._make_request(requests.get,
187-
url,
188-
content=None,
189-
request_object=opts,
190-
auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM',
191-
content_type='text/xml')
215+
resp = self.server.users.get_request(url, request_object=opts)
192216
self.assertTrue(re.search('fields=_all_', resp.request.query))
193217

194218
def test_multiple_filter_options_shorthand(self):

test/test_requests.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,7 @@ def test_make_get_request(self):
2323
m.get(requests_mock.ANY)
2424
url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks"
2525
opts = TSC.RequestOptions(pagesize=13, pagenumber=15)
26-
resp = self.server.workbooks._make_request(requests.get,
27-
url,
28-
content=None,
29-
request_object=opts,
30-
auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM',
31-
content_type='text/xml')
26+
resp = self.server.workbooks.get_request(url, request_object=opts)
3227

3328
self.assertTrue(re.search('pagesize=13', resp.request.query))
3429
self.assertTrue(re.search('pagenumber=15', resp.request.query))
@@ -37,10 +32,7 @@ def test_make_post_request(self):
3732
with requests_mock.mock() as m:
3833
m.post(requests_mock.ANY)
3934
url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks"
40-
resp = self.server.workbooks._make_request(requests.post,
41-
url,
42-
content=b'1337',
43-
request_object=None,
35+
resp = self.server.workbooks._make_request(requests.post, url, content=b'1337',
4436
auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM',
4537
content_type='multipart/mixed')
4638
self.assertEqual(resp.request.headers['x-tableau-auth'], 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM')

test/test_sort.py

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
class SortTests(unittest.TestCase):
99
def setUp(self):
1010
self.server = TSC.Server('http://test')
11+
self.server.version = "3.10"
1112
self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67'
1213
self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM'
1314
self.baseurl = self.server.workbooks.baseurl
@@ -24,12 +25,7 @@ def test_filter_equals(self):
2425
TSC.RequestOptions.Operator.Equals,
2526
'Superstore'))
2627

27-
resp = self.server.workbooks._make_request(requests.get,
28-
url,
29-
content=None,
30-
request_object=opts,
31-
auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM',
32-
content_type='text/xml')
28+
resp = self.server.workbooks.get_request(url, request_object=opts)
3329

3430
self.assertTrue(re.search('pagenumber=13', resp.request.query))
3531
self.assertTrue(re.search('pagesize=13', resp.request.query))
@@ -53,12 +49,7 @@ def test_filter_in(self):
5349
TSC.RequestOptions.Operator.In,
5450
['stocks', 'market']))
5551

56-
resp = self.server.workbooks._make_request(requests.get,
57-
url,
58-
content=None,
59-
request_object=opts,
60-
auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM',
61-
content_type='text/xml')
52+
resp = self.server.workbooks.get_request(url, request_object=opts)
6253
self.assertTrue(re.search('pagenumber=13', resp.request.query))
6354
self.assertTrue(re.search('pagesize=13', resp.request.query))
6455
self.assertTrue(re.search('filter=tags%3ain%3a%5bstocks%2cmarket%5d', resp.request.query))
@@ -71,12 +62,7 @@ def test_sort_asc(self):
7162
opts.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name,
7263
TSC.RequestOptions.Direction.Asc))
7364

74-
resp = self.server.workbooks._make_request(requests.get,
75-
url,
76-
content=None,
77-
request_object=opts,
78-
auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM',
79-
content_type='text/xml')
65+
resp = self.server.workbooks.get_request(url, request_object=opts)
8066

8167
self.assertTrue(re.search('pagenumber=13', resp.request.query))
8268
self.assertTrue(re.search('pagesize=13', resp.request.query))
@@ -96,12 +82,7 @@ def test_filter_combo(self):
9682
TSC.RequestOptions.Operator.Equals,
9783
'Publisher'))
9884

99-
resp = self.server.workbooks._make_request(requests.get,
100-
url,
101-
content=None,
102-
request_object=opts,
103-
auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM',
104-
content_type='text/xml')
85+
resp = self.server.workbooks.get_request(url, request_object=opts)
10586

10687
expected = 'pagenumber=13&pagesize=13&filter=lastlogin%3agte%3a' \
10788
'2017-01-15t00%3a00%3a00%3a00z%2csiterole%3aeq%3apublisher'

0 commit comments

Comments
 (0)