From 6f68eb0ff3990cbe41853822b0639d730bcf872f Mon Sep 17 00:00:00 2001 From: Tyler Doyle Date: Tue, 29 Oct 2019 14:32:22 -0700 Subject: [PATCH 1/2] fix filename bug --- tableauserverclient/filesys_helpers.py | 16 +++++++++ .../server/endpoint/datasources_endpoint.py | 14 ++++---- .../server/endpoint/flows_endpoint.py | 14 ++++---- .../server/endpoint/workbooks_endpoint.py | 14 ++++---- test/test_regression_tests.py | 36 +++++++++++++++++++ 5 files changed, 70 insertions(+), 24 deletions(-) diff --git a/tableauserverclient/filesys_helpers.py b/tableauserverclient/filesys_helpers.py index 0cf304b32..9d0b443bf 100644 --- a/tableauserverclient/filesys_helpers.py +++ b/tableauserverclient/filesys_helpers.py @@ -1,6 +1,22 @@ +import os ALLOWED_SPECIAL = (' ', '.', '_', '-') def to_filename(string_to_sanitize): sanitized = (c for c in string_to_sanitize if c.isalnum() or c in ALLOWED_SPECIAL) return "".join(sanitized) + + +def make_download_path(filepath, filename): + download_path = None + + if filepath is None: + download_path = filename + + elif os.path.isdir(filepath): + download_path = os.path.join(filepath, filename) + + else: + download_path = filepath + os.path.splitext(filename)[1] + + return download_path diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index c46a7dc74..eef88d09e 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -6,7 +6,7 @@ from .fileuploads_endpoint import Fileuploads from .resource_tagger import _ResourceTagger from .. import RequestFactory, DatasourceItem, PaginationItem, ConnectionItem -from ...filesys_helpers import to_filename +from ...filesys_helpers import to_filename, make_download_path from ...models.tag_item import TagItem from ...models.job_item import JobItem import os @@ -104,17 +104,15 @@ def download(self, datasource_id, filepath=None, include_extract=True, no_extrac with closing(self.get_request(url, parameters={'stream': True})) as server_response: _, params = cgi.parse_header(server_response.headers['Content-Disposition']) filename = to_filename(os.path.basename(params['filename'])) - if filepath is None: - filepath = filename - elif os.path.isdir(filepath): - filepath = os.path.join(filepath, filename) - with open(filepath, 'wb') as f: + download_path = make_download_path(filepath, filename) + + with open(download_path, 'wb') as f: for chunk in server_response.iter_content(1024): # 1KB f.write(chunk) - logger.info('Downloaded datasource to {0} (ID: {1})'.format(filepath, datasource_id)) - return os.path.abspath(filepath) + logger.info('Downloaded datasource to {0} (ID: {1})'.format(download_path, datasource_id)) + return os.path.abspath(download_path) # Update datasource @api(version="2.0") diff --git a/tableauserverclient/server/endpoint/flows_endpoint.py b/tableauserverclient/server/endpoint/flows_endpoint.py index b2c616959..7bad807e4 100644 --- a/tableauserverclient/server/endpoint/flows_endpoint.py +++ b/tableauserverclient/server/endpoint/flows_endpoint.py @@ -6,7 +6,7 @@ from .fileuploads_endpoint import Fileuploads from .resource_tagger import _ResourceTagger from .. import RequestFactory, FlowItem, PaginationItem, ConnectionItem -from ...filesys_helpers import to_filename +from ...filesys_helpers import to_filename, make_download_path from ...models.tag_item import TagItem from ...models.job_item import JobItem import os @@ -94,17 +94,15 @@ def download(self, flow_id, filepath=None): with closing(self.get_request(url, parameters={'stream': True})) as server_response: _, params = cgi.parse_header(server_response.headers['Content-Disposition']) filename = to_filename(os.path.basename(params['filename'])) - if filepath is None: - filepath = filename - elif os.path.isdir(filepath): - filepath = os.path.join(filepath, filename) - with open(filepath, 'wb') as f: + download_path = make_download_path(filepath, filename) + + with open(download_path, 'wb') as f: for chunk in server_response.iter_content(1024): # 1KB f.write(chunk) - logger.info('Downloaded flow to {0} (ID: {1})'.format(filepath, flow_id)) - return os.path.abspath(filepath) + logger.info('Downloaded flow to {0} (ID: {1})'.format(download_path, flow_id)) + return os.path.abspath(download_path) # Update flow @api(version="3.3") diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 445b0ccde..3d81e3999 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -8,7 +8,7 @@ from .. import RequestFactory, WorkbookItem, ConnectionItem, ViewItem, PaginationItem from ...models.tag_item import TagItem from ...models.job_item import JobItem -from ...filesys_helpers import to_filename +from ...filesys_helpers import to_filename, make_download_path import os import logging @@ -129,16 +129,14 @@ def download(self, workbook_id, filepath=None, include_extract=True, no_extract= with closing(self.get_request(url, parameters={"stream": True})) as server_response: _, params = cgi.parse_header(server_response.headers['Content-Disposition']) filename = to_filename(os.path.basename(params['filename'])) - if filepath is None: - filepath = filename - elif os.path.isdir(filepath): - filepath = os.path.join(filepath, filename) - with open(filepath, 'wb') as f: + download_path = make_download_path(filepath, filename) + + with open(download_path, 'wb') as f: for chunk in server_response.iter_content(1024): # 1KB f.write(chunk) - logger.info('Downloaded workbook to {0} (ID: {1})'.format(filepath, workbook_id)) - return os.path.abspath(filepath) + logger.info('Downloaded workbook to {0} (ID: {1})'.format(download_path, workbook_id)) + return os.path.abspath(download_path) # Get all views of workbook @api(version="2.0") diff --git a/test/test_regression_tests.py b/test/test_regression_tests.py index 8958c3cf8..defe13451 100644 --- a/test/test_regression_tests.py +++ b/test/test_regression_tests.py @@ -1,6 +1,9 @@ import unittest +from unittest import mock + import tableauserverclient.server.request_factory as factory from tableauserverclient.server.endpoint import Endpoint +from tableauserverclient.filesys_helpers import to_filename, make_download_path class BugFix257(unittest.TestCase): @@ -21,3 +24,36 @@ class FakeResponse(object): server_response = FakeResponse() self.assertEqual(Endpoint._safe_to_log(server_response), '[Truncated File Contents]') + + +class FileSysHelpers(unittest.TestCase): + def test_to_filename(self): + invalid = [ + "23brhafbjrjhkbbea.txt", + 'a_b_C.txt', + 'windows space.txt', + 'abc#def.txt', + 't@bL3A()', + ] + + valid = [ + "23brhafbjrjhkbbea.txt", + 'a_b_C.txt', + 'windows space.txt', + 'abcdef.txt', + 'tbL3A', + ] + + self.assertTrue(all([(to_filename(i) == v) for i, v in zip(invalid, valid)])) + + def test_make_download_path(self): + no_file_path = (None, 'file.ext') + has_file_path_folder = ('/root/folder/', 'file.ext') + has_file_path_file = ('out', 'file.ext') + + self.assertEquals('file.ext', make_download_path(*no_file_path)) + self.assertEquals('out.ext', make_download_path(*has_file_path_file)) + + with mock.patch('os.path.isdir') as mocked_isdir: + mocked_isdir.return_value = True + self.assertEquals('/root/folder/file.ext', make_download_path(*has_file_path_folder)) From bac9917a8d82b01035dfb92a52d700ee3c8aca60 Mon Sep 17 00:00:00 2001 From: Tyler Doyle Date: Tue, 29 Oct 2019 14:39:19 -0700 Subject: [PATCH 2/2] Python 2.7 is still haunting me --- setup.py | 3 ++- test/test_regression_tests.py | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index a7b29aa90..cea7260a0 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ ], tests_require=[ 'requests-mock>=1.0,<2.0', - 'pytest' + 'pytest', + 'mock' ] ) diff --git a/test/test_regression_tests.py b/test/test_regression_tests.py index defe13451..932e993de 100644 --- a/test/test_regression_tests.py +++ b/test/test_regression_tests.py @@ -1,5 +1,9 @@ import unittest -from unittest import mock + +try: + from unittest import mock +except ImportError: + import mock import tableauserverclient.server.request_factory as factory from tableauserverclient.server.endpoint import Endpoint