From 9a8f0d165a313996a05be31a9fcc8c4a4561d24f Mon Sep 17 00:00:00 2001 From: T8y8 Date: Wed, 29 Jun 2016 21:15:20 -0700 Subject: [PATCH 1/7] Initial TDSX support. Lots of duplicated code with TWBX's --- tableaudocumentapi/datasource.py | 83 +++++++++++++++++++++++++++++-- test/assets/TABLEAU_10_TDSX.tdsx | Bin 0 -> 1866 bytes test/bvt.py | 43 +++++++++++++++- 3 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 test/assets/TABLEAU_10_TDSX.tdsx diff --git a/tableaudocumentapi/datasource.py b/tableaudocumentapi/datasource.py index 617004a..3d0a419 100644 --- a/tableaudocumentapi/datasource.py +++ b/tableaudocumentapi/datasource.py @@ -3,10 +3,51 @@ # Datasource - A class for writing datasources to Tableau files # ############################################################################### +import contextlib +import os +import shutil +import tempfile +import zipfile + import xml.etree.ElementTree as ET from tableaudocumentapi import Connection +@contextlib.contextmanager +def temporary_directory(*args, **kwargs): + d = tempfile.mkdtemp(*args, **kwargs) + try: + yield d + finally: + shutil.rmtree(d) + + +def find_tds_in_zip(zip): + for filename in zip.namelist(): + if os.path.splitext(filename)[-1].lower() == '.tds': + return filename + + +def get_tds_xml_from_tdsx(filename): + with temporary_directory() as temp: + with zipfile.ZipFile(filename) as zf: + zf.extractall(temp) + tds_file = find_tds_in_zip(zf) + tds_xml = ET.parse(os.path.join(temp, tds_file)) + + return tds_xml + + +def build_tdsx_file(tdsx_contents, zip): + for root_dir, _, files in os.walk(tdsx_contents): + relative_dir = os.path.relpath(root_dir, tdsx_contents) + for f in files: + temp_file_full_path = os.path.join( + tdsx_contents, relative_dir, f) + zipname = os.path.join(relative_dir, f) + zip.write(temp_file_full_path, arcname=zipname) + + class ConnectionParser(object): def __init__(self, datasource_xml, version): @@ -56,9 +97,36 @@ def __init__(self, dsxml, filename=None): @classmethod def from_file(cls, filename): "Initialize datasource from file (.tds)" - dsxml = ET.parse(filename).getroot() + + if zipfile.is_zipfile(filename): + dsxml = get_tds_xml_from_tdsx(filename).getroot() + else: + dsxml = ET.parse(filename).getroot() return cls(dsxml, filename) + def _save_into_tdsx(self, filename=None): + # Save reuses existing filename, 'save as' takes a new one + if filename is None: + filename = self._filename + + # Saving a tdsx means extracting the contents into a temp folder, + # saving the changes over the tds 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: + tds_file = find_tds_in_zip(zf) + zf.extractall(temp_path) + # Write the new version of the tds to the temp directory + self._datasourceTree.write(os.path.join( + temp_path, tds_file), encoding="utf-8", xml_declaration=True) + + # Write the new tdsx with the contents of the temp folder + with zipfile.ZipFile(filename, "w", compression=zipfile.ZIP_DEFLATED) as new_tdsx: + build_tdsx_file(temp_path, new_tdsx) + def save(self): """ Call finalization code and save file. @@ -72,7 +140,12 @@ def save(self): """ # save the file - self._datasourceTree.write(self._filename, encoding="utf-8", xml_declaration=True) + + if zipfile.is_zipfile(self._filename): + self._save_into_tdsx(self._filename) + else: + self._datasourceTree.write( + self._filename, encoding="utf-8", xml_declaration=True) def save_as(self, new_filename): """ @@ -85,7 +158,11 @@ def save_as(self, new_filename): Nothing. """ - self._datasourceTree.write(new_filename, encoding="utf-8", xml_declaration=True) + if zipfile.is_zipfile(self._filename): + self._save_into_tdsx(new_filename) + else: + self._datasourceTree.write( + new_filename, encoding="utf-8", xml_declaration=True) ########### # name diff --git a/test/assets/TABLEAU_10_TDSX.tdsx b/test/assets/TABLEAU_10_TDSX.tdsx new file mode 100644 index 0000000000000000000000000000000000000000..f94b678fbdbf718b81ff4353b4014424cad97a55 GIT binary patch literal 1866 zcma)-cRbq(1IB+r)yP>f+R`)57MmE2J!;jcJB_18XsFd95=6|@sB$aco zo#Rkdi4~g?xuPhC_U+yK_xt(0&mYh8dH#5wf4}zD%q;u>E5HV@S1(yK*gsdwW(5Eq zV*ro`008yC`$32&`hmBSYA`lT3xiTo*YL(@X{jP%4q5|i zHV!8@Gb?Nf10GGchV)6uGFg=edWLbD4TZglPU@Vt-9$s=KFt2% zXmfX9U^V~1;wbaWm)dPBZi2b(3U@{oI>R(dq48#nzM(2{G|3{;AnJUJA$+OjoI6ep z4jdgwZ}8Qtd`CYL2>j|{7rADa+eZlUN+gL@31*@|$0}Zfx%j}6Lxfkn9NWC>Nky}#lRSoV^Z151-~x#+qr{k6vMOBSK0;FCd?d$)93*>fZuef} zTFxEIbO@2dj*yf_`^B|bRY)Ib3`|RXzr`TGpw81&ykuohtbQ+mEB7t)DR3a0I2{qY zruT3CwvM>rEP}>CZ)e#2G@-njr9oh9~E$niG^{$yW5U&YhMz>zJ8LoMSc1iS}GF z(vGzT@jK@BgP@ziJq`%V=1L5yQ6`NgPm zr@RJ-iAgDWvbpIjBz)T z_oHk_;d3jimOSS+_lBhVHl~~AkTqc*{YUO)-0`tS$fw>0m?iN^p3Y91G?=R?&}7AY zZ~%@d54a$qR{5yZX9RU26U?69`Q_z6H|gAv1c<{8_IoCQ586ud-wyqyrFadD~7MHL-(|Fw%qU*8!$4KHYmT|Trd z)*MMQ8QlWQV6|FpdKtxLl)Rrgf8UtnJ5>ryP=eMR71Re2+?r;6hBu`8+;|_tbp%+& z$4elYiyn}JcGecP8(j^;X;(8U=RE=vRL>Kk$nE?-ZSn94N30+=jNs+hoD#bTxLnV| zH3(;Afaa3}erT>rz96oMDj?!8YvfA%TkJVIidqy?XnkMZv%$-1iMI#W^FMLimvw)V zll|vRKjh$1$Y1c!CoaAI2o9~*X3VjXPC#Ks8+RIWOlMajB5?HFLN|sD2(H9A{(QMJO;|4OO7i;|{9!2>znk&>#@FXb zq02FkPkw`k<)u&fK@<9ZhK$~0asHDQL=4BiBB<;}p->u!<2JE~J4-U$JPU*8wNf+) zY4|k`sga5K@7Lg~%D)=SmmG?mu`?F40%A^%WIgY(7@u}^tlzL%nlq){SAx#LQ~P3} zxS1gmHc2QvIjjbIykb9mDpotU@O>LY1UEvO)I_;8&a&XlGT!e6PA+27yUio`&ktw( zH9`@5O3cOfJ?XQmP-}cHKYFo)O_S{AXQN(b;&}i+u`#ankiO&iLfyAK zrWY<0DH^v62L#|F28PjlaWwIJhIFwX+tyrrigrJ!ALv=pm;wC$RveRzFQy}Z#y#Kq zrI8FWOQia{ztJWBN^QDKEL5X$$!pMoNfkCBX>K0Oz!ey~MO@$d6ofDQ4(LP5_R3QF zGy}_x+m|McPYO^ryXlmZE_`4EXZ)F2W3~}R*ah4sAM?bh?YM)>Il4ER`uP_fgZHST9zug#6rJU&}y!*c5HlSeFH)-IBFQu$Zb-`HD& km;m5^)9oAJ0MI}7ul|45*;`-v#>juRAOP_#7yoVl0Xp7+R{#J2 literal 0 HcmV?d00001 diff --git a/test/bvt.py b/test/bvt.py index aa4a247..81b49b9 100644 --- a/test/bvt.py +++ b/test/bvt.py @@ -15,10 +15,13 @@ TABLEAU_10_TWB = os.path.join(TEST_DIR, 'assets', 'TABLEAU_10_TWB.twb') -TABLEAU_CONNECTION_XML = ET.parse(os.path.join(TEST_DIR, 'assets', 'CONNECTION.xml')).getroot() +TABLEAU_CONNECTION_XML = ET.parse(os.path.join( + TEST_DIR, 'assets', 'CONNECTION.xml')).getroot() TABLEAU_10_TWBX = os.path.join(TEST_DIR, 'assets', 'TABLEAU_10_TWBX.twbx') +TABLEAU_10_TDSX = os.path.join(TEST_DIR, 'assets', 'TABLEAU_10_TDSX.tdsx') + class HelperMethodTests(unittest.TestCase): @@ -144,6 +147,44 @@ def test_can_save_tds(self): self.assertEqual(new_tds.connections[0].dbname, 'newdb.test.tsi.lan') +class DatasourceModelV10TDSXTests(unittest.TestCase): + + def setUp(self): + with open(TABLEAU_10_TDSX, 'rb') as in_file, open('test.tdsx', 'wb') as out_file: + out_file.write(in_file.read()) + self.tdsx_file = out_file + + def tearDown(self): + self.tdsx_file.close() + os.unlink(self.tdsx_file.name) + + def test_can_open_tdsx(self): + ds = Datasource.from_file(self.tdsx_file.name) + self.assertTrue(ds.connections) + self.assertTrue(ds.name) + + def test_can_open_tdsx_and_save_changes(self): + original_tdsx = Datasource.from_file(self.tdsx_file.name) + original_tdsx.connections[0].server = 'newdb.test.tsi.lan' + original_tdsx.save() + + new_tdsx = Datasource.from_file(self.tdsx_file.name) + self.assertEqual(new_tdsx.connections[ + 0].server, 'newdb.test.tsi.lan') + + def test_can_open_tdsx_and_save_as_changes(self): + new_tdsx_filename = self.tdsx_file.name + "_TEST_SAVE_AS" + original_wb = Datasource.from_file(self.tdsx_file.name) + original_wb.connections[0].server = 'newdb.test.tsi.lan' + original_wb.save_as(new_tdsx_filename) + + new_wb = Datasource.from_file(new_tdsx_filename) + self.assertEqual(new_wb.connections[ + 0].server, 'newdb.test.tsi.lan') + + os.unlink(new_tdsx_filename) + + class WorkbookModelTests(unittest.TestCase): def setUp(self): From bd6d3c9b04c489f99fc7ee8e98c6304edbce836b Mon Sep 17 00:00:00 2001 From: T8y8 Date: Wed, 29 Jun 2016 22:18:42 -0700 Subject: [PATCH 2/7] Refactor all the archive manipulation logic into one module --- tableaudocumentapi/archivefile.py | 67 ++++++++++++++++++++++++++++ tableaudocumentapi/datasource.py | 70 +++--------------------------- tableaudocumentapi/workbook.py | 72 +++---------------------------- test/bvt.py | 5 +-- 4 files changed, 81 insertions(+), 133 deletions(-) create mode 100644 tableaudocumentapi/archivefile.py diff --git a/tableaudocumentapi/archivefile.py b/tableaudocumentapi/archivefile.py new file mode 100644 index 0000000..5b28a15 --- /dev/null +++ b/tableaudocumentapi/archivefile.py @@ -0,0 +1,67 @@ +import contextlib +import os +import shutil +import tempfile +import zipfile + +import xml.etree.ElementTree as ET + + +@contextlib.contextmanager +def temporary_directory(*args, **kwargs): + d = tempfile.mkdtemp(*args, **kwargs) + try: + yield d + finally: + shutil.rmtree(d) + + +def find_file_in_zip(zip, ext): + for filename in zip.namelist(): + if os.path.splitext(filename)[-1].lower() == ext[:-1]: + return filename + + +def get_xml_from_archive(filename): + file_type = os.path.splitext(filename)[-1].lower() + with temporary_directory() as temp: + with zipfile.ZipFile(filename) as zf: + zf.extractall(temp) + xml_file = find_file_in_zip(zf, file_type) + xml_tree = ET.parse(os.path.join(temp, xml_file)) + + return xml_tree + + +def build_archive_file(archive_contents, zip): + for root_dir, _, files in os.walk(archive_contents): + relative_dir = os.path.relpath(root_dir, archive_contents) + for f in files: + temp_file_full_path = os.path.join( + archive_contents, relative_dir, f) + zipname = os.path.join(relative_dir, f) + zip.write(temp_file_full_path, arcname=zipname) + + +def save_into_archive(xml_tree, filename, new_filename=None): + # Saving a archive 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 + + if new_filename is None: + new_filename = filename + + # Extract to temp directory + with temporary_directory() as temp_path: + file_type = os.path.splitext(filename)[-1].lower() + with zipfile.ZipFile(filename) as zf: + twb_file = find_file_in_zip(zf, file_type) + zf.extractall(temp_path) + # Write the new version of the twb to the temp directory + xml_tree.write(os.path.join( + temp_path, twb_file), encoding="utf-8", xml_declaration=True) + + # Write the new archive with the contents of the temp folder + with zipfile.ZipFile(new_filename, "w", compression=zipfile.ZIP_DEFLATED) as new_archive: + build_archive_file(temp_path, new_archive) diff --git a/tableaudocumentapi/datasource.py b/tableaudocumentapi/datasource.py index 3d0a419..3d23412 100644 --- a/tableaudocumentapi/datasource.py +++ b/tableaudocumentapi/datasource.py @@ -3,49 +3,11 @@ # Datasource - A class for writing datasources to Tableau files # ############################################################################### -import contextlib import os -import shutil -import tempfile import zipfile import xml.etree.ElementTree as ET -from tableaudocumentapi import Connection - - -@contextlib.contextmanager -def temporary_directory(*args, **kwargs): - d = tempfile.mkdtemp(*args, **kwargs) - try: - yield d - finally: - shutil.rmtree(d) - - -def find_tds_in_zip(zip): - for filename in zip.namelist(): - if os.path.splitext(filename)[-1].lower() == '.tds': - return filename - - -def get_tds_xml_from_tdsx(filename): - with temporary_directory() as temp: - with zipfile.ZipFile(filename) as zf: - zf.extractall(temp) - tds_file = find_tds_in_zip(zf) - tds_xml = ET.parse(os.path.join(temp, tds_file)) - - return tds_xml - - -def build_tdsx_file(tdsx_contents, zip): - for root_dir, _, files in os.walk(tdsx_contents): - relative_dir = os.path.relpath(root_dir, tdsx_contents) - for f in files: - temp_file_full_path = os.path.join( - tdsx_contents, relative_dir, f) - zipname = os.path.join(relative_dir, f) - zip.write(temp_file_full_path, arcname=zipname) +from tableaudocumentapi import Connection, archivefile class ConnectionParser(object): @@ -99,34 +61,11 @@ def from_file(cls, filename): "Initialize datasource from file (.tds)" if zipfile.is_zipfile(filename): - dsxml = get_tds_xml_from_tdsx(filename).getroot() + dsxml = archivefile.get_xml_from_archive(filename).getroot() else: dsxml = ET.parse(filename).getroot() return cls(dsxml, filename) - def _save_into_tdsx(self, filename=None): - # Save reuses existing filename, 'save as' takes a new one - if filename is None: - filename = self._filename - - # Saving a tdsx means extracting the contents into a temp folder, - # saving the changes over the tds 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: - tds_file = find_tds_in_zip(zf) - zf.extractall(temp_path) - # Write the new version of the tds to the temp directory - self._datasourceTree.write(os.path.join( - temp_path, tds_file), encoding="utf-8", xml_declaration=True) - - # Write the new tdsx with the contents of the temp folder - with zipfile.ZipFile(filename, "w", compression=zipfile.ZIP_DEFLATED) as new_tdsx: - build_tdsx_file(temp_path, new_tdsx) - def save(self): """ Call finalization code and save file. @@ -142,7 +81,7 @@ def save(self): # save the file if zipfile.is_zipfile(self._filename): - self._save_into_tdsx(self._filename) + archivefile.save_into_archive(self._datasourceTree, self._filename) else: self._datasourceTree.write( self._filename, encoding="utf-8", xml_declaration=True) @@ -159,7 +98,8 @@ def save_as(self, new_filename): """ if zipfile.is_zipfile(self._filename): - self._save_into_tdsx(new_filename) + archivefile.save_into_archive( + self._datasourceTree, self._filename, new_filename) else: self._datasourceTree.write( new_filename, encoding="utf-8", xml_declaration=True) diff --git a/tableaudocumentapi/workbook.py b/tableaudocumentapi/workbook.py index 0da1827..fb2c824 100644 --- a/tableaudocumentapi/workbook.py +++ b/tableaudocumentapi/workbook.py @@ -3,15 +3,12 @@ # 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 +from tableaudocumentapi import Datasource, archivefile ########################################################################### # @@ -20,41 +17,6 @@ ########################################################################### -@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): """ A class for writing Tableau workbook files. @@ -75,7 +37,8 @@ def __init__(self, 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) + self._workbookTree = archivefile.get_xml_from_archive( + self._filename) else: self._workbookTree = ET.parse(self._filename) @@ -113,7 +76,8 @@ def save(self): # save the file if zipfile.is_zipfile(self._filename): - self._save_into_twbx(self._filename) + archivefile.save_into_archive( + self._workbookTree, filename=self._filename) else: self._workbookTree.write( self._filename, encoding="utf-8", xml_declaration=True) @@ -131,7 +95,8 @@ def save_as(self, new_filename): """ if zipfile.is_zipfile(self._filename): - self._save_into_twbx(new_filename) + archivefile.save_into_archive( + self._workbookTree, self._filename, new_filename) else: self._workbookTree.write( new_filename, encoding="utf-8", xml_declaration=True) @@ -151,29 +116,6 @@ 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), encoding="utf-8", xml_declaration=True) - - # 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/bvt.py b/test/bvt.py index 81b49b9..49393b3 100644 --- a/test/bvt.py +++ b/test/bvt.py @@ -173,7 +173,7 @@ def test_can_open_tdsx_and_save_changes(self): 0].server, 'newdb.test.tsi.lan') def test_can_open_tdsx_and_save_as_changes(self): - new_tdsx_filename = self.tdsx_file.name + "_TEST_SAVE_AS" + new_tdsx_filename = 'newtdsx.tdsx' original_wb = Datasource.from_file(self.tdsx_file.name) original_wb.connections[0].server = 'newdb.test.tsi.lan' original_wb.save_as(new_tdsx_filename) @@ -181,7 +181,6 @@ def test_can_open_tdsx_and_save_as_changes(self): new_wb = Datasource.from_file(new_tdsx_filename) self.assertEqual(new_wb.connections[ 0].server, 'newdb.test.tsi.lan') - os.unlink(new_tdsx_filename) @@ -281,7 +280,7 @@ def test_can_open_twbx_and_save_changes(self): 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" + new_twbx_filename = 'newtwbx.twbx' 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) From 8385b5c92065f97c23fc882f719c86b0875e4956 Mon Sep 17 00:00:00 2001 From: T8y8 Date: Thu, 30 Jun 2016 09:33:30 -0700 Subject: [PATCH 3/7] Move save logic into helper function. Remove unused tests and methods from Workbook class --- .../{archivefile.py => containerfile.py} | 18 ++++++++++--- tableaudocumentapi/datasource.py | 17 +++--------- tableaudocumentapi/workbook.py | 26 ++++--------------- test/bvt.py | 13 ---------- 4 files changed, 23 insertions(+), 51 deletions(-) rename tableaudocumentapi/{archivefile.py => containerfile.py} (79%) diff --git a/tableaudocumentapi/archivefile.py b/tableaudocumentapi/containerfile.py similarity index 79% rename from tableaudocumentapi/archivefile.py rename to tableaudocumentapi/containerfile.py index 5b28a15..a4a7930 100644 --- a/tableaudocumentapi/archivefile.py +++ b/tableaudocumentapi/containerfile.py @@ -45,7 +45,7 @@ def build_archive_file(archive_contents, zip): def save_into_archive(xml_tree, filename, new_filename=None): # Saving a archive means extracting the contents into a temp folder, - # saving the changes over the twb in that folder, and then + # saving the changes over the twb/tds in that folder, and then # packaging it back up into a specifically formatted zip with the correct # relative file paths @@ -56,12 +56,22 @@ def save_into_archive(xml_tree, filename, new_filename=None): with temporary_directory() as temp_path: file_type = os.path.splitext(filename)[-1].lower() with zipfile.ZipFile(filename) as zf: - twb_file = find_file_in_zip(zf, file_type) + xml_file = find_file_in_zip(zf, file_type) zf.extractall(temp_path) - # Write the new version of the twb to the temp directory + # Write the new version of the file to the temp directory xml_tree.write(os.path.join( - temp_path, twb_file), encoding="utf-8", xml_declaration=True) + temp_path, xml_file), encoding="utf-8", xml_declaration=True) # Write the new archive with the contents of the temp folder with zipfile.ZipFile(new_filename, "w", compression=zipfile.ZIP_DEFLATED) as new_archive: build_archive_file(temp_path, new_archive) + + +def _save_file(container_file, xml_tree, new_filename=None): + if zipfile.is_zipfile(container_file): + save_into_archive(xml_tree, container_file, new_filename) + else: + xml_tree.write(container_file, encoding="utf-8", xml_declaration=True) + + + diff --git a/tableaudocumentapi/datasource.py b/tableaudocumentapi/datasource.py index 3d23412..33c0c4f 100644 --- a/tableaudocumentapi/datasource.py +++ b/tableaudocumentapi/datasource.py @@ -7,7 +7,7 @@ import zipfile import xml.etree.ElementTree as ET -from tableaudocumentapi import Connection, archivefile +from tableaudocumentapi import Connection, containerfile class ConnectionParser(object): @@ -61,7 +61,7 @@ def from_file(cls, filename): "Initialize datasource from file (.tds)" if zipfile.is_zipfile(filename): - dsxml = archivefile.get_xml_from_archive(filename).getroot() + dsxml = containerfile.get_xml_from_archive(filename).getroot() else: dsxml = ET.parse(filename).getroot() return cls(dsxml, filename) @@ -80,11 +80,7 @@ def save(self): # save the file - if zipfile.is_zipfile(self._filename): - archivefile.save_into_archive(self._datasourceTree, self._filename) - else: - self._datasourceTree.write( - self._filename, encoding="utf-8", xml_declaration=True) + containerfile._save_file(self._filename, self._datasourceTree) def save_as(self, new_filename): """ @@ -97,12 +93,7 @@ def save_as(self, new_filename): Nothing. """ - if zipfile.is_zipfile(self._filename): - archivefile.save_into_archive( - self._datasourceTree, self._filename, new_filename) - else: - self._datasourceTree.write( - new_filename, encoding="utf-8", xml_declaration=True) + containerfile._save_file(self._filename, self._datasourceTree, new_filename) ########### # name diff --git a/tableaudocumentapi/workbook.py b/tableaudocumentapi/workbook.py index fb2c824..ecfc13d 100644 --- a/tableaudocumentapi/workbook.py +++ b/tableaudocumentapi/workbook.py @@ -8,7 +8,7 @@ import xml.etree.ElementTree as ET -from tableaudocumentapi import Datasource, archivefile +from tableaudocumentapi import Datasource, containerfile ########################################################################### # @@ -37,7 +37,7 @@ def __init__(self, filename): # Determine if this is a twb or twbx and get the xml root if zipfile.is_zipfile(self._filename): - self._workbookTree = archivefile.get_xml_from_archive( + self._workbookTree = containerfile.get_xml_from_archive( self._filename) else: self._workbookTree = ET.parse(self._filename) @@ -74,13 +74,7 @@ def save(self): """ # save the file - - if zipfile.is_zipfile(self._filename): - archivefile.save_into_archive( - self._workbookTree, filename=self._filename) - else: - self._workbookTree.write( - self._filename, encoding="utf-8", xml_declaration=True) + containerfile._save_file(self._filename, self._workbookTree) def save_as(self, new_filename): """ @@ -93,13 +87,8 @@ def save_as(self, new_filename): Nothing. """ - - if zipfile.is_zipfile(self._filename): - archivefile.save_into_archive( - self._workbookTree, self._filename, new_filename) - else: - self._workbookTree.write( - new_filename, encoding="utf-8", xml_declaration=True) + containerfile._save_file( + self._filename, self._workbookTree, new_filename) ########################################################################### # @@ -115,8 +104,3 @@ def _prepare_datasources(self, xmlRoot): datasources.append(ds) return datasources - - @staticmethod - def _is_valid_file(filename): - fileExtension = os.path.splitext(filename)[-1].lower() - return fileExtension in ('.twb', '.tds') diff --git a/test/bvt.py b/test/bvt.py index 49393b3..1dedd57 100644 --- a/test/bvt.py +++ b/test/bvt.py @@ -23,19 +23,6 @@ TABLEAU_10_TDSX = os.path.join(TEST_DIR, 'assets', 'TABLEAU_10_TDSX.tdsx') -class HelperMethodTests(unittest.TestCase): - - def test_is_valid_file_with_valid_inputs(self): - self.assertTrue(Workbook._is_valid_file('file1.tds')) - self.assertTrue(Workbook._is_valid_file('file2.twb')) - self.assertTrue(Workbook._is_valid_file('tds.twb')) - - def test_is_valid_file_with_invalid_inputs(self): - self.assertFalse(Workbook._is_valid_file('')) - self.assertFalse(Workbook._is_valid_file('file1.tds2')) - self.assertFalse(Workbook._is_valid_file('file2.twb3')) - - class ConnectionParserTests(unittest.TestCase): def test_can_extract_legacy_connection(self): From cbcda8f0baa691ff0b7c1e0533df85f186cd3841 Mon Sep 17 00:00:00 2001 From: T8y8 Date: Thu, 30 Jun 2016 11:33:54 -0700 Subject: [PATCH 4/7] Now find the file by checking if it's a workbook or datasource, and open the file directly from the zip instead of extracting multiple times --- tableaudocumentapi/containerfile.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/tableaudocumentapi/containerfile.py b/tableaudocumentapi/containerfile.py index a4a7930..30b389f 100644 --- a/tableaudocumentapi/containerfile.py +++ b/tableaudocumentapi/containerfile.py @@ -16,19 +16,21 @@ def temporary_directory(*args, **kwargs): shutil.rmtree(d) -def find_file_in_zip(zip, ext): +def find_file_in_zip(zip): for filename in zip.namelist(): - if os.path.splitext(filename)[-1].lower() == ext[:-1]: + try: + ET.parse(zip.open(filename)).getroot().tag in ( + 'workbook', 'datasource') return filename + except ET.ParseError: + # That's not an XML file by gosh + pass def get_xml_from_archive(filename): - file_type = os.path.splitext(filename)[-1].lower() - with temporary_directory() as temp: - with zipfile.ZipFile(filename) as zf: - zf.extractall(temp) - xml_file = find_file_in_zip(zf, file_type) - xml_tree = ET.parse(os.path.join(temp, xml_file)) + with zipfile.ZipFile(filename) as zf: + xml_file = zf.open(find_file_in_zip(zf)) + xml_tree = ET.parse(xml_file) return xml_tree @@ -54,9 +56,8 @@ def save_into_archive(xml_tree, filename, new_filename=None): # Extract to temp directory with temporary_directory() as temp_path: - file_type = os.path.splitext(filename)[-1].lower() with zipfile.ZipFile(filename) as zf: - xml_file = find_file_in_zip(zf, file_type) + xml_file = find_file_in_zip(zf) zf.extractall(temp_path) # Write the new version of the file to the temp directory xml_tree.write(os.path.join( @@ -72,6 +73,3 @@ def _save_file(container_file, xml_tree, new_filename=None): save_into_archive(xml_tree, container_file, new_filename) else: xml_tree.write(container_file, encoding="utf-8", xml_declaration=True) - - - From 813e07921912f10b19ea7c822d8cea7c3dba9c88 Mon Sep 17 00:00:00 2001 From: T8y8 Date: Thu, 30 Jun 2016 12:12:21 -0700 Subject: [PATCH 5/7] ZipFile.open should be a context manager --- tableaudocumentapi/containerfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tableaudocumentapi/containerfile.py b/tableaudocumentapi/containerfile.py index 30b389f..87cef69 100644 --- a/tableaudocumentapi/containerfile.py +++ b/tableaudocumentapi/containerfile.py @@ -29,8 +29,8 @@ def find_file_in_zip(zip): def get_xml_from_archive(filename): with zipfile.ZipFile(filename) as zf: - xml_file = zf.open(find_file_in_zip(zf)) - xml_tree = ET.parse(xml_file) + with zf.open(find_file_in_zip(zf)) as xml_file: + xml_tree = ET.parse(xml_file) return xml_tree From be4c9c8ecc5d1047b1908dbb288d6d68a0a361d2 Mon Sep 17 00:00:00 2001 From: T8y8 Date: Thu, 30 Jun 2016 12:14:44 -0700 Subject: [PATCH 6/7] Missed a spot with the context manager --- tableaudocumentapi/containerfile.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tableaudocumentapi/containerfile.py b/tableaudocumentapi/containerfile.py index 87cef69..13e08c7 100644 --- a/tableaudocumentapi/containerfile.py +++ b/tableaudocumentapi/containerfile.py @@ -19,9 +19,10 @@ def temporary_directory(*args, **kwargs): def find_file_in_zip(zip): for filename in zip.namelist(): try: - ET.parse(zip.open(filename)).getroot().tag in ( - 'workbook', 'datasource') - return filename + with zip.open(filename) as xml_candidate: + ET.parse(xml_candidate).getroot().tag in ( + 'workbook', 'datasource') + return filename except ET.ParseError: # That's not an XML file by gosh pass From a885f159e239c29fadb5071ad570ee3d31c3cc14 Mon Sep 17 00:00:00 2001 From: T8y8 Date: Thu, 30 Jun 2016 14:02:57 -0700 Subject: [PATCH 7/7] The truth is out there... --- tableaudocumentapi/datasource.py | 8 ++++---- tableaudocumentapi/workbook.py | 8 ++++---- tableaudocumentapi/{containerfile.py => xfile.py} | 0 3 files changed, 8 insertions(+), 8 deletions(-) rename tableaudocumentapi/{containerfile.py => xfile.py} (100%) diff --git a/tableaudocumentapi/datasource.py b/tableaudocumentapi/datasource.py index 33c0c4f..b4fb8ed 100644 --- a/tableaudocumentapi/datasource.py +++ b/tableaudocumentapi/datasource.py @@ -7,7 +7,7 @@ import zipfile import xml.etree.ElementTree as ET -from tableaudocumentapi import Connection, containerfile +from tableaudocumentapi import Connection, xfile class ConnectionParser(object): @@ -61,7 +61,7 @@ def from_file(cls, filename): "Initialize datasource from file (.tds)" if zipfile.is_zipfile(filename): - dsxml = containerfile.get_xml_from_archive(filename).getroot() + dsxml = xfile.get_xml_from_archive(filename).getroot() else: dsxml = ET.parse(filename).getroot() return cls(dsxml, filename) @@ -80,7 +80,7 @@ def save(self): # save the file - containerfile._save_file(self._filename, self._datasourceTree) + xfile._save_file(self._filename, self._datasourceTree) def save_as(self, new_filename): """ @@ -93,7 +93,7 @@ def save_as(self, new_filename): Nothing. """ - containerfile._save_file(self._filename, self._datasourceTree, new_filename) + xfile._save_file(self._filename, self._datasourceTree, new_filename) ########### # name diff --git a/tableaudocumentapi/workbook.py b/tableaudocumentapi/workbook.py index ecfc13d..9e29973 100644 --- a/tableaudocumentapi/workbook.py +++ b/tableaudocumentapi/workbook.py @@ -8,7 +8,7 @@ import xml.etree.ElementTree as ET -from tableaudocumentapi import Datasource, containerfile +from tableaudocumentapi import Datasource, xfile ########################################################################### # @@ -37,7 +37,7 @@ def __init__(self, filename): # Determine if this is a twb or twbx and get the xml root if zipfile.is_zipfile(self._filename): - self._workbookTree = containerfile.get_xml_from_archive( + self._workbookTree = xfile.get_xml_from_archive( self._filename) else: self._workbookTree = ET.parse(self._filename) @@ -74,7 +74,7 @@ def save(self): """ # save the file - containerfile._save_file(self._filename, self._workbookTree) + xfile._save_file(self._filename, self._workbookTree) def save_as(self, new_filename): """ @@ -87,7 +87,7 @@ def save_as(self, new_filename): Nothing. """ - containerfile._save_file( + xfile._save_file( self._filename, self._workbookTree, new_filename) ########################################################################### diff --git a/tableaudocumentapi/containerfile.py b/tableaudocumentapi/xfile.py similarity index 100% rename from tableaudocumentapi/containerfile.py rename to tableaudocumentapi/xfile.py 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