From 6663c876a5795b9aa56cff3935a09e9bace39cbf Mon Sep 17 00:00:00 2001 From: T8y8 Date: Tue, 24 May 2016 10:58:58 -0700 Subject: [PATCH 1/4] Here's an intermediate stab at twbx support. Does not include TDSX yet. I'm not sure where a lot of the zip stuff should go. --- tableaudocumentapi/workbook.py | 115 +++++++++++++++++++++++++-------- test.py | 52 +++++++++++++-- 2 files changed, 137 insertions(+), 30 deletions(-) diff --git a/tableaudocumentapi/workbook.py b/tableaudocumentapi/workbook.py index 67dbc32..80449f1 100644 --- a/tableaudocumentapi/workbook.py +++ b/tableaudocumentapi/workbook.py @@ -3,10 +3,57 @@ # Workbook - A class for writing Tableau workbook files # ############################################################################### +import contextlib import os +import shutil +import tempfile +import zipfile + import xml.etree.ElementTree as ET + from tableaudocumentapi import Datasource +########################################################################### +# +# Utility Functions +# +########################################################################### + + +@contextlib.contextmanager +def temporary_directory(*args, **kwargs): + d = tempfile.mkdtemp(*args, **kwargs) + try: + yield d + finally: + shutil.rmtree(d) + + +def find_twb_in_zip(zip): + for filename in zip.namelist(): + if os.path.splitext(filename)[-1].lower() == '.twb': + return filename + + +def get_twb_xml_from_twbx(filename): + with temporary_directory() as temp: + with zipfile.ZipFile(filename) as zf: + zf.extractall(temp) + twb_file = find_twb_in_zip(zf) + twb_xml = ET.parse(os.path.join(temp, twb_file)) + + return twb_xml + + +def build_twbx_file(twbx_contents, zip): + for root_dir, _, files in os.walk(twbx_contents): + relative_dir = os.path.relpath(root_dir, twbx_contents) + for f in files: + temp_file_full_path = os.path.join( + twbx_contents, relative_dir, f) + zipname = os.path.join(relative_dir, f) + zip.write(temp_file_full_path, arcname=zipname) + class Workbook(object): """ @@ -24,30 +71,18 @@ def __init__(self, filename): Constructor. """ - # We have a valid type of input file - if self._is_valid_file(filename): - # set our filename, open .twb, initialize things - self._filename = filename - self._workbookTree = ET.parse(filename) - self._workbookRoot = self._workbookTree.getroot() - - # prepare our datasource objects - self._datasources = self._prepare_datasources( - self._workbookRoot) # self.workbookRoot.find('datasources') - else: - print('Invalid file type. Must be .twb or .tds.') - raise Exception() - - @classmethod - def from_file(cls, filename): - "Initialize datasource from file (.tds)" - if self._is_valid_file(filename): - self._filename = filename - dsxml = ET.parse(filename).getroot() - return cls(dsxml) + self._filename = filename + + # Determine if this is a twb or twbx and get the xml root + if zipfile.is_zipfile(self._filename): + self._workbookTree = get_twb_xml_from_twbx(self._filename) else: - print('Invalid file type. Must be .twb or .tds.') - raise Exception() + self._workbookTree = ET.parse(self._filename) + + self._workbookRoot = self._workbookTree.getroot() + # prepare our datasource objects + self._datasources = self._prepare_datasources( + self._workbookRoot) # self.workbookRoot.find('datasources') ########### # datasources @@ -76,7 +111,11 @@ def save(self): """ # save the file - self._workbookTree.write(self._filename) + + if zipfile.is_zipfile(self._filename): + self._save_into_twbx(self._filename) + else: + self._workbookTree.write(self._filename) def save_as(self, new_filename): """ @@ -89,8 +128,10 @@ def save_as(self, new_filename): Nothing. """ - - self._workbookTree.write(new_filename) + if zipfile.is_zipfile(self._filename): + self._save_into_twbx(new_filename) + else: + self._workbookTree.write(new_filename) ########################################################################### # @@ -107,6 +148,28 @@ def _prepare_datasources(self, xmlRoot): return datasources + def _save_into_twbx(self, filename=None): + # Save reuses existing filename, 'save as' takes a new one + if filename is None: + filename = self._filename + + # Saving a twbx means extracting the contents into a temp folder, + # saving the changes over the twb in that folder, and then + # packaging it back up into a specifically formatted zip with the correct + # relative file paths + + # Extract to temp directory + with temporary_directory() as temp_path: + with zipfile.ZipFile(self._filename) as zf: + twb_file = find_twb_in_zip(zf) + zf.extractall(temp_path) + # Write the new version of the twb to the temp directory + self._workbookTree.write(os.path.join(temp_path, twb_file)) + + # Write the new twbx with the contents of the temp folder + with zipfile.ZipFile(filename, "w", compression=zipfile.ZIP_DEFLATED) as new_twbx: + build_twbx_file(temp_path, new_twbx) + @staticmethod def _is_valid_file(filename): fileExtension = os.path.splitext(filename)[-1].lower() diff --git a/test.py b/test.py index fd7d1bd..56364e2 100644 --- a/test.py +++ b/test.py @@ -1,6 +1,8 @@ -import unittest +import base64 import io import os +import unittest + import xml.etree.ElementTree as ET from tableaudocumentapi import Workbook, Datasource, Connection, ConnectionParser @@ -17,6 +19,9 @@ TABLEAU_CONNECTION_XML = ET.fromstring( '''''') +TABLEAU_10_TWBX = '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' + + class HelperMethodTests(unittest.TestCase): def test_is_valid_file_with_valid_inputs(self): @@ -39,7 +44,6 @@ def test_can_extract_legacy_connection(self): self.assertIsInstance(connections[0], Connection) self.assertEqual(connections[0].dbname, 'TestV1') - def test_can_extract_federated_connections(self): parser = ConnectionParser(ET.fromstring(TABLEAU_10_TDS), '10.0') connections = parser.get_connections() @@ -122,7 +126,8 @@ def test_can_update_datasource_connection_and_save(self): original_wb.save() new_wb = Workbook(self.workbook_file.name) - self.assertEqual(new_wb.datasources[0].connections[0].dbname, 'newdb.test.tsi.lan') + self.assertEqual(new_wb.datasources[0].connections[ + 0].dbname, 'newdb.test.tsi.lan') class WorkbookModelV10Tests(unittest.TestCase): @@ -152,7 +157,46 @@ def test_can_update_datasource_connection_and_saveV10(self): original_wb.save() new_wb = Workbook(self.workbook_file.name) - self.assertEqual(new_wb.datasources[0].connections[0].dbname, 'newdb.test.tsi.lan') + self.assertEqual(new_wb.datasources[0].connections[ + 0].dbname, 'newdb.test.tsi.lan') + + +class WorkbookModelV10TWBXTests(unittest.TestCase): + + def setUp(self): + self.workbook_file = io.FileIO('testtwbx.twbx', 'wb') + self.workbook_file.write(base64.b64decode(TABLEAU_10_TWBX)) + self.workbook_file.seek(0) + + def tearDown(self): + self.workbook_file.close() + os.unlink(self.workbook_file.name) + + def test_can_open_twbx(self): + wb = Workbook(self.workbook_file.name) + self.assertTrue(wb.datasources) + self.assertTrue(wb.datasources[0].connections) + + def test_can_open_twbx_and_save_changes(self): + original_wb = Workbook(self.workbook_file.name) + original_wb.datasources[0].connections[0].server = 'newdb.test.tsi.lan' + original_wb.save() + + new_wb = Workbook(self.workbook_file.name) + self.assertEqual(new_wb.datasources[0].connections[ + 0].server, 'newdb.test.tsi.lan') + + def test_can_open_twbx_and_save_as_changes(self): + new_twbx_filename = self.workbook_file.name + "_TEST_SAVE_AS" + original_wb = Workbook(self.workbook_file.name) + original_wb.datasources[0].connections[0].server = 'newdb.test.tsi.lan' + original_wb.save_as(new_twbx_filename) + + new_wb = Workbook(new_twbx_filename) + self.assertEqual(new_wb.datasources[0].connections[ + 0].server, 'newdb.test.tsi.lan') + + os.unlink(new_twbx_filename) if __name__ == '__main__': unittest.main() From c70cb5fda4bc07d51948cf53eb78ff19987938e2 Mon Sep 17 00:00:00 2001 From: T8y8 Date: Wed, 29 Jun 2016 15:16:40 -0700 Subject: [PATCH 2/4] Update the tests to use external files --- test/assets/CONNECTION.xml | 1 + test/assets/TABLEAU_10_TDS.TDS | 1 + test/assets/TABLEAU_10_TWB.twb | 1 + test/assets/TABLEAU_10_TWBX.twbx | Bin 0 -> 12146 bytes test/assets/TABLEAU_93_TDS.tds | 1 + test/assets/TABLEAU_93_TWB.twb | 1 + test/bvt.py | 51 ++++++++++++++----------------- 7 files changed, 28 insertions(+), 28 deletions(-) create mode 100644 test/assets/CONNECTION.xml create mode 100644 test/assets/TABLEAU_10_TDS.TDS create mode 100644 test/assets/TABLEAU_10_TWB.twb create mode 100644 test/assets/TABLEAU_10_TWBX.twbx create mode 100644 test/assets/TABLEAU_93_TDS.tds create mode 100644 test/assets/TABLEAU_93_TWB.twb diff --git a/test/assets/CONNECTION.xml b/test/assets/CONNECTION.xml new file mode 100644 index 0000000..392d112 --- /dev/null +++ b/test/assets/CONNECTION.xml @@ -0,0 +1 @@ + diff --git a/test/assets/TABLEAU_10_TDS.TDS b/test/assets/TABLEAU_10_TDS.TDS new file mode 100644 index 0000000..7a81784 --- /dev/null +++ b/test/assets/TABLEAU_10_TDS.TDS @@ -0,0 +1 @@ + diff --git a/test/assets/TABLEAU_10_TWB.twb b/test/assets/TABLEAU_10_TWB.twb new file mode 100644 index 0000000..c116bdf --- /dev/null +++ b/test/assets/TABLEAU_10_TWB.twb @@ -0,0 +1 @@ + diff --git a/test/assets/TABLEAU_10_TWBX.twbx b/test/assets/TABLEAU_10_TWBX.twbx new file mode 100644 index 0000000000000000000000000000000000000000..ef8f910d02036fbaeb6d9948f46df02078a45b39 GIT binary patch literal 12146 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 literal 0 HcmV?d00001 diff --git a/test/assets/TABLEAU_93_TDS.tds b/test/assets/TABLEAU_93_TDS.tds new file mode 100644 index 0000000..2afa3ea --- /dev/null +++ b/test/assets/TABLEAU_93_TDS.tds @@ -0,0 +1 @@ + diff --git a/test/assets/TABLEAU_93_TWB.twb b/test/assets/TABLEAU_93_TWB.twb new file mode 100644 index 0000000..cdb6484 --- /dev/null +++ b/test/assets/TABLEAU_93_TWB.twb @@ -0,0 +1 @@ + diff --git a/test/bvt.py b/test/bvt.py index 589a9ee..779fd7b 100644 --- a/test/bvt.py +++ b/test/bvt.py @@ -1,5 +1,3 @@ -import base64 -import io import os import unittest @@ -7,21 +5,18 @@ from tableaudocumentapi import Workbook, Datasource, Connection, ConnectionParser -# Disable the 120 line limit because of the embedded XML on these lines -# TODO: Move the XML into external files and load them when needed -TABLEAU_93_WORKBOOK = '''''' # noqa +TABLEAU_93_TWB = 'test/assets/TABLEAU_93_TWB.twb' -TABLEAU_93_TDS = '''''' # noqa +TABLEAU_93_TDS = 'test/assets/TABLEAU_93_TDS.tds' -TABLEAU_10_TDS = '''''' # noqa +TABLEAU_10_TDS = 'test/assets/TABLEAU_10_TDS.tds' -TABLEAU_10_WORKBOOK = '''''' # noqa +TABLEAU_10_TWB = 'test/assets/TABLEAU_10_TWB.twb' -TABLEAU_CONNECTION_XML = ET.fromstring( - '''''') # noqa +TABLEAU_CONNECTION_XML = ET.parse('test/assets/CONNECTION.xml').getroot() -TABLEAU_10_TWBX = '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' +TABLEAU_10_TWBX = 'test/assets/TABLEAU_10_TWBX.twbx' class HelperMethodTests(unittest.TestCase): @@ -40,14 +35,14 @@ def test_is_valid_file_with_invalid_inputs(self): class ConnectionParserTests(unittest.TestCase): def test_can_extract_legacy_connection(self): - parser = ConnectionParser(ET.fromstring(TABLEAU_93_TDS), '9.2') + parser = ConnectionParser(ET.parse(TABLEAU_93_TDS), '9.2') connections = parser.get_connections() self.assertIsInstance(connections, list) self.assertIsInstance(connections[0], Connection) self.assertEqual(connections[0].dbname, 'TestV1') def test_can_extract_federated_connections(self): - parser = ConnectionParser(ET.fromstring(TABLEAU_10_TDS), '10.0') + parser = ConnectionParser(ET.parse(TABLEAU_10_TDS), '10.0') connections = parser.get_connections() self.assertIsInstance(connections, list) self.assertIsInstance(connections[0], Connection) @@ -80,9 +75,9 @@ def test_can_write_attributes_to_connection(self): class DatasourceModelTests(unittest.TestCase): def setUp(self): - self.tds_file = io.FileIO('test.tds', 'w') - self.tds_file.write(TABLEAU_93_TDS.encode('utf8')) - self.tds_file.seek(0) + with open(TABLEAU_93_TDS, 'rb') as in_file, open('test.tds', 'wb') as out_file: + out_file.write(in_file.read()) + self.tds_file = out_file def tearDown(self): self.tds_file.close() @@ -121,9 +116,9 @@ def test_save_has_xml_declaration(self): class DatasourceModelV10Tests(unittest.TestCase): def setUp(self): - self.tds_file = io.FileIO('test10.tds', 'w') - self.tds_file.write(TABLEAU_10_TDS.encode('utf8')) - self.tds_file.seek(0) + with open(TABLEAU_10_TDS, 'rb') as in_file, open('test.twb', 'wb') as out_file: + out_file.write(in_file.read()) + self.tds_file = out_file def tearDown(self): self.tds_file.close() @@ -151,9 +146,9 @@ def test_can_save_tds(self): class WorkbookModelTests(unittest.TestCase): def setUp(self): - self.workbook_file = io.FileIO('test.twb', 'w') - self.workbook_file.write(TABLEAU_93_WORKBOOK.encode('utf8')) - self.workbook_file.seek(0) + with open(TABLEAU_93_TWB, 'rb') as in_file, open('test.twb', 'wb') as out_file: + out_file.write(in_file.read()) + self.workbook_file = out_file def tearDown(self): self.workbook_file.close() @@ -179,9 +174,9 @@ def test_can_update_datasource_connection_and_save(self): class WorkbookModelV10Tests(unittest.TestCase): def setUp(self): - self.workbook_file = io.FileIO('testv10.twb', 'w') - self.workbook_file.write(TABLEAU_10_WORKBOOK.encode('utf8')) - self.workbook_file.seek(0) + with open(TABLEAU_10_TWB, 'rb') as in_file, open('test.twb', 'wb') as out_file: + out_file.write(in_file.read()) + self.workbook_file = out_file def tearDown(self): self.workbook_file.close() @@ -221,9 +216,9 @@ def test_save_has_xml_declaration(self): class WorkbookModelV10TWBXTests(unittest.TestCase): def setUp(self): - self.workbook_file = io.FileIO('testtwbx.twbx', 'wb') - self.workbook_file.write(base64.b64decode(TABLEAU_10_TWBX)) - self.workbook_file.seek(0) + with open(TABLEAU_10_TWBX, 'rb') as in_file, open('test.twbx', 'wb') as out_file: + out_file.write(in_file.read()) + self.workbook_file = out_file def tearDown(self): self.workbook_file.close() From 955e418d3235db7a28fe6209f3048b7b8441d81f Mon Sep 17 00:00:00 2001 From: T8y8 Date: Wed, 29 Jun 2016 15:37:27 -0700 Subject: [PATCH 3/4] Fix case sensitivity --- test/assets/{TABLEAU_10_TDS.TDS => TABLEAU_10_TDS.tds} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/assets/{TABLEAU_10_TDS.TDS => TABLEAU_10_TDS.tds} (100%) diff --git a/test/assets/TABLEAU_10_TDS.TDS b/test/assets/TABLEAU_10_TDS.tds similarity index 100% rename from test/assets/TABLEAU_10_TDS.TDS rename to test/assets/TABLEAU_10_TDS.tds From 92668e0b065ea4a3d7bf70146c0c918af3c3ba30 Mon Sep 17 00:00:00 2001 From: T8y8 Date: Wed, 29 Jun 2016 16:00:54 -0700 Subject: [PATCH 4/4] Use more flexible paths for test assets --- test/bvt.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/bvt.py b/test/bvt.py index 779fd7b..aa4a247 100644 --- a/test/bvt.py +++ b/test/bvt.py @@ -5,18 +5,19 @@ from tableaudocumentapi import Workbook, Datasource, Connection, ConnectionParser +TEST_DIR = os.path.dirname(__file__) -TABLEAU_93_TWB = 'test/assets/TABLEAU_93_TWB.twb' +TABLEAU_93_TWB = os.path.join(TEST_DIR, 'assets', 'TABLEAU_93_TWB.twb') -TABLEAU_93_TDS = 'test/assets/TABLEAU_93_TDS.tds' +TABLEAU_93_TDS = os.path.join(TEST_DIR, 'assets', 'TABLEAU_93_TDS.tds') -TABLEAU_10_TDS = 'test/assets/TABLEAU_10_TDS.tds' +TABLEAU_10_TDS = os.path.join(TEST_DIR, 'assets', 'TABLEAU_10_TDS.tds') -TABLEAU_10_TWB = 'test/assets/TABLEAU_10_TWB.twb' +TABLEAU_10_TWB = os.path.join(TEST_DIR, 'assets', 'TABLEAU_10_TWB.twb') -TABLEAU_CONNECTION_XML = ET.parse('test/assets/CONNECTION.xml').getroot() +TABLEAU_CONNECTION_XML = ET.parse(os.path.join(TEST_DIR, 'assets', 'CONNECTION.xml')).getroot() -TABLEAU_10_TWBX = 'test/assets/TABLEAU_10_TWBX.twbx' +TABLEAU_10_TWBX = os.path.join(TEST_DIR, 'assets', 'TABLEAU_10_TWBX.twbx') class HelperMethodTests(unittest.TestCase): pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy