Skip to content

Commit

Permalink
Merge pull request #759 from tableau/0.14.0-patch
Browse files Browse the repository at this point in the history
v0.14.1 patch release
* Fixed filter query issue for server version below 2020.1 (#745)
* Fixed large workbook/datasource publish issue (#757)
  • Loading branch information
Chris Shin authored Dec 10, 2020
2 parents bcb881c + 1fc349c commit a5b9252
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 73 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.14.1 (9 Dec 2020)
* Fixed filter query issue for server version below 2020.1 (#745)
* Fixed large workbook/datasource publish issue (#757)

## 0.14.0 (6 Nov 2020)
* Added django-style filtering and sorting (#615)
* Added encoding tag-name before deleting (#687)
Expand Down
26 changes: 17 additions & 9 deletions tableauserverclient/server/endpoint/endpoint.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .exceptions import ServerResponseError, InternalServerError, NonXMLResponseError
from .exceptions import ServerResponseError, InternalServerError, NonXMLResponseError, EndpointUnavailableError
from functools import wraps
from xml.etree.ElementTree import ParseError
from ..query import QuerySet
Expand Down Expand Up @@ -39,11 +39,9 @@ def _safe_to_log(server_response):
else:
return server_response.content

def _make_request(self, method, url, content=None, request_object=None,
auth_token=None, content_type=None, parameters=None):
def _make_request(self, method, url, content=None, auth_token=None,
content_type=None, parameters=None):
parameters = parameters or {}
if request_object is not None:
parameters["params"] = request_object.get_query_params()
parameters.update(self.parent_srv.http_options)
parameters['headers'] = Endpoint._make_common_headers(auth_token, content_type)

Expand Down Expand Up @@ -78,12 +76,22 @@ def _check_status(self, server_response):
# anything else re-raise here
raise

def get_unauthenticated_request(self, url, request_object=None):
return self._make_request(self.parent_srv.session.get, url, request_object=request_object)
def get_unauthenticated_request(self, url):
return self._make_request(self.parent_srv.session.get, url)

def get_request(self, url, request_object=None, parameters=None):
return self._make_request(self.parent_srv.session.get, url, auth_token=self.parent_srv.auth_token,
request_object=request_object, parameters=parameters)
if request_object is not None:
try:
# Query param delimiters don't need to be encoded for versions before 3.7 (2020.1)
self.parent_srv.assert_at_least_version("3.7")
parameters = parameters or {}
parameters["params"] = request_object.get_query_params()
except EndpointUnavailableError:
url = request_object.apply_query_params(url)

return self._make_request(self.parent_srv.session.get, url,
auth_token=self.parent_srv.auth_token,
parameters=parameters)

def delete_request(self, url):
# We don't return anything for a delete
Expand Down
16 changes: 10 additions & 6 deletions tableauserverclient/server/endpoint/fileuploads_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,18 @@ def append(self, xml_request, content_type):
return FileuploadItem.from_response(server_response.content, self.parent_srv.namespace)

def read_chunks(self, file):
file_opened = False
try:
file_content = open(file, 'rb')
file_opened = True
except TypeError:
file_content = file

while True:
chunked_content = file.read(CHUNK_SIZE)
chunked_content = file_content.read(CHUNK_SIZE)
if not chunked_content:
if file_opened:
file_content.close()
break
yield chunked_content

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

try:
with open(file, 'rb') as f:
chunks = file_uploader.read_chunks(f)
except TypeError:
chunks = file_uploader.read_chunks(file)
chunks = file_uploader.read_chunks(file)
for chunk in chunks:
xml_request, content_type = RequestFactory.Fileupload.chunk_req(chunk)
fileupload_item = file_uploader.append(xml_request, content_type)
Expand Down
4 changes: 1 addition & 3 deletions tableauserverclient/server/request_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@


class RequestOptionsBase(object):
# This method is used if server api version is below 3.7 (2020.1)
def apply_query_params(self, url):
import warnings
warnings.simplefilter('always', DeprecationWarning)
warnings.warn('apply_query_params is deprecated, please use get_query_params instead.', DeprecationWarning)
try:
params = self.get_query_params()
params_list = ["{}={}".format(k, v) for (k, v) in params.items()]
Expand Down
3 changes: 3 additions & 0 deletions test/assets/fileupload_append.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?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">
<fileUpload uploadSessionId="3877:63f8579222de403ea574673e22dafdca-1:0" fileSize="5"/>
</tsResponse>
3 changes: 3 additions & 0 deletions test/assets/fileupload_initialize.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<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">
<fileUpload uploadSessionId="7720:170fe6b1c1c7422dadff20f944d58a52-1:0" fileSize="0"/>
</tsResponse>
70 changes: 70 additions & 0 deletions test/test_fileuploads.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import os
import requests_mock
import unittest

from ._utils import asset
from tableauserverclient.server import Server
from tableauserverclient.server.endpoint.fileuploads_endpoint import Fileuploads

TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets')
FILEUPLOAD_INITIALIZE = os.path.join(TEST_ASSET_DIR, 'fileupload_initialize.xml')
FILEUPLOAD_APPEND = os.path.join(TEST_ASSET_DIR, 'fileupload_append.xml')


class FileuploadsTests(unittest.TestCase):
def setUp(self):
self.server = Server('http://test')

# Fake sign in
self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67'
self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM'

self.baseurl = '{}/sites/{}/fileUploads'.format(self.server.baseurl, self.server.site_id)

def test_read_chunks_file_path(self):
fileuploads = Fileuploads(self.server)

file_path = asset('SampleWB.twbx')
chunks = fileuploads.read_chunks(file_path)
for chunk in chunks:
self.assertIsNotNone(chunk)

def test_read_chunks_file_object(self):
fileuploads = Fileuploads(self.server)

with open(asset('SampleWB.twbx'), 'rb') as f:
chunks = fileuploads.read_chunks(f)
for chunk in chunks:
self.assertIsNotNone(chunk)

def test_upload_chunks_file_path(self):
fileuploads = Fileuploads(self.server)
file_path = asset('SampleWB.twbx')
upload_id = '7720:170fe6b1c1c7422dadff20f944d58a52-1:0'

with open(FILEUPLOAD_INITIALIZE, 'rb') as f:
initialize_response_xml = f.read().decode('utf-8')
with open(FILEUPLOAD_APPEND, 'rb') as f:
append_response_xml = f.read().decode('utf-8')
with requests_mock.mock() as m:
m.post(self.baseurl, text=initialize_response_xml)
m.put(self.baseurl + '/' + upload_id, text=append_response_xml)
actual = fileuploads.upload_chunks(self.server, file_path)

self.assertEqual(upload_id, actual)

def test_upload_chunks_file_object(self):
fileuploads = Fileuploads(self.server)
upload_id = '7720:170fe6b1c1c7422dadff20f944d58a52-1:0'

with open(asset('SampleWB.twbx'), 'rb') as file_content:
with open(FILEUPLOAD_INITIALIZE, 'rb') as f:
initialize_response_xml = f.read().decode('utf-8')
with open(FILEUPLOAD_APPEND, 'rb') as f:
append_response_xml = f.read().decode('utf-8')
with requests_mock.mock() as m:
m.post(self.baseurl, text=initialize_response_xml)
m.put(self.baseurl + '/' + upload_id, text=append_response_xml)
actual = fileuploads.upload_chunks(self.server, file_content)

self.assertEqual(upload_id, actual)
66 changes: 45 additions & 21 deletions test/test_request_option.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def setUp(self):
self.server = TSC.Server('http://test')

# Fake signin
self.server.version = "3.10"
self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67'
self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM'

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

opts.filter.add(TSC.Filter(TSC.RequestOptions.Field.Tags,
TSC.RequestOptions.Operator.In,
['stocks', 'market']))
opts.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name,
TSC.RequestOptions.Direction.Asc))

resp = self.server.workbooks._make_request(requests.get,
url,
content=None,
request_object=opts,
auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM',
content_type='text/xml')
resp = self.server.workbooks.get_request(url, request_object=opts)
self.assertTrue(re.search('queryparamexists=true', resp.request.query))
self.assertTrue(re.search('filter=tags%3ain%3a%5bstocks%2cmarket%5d', resp.request.query))
self.assertTrue(re.search('sort=name%3aasc', resp.request.query))

# Test req_options for versions below 3.7
def test_filter_sort_legacy(self):
self.server.version = "3.6"
with requests_mock.mock() as m:
m.get(requests_mock.ANY)
url = self.baseurl + "/views?queryParamExists=true"
opts = TSC.RequestOptions()

opts.filter.add(TSC.Filter(TSC.RequestOptions.Field.Tags,
TSC.RequestOptions.Operator.In,
['stocks', 'market']))
opts.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name,
TSC.RequestOptions.Direction.Asc))

resp = self.server.workbooks.get_request(url, request_object=opts)
self.assertTrue(re.search('queryparamexists=true', resp.request.query))
self.assertTrue(re.search('filter=tags:in:%5bstocks,market%5d', resp.request.query))
self.assertTrue(re.search('sort=name:asc', resp.request.query))

def test_vf(self):
with requests_mock.mock() as m:
m.get(requests_mock.ANY)
url = "http://test/api/2.3/sites/123/views/456/data"
url = self.baseurl + "/views/456/data"
opts = TSC.PDFRequestOptions()
opts.vf("name1#", "value1")
opts.vf("name2$", "value2")
opts.page_type = TSC.PDFRequestOptions.PageType.Tabloid

resp = self.server.workbooks._make_request(requests.get,
url,
content=None,
request_object=opts,
auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM',
content_type='text/xml')
resp = self.server.workbooks.get_request(url, request_object=opts)
self.assertTrue(re.search('vf_name1%23=value1', resp.request.query))
self.assertTrue(re.search('vf_name2%24=value2', resp.request.query))
self.assertTrue(re.search('type=tabloid', resp.request.query))

# Test req_options for versions beloe 3.7
def test_vf_legacy(self):
self.server.version = "3.6"
with requests_mock.mock() as m:
m.get(requests_mock.ANY)
url = self.baseurl + "/views/456/data"
opts = TSC.PDFRequestOptions()
opts.vf("name1@", "value1")
opts.vf("name2$", "value2")
opts.page_type = TSC.PDFRequestOptions.PageType.Tabloid

resp = self.server.workbooks.get_request(url, request_object=opts)
self.assertTrue(re.search('vf_name1@=value1', resp.request.query))
self.assertTrue(re.search('vf_name2\\$=value2', resp.request.query))
self.assertTrue(re.search('type=tabloid', resp.request.query))

def test_all_fields(self):
with requests_mock.mock() as m:
m.get(requests_mock.ANY)
url = "http://test/api/2.3/sites/123/views/456/data"
url = self.baseurl + "/views/456/data"
opts = TSC.RequestOptions()
opts._all_fields = True

resp = self.server.users._make_request(requests.get,
url,
content=None,
request_object=opts,
auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM',
content_type='text/xml')
resp = self.server.users.get_request(url, request_object=opts)
self.assertTrue(re.search('fields=_all_', resp.request.query))

def test_multiple_filter_options_shorthand(self):
Expand Down
12 changes: 2 additions & 10 deletions test/test_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,7 @@ def test_make_get_request(self):
m.get(requests_mock.ANY)
url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks"
opts = TSC.RequestOptions(pagesize=13, pagenumber=15)
resp = self.server.workbooks._make_request(requests.get,
url,
content=None,
request_object=opts,
auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM',
content_type='text/xml')
resp = self.server.workbooks.get_request(url, request_object=opts)

self.assertTrue(re.search('pagesize=13', resp.request.query))
self.assertTrue(re.search('pagenumber=15', resp.request.query))
Expand All @@ -37,10 +32,7 @@ def test_make_post_request(self):
with requests_mock.mock() as m:
m.post(requests_mock.ANY)
url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks"
resp = self.server.workbooks._make_request(requests.post,
url,
content=b'1337',
request_object=None,
resp = self.server.workbooks._make_request(requests.post, url, content=b'1337',
auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM',
content_type='multipart/mixed')
self.assertEqual(resp.request.headers['x-tableau-auth'], 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM')
Expand Down
29 changes: 5 additions & 24 deletions test/test_sort.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
class SortTests(unittest.TestCase):
def setUp(self):
self.server = TSC.Server('http://test')
self.server.version = "3.10"
self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67'
self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM'
self.baseurl = self.server.workbooks.baseurl
Expand All @@ -24,12 +25,7 @@ def test_filter_equals(self):
TSC.RequestOptions.Operator.Equals,
'Superstore'))

resp = self.server.workbooks._make_request(requests.get,
url,
content=None,
request_object=opts,
auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM',
content_type='text/xml')
resp = self.server.workbooks.get_request(url, request_object=opts)

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

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

resp = self.server.workbooks._make_request(requests.get,
url,
content=None,
request_object=opts,
auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM',
content_type='text/xml')
resp = self.server.workbooks.get_request(url, request_object=opts)

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

resp = self.server.workbooks._make_request(requests.get,
url,
content=None,
request_object=opts,
auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM',
content_type='text/xml')
resp = self.server.workbooks.get_request(url, request_object=opts)

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

0 comments on commit a5b9252

Please sign in to comment.