Skip to content

Commit

Permalink
Removed the old original TableauFile class, which is now superceded b…
Browse files Browse the repository at this point in the history
…y the current objects and the TableauFileOpener class
  • Loading branch information
Bryant Howell committed Dec 6, 2019
1 parent 99c1e58 commit 8d5f04c
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 255 deletions.
9 changes: 9 additions & 0 deletions examples/tableau_documents_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

def live_db_connection_changes():
t_file: TDS = TableauFileManager.open(filename='Live PostgreSQL.tds')
if isinstance(t_file, DatasourceFileInterface):
print('Yeah I got data sources')
print(t_file.file_type)
# print(t_file.tableau_xml_file)
print(t_file.tableau_document)
Expand All @@ -23,6 +25,7 @@ def live_db_connection_changes():
for ds in t_file_2.datasources:
print(ds)
print(ds.connections)
t_file_2.save_new_file('New TDSX')

t_file_3: TWBX = TableauFileManager.open(filename='First Workbook Revision.twbx')
print(t_file_3.file_type)
Expand All @@ -32,6 +35,12 @@ def live_db_connection_changes():
for ds in t_file_3.datasources:
print(ds)
print(ds.connections)
print(ds.ds_name)
print(ds.published)
for conn in ds.connections:
print(conn.connection_name)
print(conn.connection_type)
t_file_3.save_new_file('New TWBX')

def flat_file_connection_changes():
pass
Expand Down
269 changes: 14 additions & 255 deletions tableau_documents/tableau_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,279 +17,37 @@
# from tableau_documents.tableau_document import TableauDocument


class TableauFile(LoggingMethods):
def __init__(self, filename: str, logger_obj: Optional[Logger] = None,
create_new: bool = False, ds_version: Optional[str] = '10'):
self.logger: Optional[Logger] = logger_obj
self.log('TableauFile initializing for {}'.format(filename))
self.packaged_file: bool = False
self.packaged_filename: Optional[str] = None
self.tableau_xml_file: Optional[ET.Element] = None
#self._tableau_document: Optional[TableauDocument] = None
self._original_file_type: Optional[str] = None
self._final_file_type: Optional[str] = None
self.other_files: List[str] = []
self.temp_filename: Optional[str] = None
self.orig_filename: str = filename
self._document_type = None

if filename is None:
# Assume we start as TDS when building from scratch
self._original_file_type = 'tds'
self._final_file_type = 'tds'
if filename.lower().find('.tdsx') != -1:
self._original_file_type = 'tdsx'
self._final_file_type = 'tdsx'
self.packaged_file = True
elif filename.lower().find('.twbx') != -1:
self._original_file_type = 'twbx'
self._final_file_type = 'twbx'
self.packaged_file = True
elif filename.lower().find('.twb') != -1:
self._original_file_type = 'twb'
self._final_file_type = 'twb'
elif filename.lower().find('.tds') != -1:
self._original_file_type = 'tds'
self._final_file_type = 'tds'
elif filename.lower().find('tfl') != -1:
self._original_file_type = 'tfl'
self._final_file_type = 'tfl'
else:
raise InvalidOptionException('Must open a Tableau file with ending of tds, tdsx, twb, twbx, tfl')
try:
if create_new is True:
if self._original_file_type in ['tds', 'tdsx']:
self._tableau_document = TableauDatasource(None, logger_obj, ds_version=ds_version)
else:
raise InvalidOptionException('Cannot create a new TWB or TWBX from scratch currently')
else:
file_obj = open(filename, 'rb')
self.log('File type is {}'.format(self.file_type))
# Extract the TWB or TDS file to disk, then create a sub TableauFile
if self.file_type in ['twbx', 'tdsx']:
self.zf = zipfile.ZipFile(file_obj)
# Ignore anything in the subdirectories
for name in self.zf.namelist():
if name.find('/') == -1:
if name.endswith('.tds'):
self.log('Detected a TDS file in archive, saving temporary file')
self.packaged_filename = os.path.basename(self.zf.extract(name))
elif name.endswith('.twb'):
self.log('Detected a TWB file in archive, saving temporary file')
self.packaged_filename = os.path.basename(self.zf.extract(name))
else:
self.other_files.append(name)

self.tableau_xml_file = TableauFile(self.packaged_filename, self.logger)
self._tableau_document = self.tableau_xml_file._tableau_document
elif self.file_type == 'twb':
self._tableau_document = TableauWorkbook(filename, self.logger)
elif self.file_type == 'tds':
# Here we throw out metadata-records even when opening a workbook from disk, they take up space
# and are recreate automatically. Very similar to what we do in initialization of TableauWorkbook
o_ds_fh = codecs.open(filename, 'r', encoding='utf-8')
ds_fh = codecs.open('temp_file.txt', 'w', encoding='utf-8')
self.temp_filename = 'temp_file.txt'
metadata_flag = None
for line in o_ds_fh:
# Grab the datasources

if line.find("<metadata-records") != -1 and metadata_flag is None:
metadata_flag = True
if metadata_flag is not True:
ds_fh.write(line)
if line.find("</metadata-records") != -1 and metadata_flag is True:
metadata_flag = False
o_ds_fh.close()

ds_fh.close()
utf8_parser = ET.XMLParser(encoding='utf-8')

ds_xml = ET.parse('temp_file.txt', parser=utf8_parser)

self._tableau_document = TableauDatasource(ds_xml.getroot(), self.logger)
self.xml_name = None
file_obj.close()
except IOError:
self.log("Cannot open file {}".format(filename))
raise

@property
def tableau_document(self) -> Union[TableauDatasource, TableauWorkbook]:
return self._tableau_document

@property
def file_type(self) -> str:
return self._original_file_type

@property
def datasources(self) -> List[TableauDatasource]:
if self._tableau_document.document_type == 'workbook':
return self._tableau_document.datasources
elif self._tableau_document.document_type == 'datasource':
return [self._tableau_document, ]
else:
return []

# Appropriate extension added if needed
def save_new_file(self, new_filename_no_extension: str, data_file_replacement_map: Optional[Dict],
new_data_files_map: Optional[Dict]) -> str:
self.start_log_block()
new_filename = new_filename_no_extension.split('.')[0] # simple algorithm to kill extension
if new_filename is None:
new_filename = new_filename_no_extension
self.log('Saving to a file with new filename {}'.format(new_filename))
# Change filetype if there are new extracts to add
for ds in self.datasources:
if ds.tde_filename is not None or new_data_files_map is not None:
if self.file_type == 'twb':
self._final_file_type = 'twbx'
self.packaged_filename = "{}.twb".format(new_filename)
self.log('Final filetype will be TWBX')
break
if self.file_type == 'tds' or new_data_files_map is not None:
self._final_file_type = 'tdsx'
self.packaged_filename = "{}.tds".format(new_filename)
self.log('Final filetype will be TDSX')
break

if self._final_file_type in ['twbx', 'tdsx']:
initial_save_filename = "{}.{}".format(new_filename, self._final_file_type)
# Make sure you don't overwrite the existing original file
files = list(filter(os.path.isfile, os.listdir(os.curdir))) # files only
save_filename = initial_save_filename
file_versions = 1
while save_filename in files:
name_parts = initial_save_filename.split(".")
save_filename = "{} ({}).{}".format(name_parts[0],file_versions, name_parts[1])
file_versions += 1
new_zf = zipfile.ZipFile(save_filename, 'w', zipfile.ZIP_DEFLATED)
# Save the object down
self.log('Creating temporary XML file {}'.format(self.packaged_filename))
# Have to extract the original TWB to temporary file
self.log('Creating from original file {}'.format(self.orig_filename))
if self._original_file_type == 'twbx':
file_obj = open(self.orig_filename, 'rb')
o_zf = zipfile.ZipFile(file_obj)
o_zf.extract(self.tableau_document.twb_filename)
shutil.copy(self.tableau_document.twb_filename, 'temp.twb')
os.remove(self.tableau_document.twb_filename)
self.tableau_document.twb_filename = 'temp.twb'
file_obj.close()

# Call to the tableau_document object to write the Tableau XML
self.tableau_document.save_file(self.packaged_filename)
new_zf.write(self.packaged_filename)
self.log('Removing file {}'.format(self.packaged_filename))
os.remove(self.packaged_filename)

if self._original_file_type == 'twbx':
os.remove('temp.twb')
self.log('Removed file temp.twb'.format(self.packaged_filename))

temp_directories_to_remove = {}

if len(self.other_files) > 0:
file_obj = open(self.orig_filename, 'rb')
o_zf = zipfile.ZipFile(file_obj)

# Find datasources with new extracts, and skip their files
extracts_to_skip = []
for ds in self.tableau_document.datasources:
if ds.existing_tde_filename is not None and ds.tde_filename is not None:
extracts_to_skip.append(ds.existing_tde_filename)

for filename in self.other_files:
self.log('Looking into additional files: {}'.format(filename))

# Skip extracts listed for replacement
if filename in extracts_to_skip:
self.log('File {} is from an extract that has been replaced, skipping'.format(filename))
continue

# If file is listed in the data_file_replacement_map, write data from the mapped in file
if data_file_replacement_map and filename in data_file_replacement_map:
new_zf.write(data_file_replacement_map[filename], "/" + filename)
# Delete from the data_file_replacement_map to reduce down to end
del data_file_replacement_map[filename]
else:
o_zf.extract(filename)
new_zf.write(filename)
os.remove(filename)
self.log('Removed file {}'.format(filename))
lowest_level = filename.split('/')
temp_directories_to_remove[lowest_level[0]] = True
file_obj.close()

# Loop through remaining files in data_file_replacement_map to just add
for filename in new_data_files_map:
new_zf.write(new_data_files_map[filename], "/" + filename)

# If new extract, write that file
for ds in self.tableau_document.datasources:
if ds.tde_filename is not None:
new_zf.write(ds.tde_filename, '/Data/Datasources/{}'.format(ds.tde_filename))
os.remove(ds.tde_filename)
self.log('Removed file {}'.format(ds.tde_filename))

# Cleanup all the temporary directories
for directory in temp_directories_to_remove:
self.log('Removing directory {}'.format(directory))
try:
shutil.rmtree(directory)
except OSError as e:
# Just means that directory didn't exist for some reason, probably a swap occurred
pass
new_zf.close()

return save_filename
else:
initial_save_filename = "{}.{}".format(new_filename_no_extension, self.file_type)
# Make sure you don't overwrite the existing original file
files = list(filter(os.path.isfile, os.listdir(os.curdir))) # files only
save_filename = initial_save_filename
file_versions = 1
while save_filename in files:
name_parts = initial_save_filename.split(".")
save_filename = "{} ({}).{}".format(name_parts[0],file_versions, name_parts[1])
file_versions += 1

self.tableau_document.save_file(save_filename)
return save_filename


# Hyper files are not considered in this situation as they are binary and generated a different way

# This is a helper class with factory and static methods
class TableauFileManager(LoggingMethods):
class TableauFileManager():

@staticmethod
def open(filename: str, logger_obj: Optional[Logger] = None):
# logger_obj.log('Opening {}'.format(filename))
# Packaged (X) files must come first because they are supersets
if filename.lower().find('.tdsx') != -1:

return TDSX(filename=filename, logger_obj=logger_obj)
elif filename.lower().find('.twbx') != -1:

elif filename.lower().find('.twbx') != -1:
return TWBX(filename=filename, logger_obj=logger_obj)
elif filename.lower().find('.tflx') != -1:

elif filename.lower().find('.tflx') != -1:
return TFLX(filename=filename, logger_obj=logger_obj)

elif filename.lower().find('.twb') != -1:

return TWB(filename=filename, logger_obj=logger_obj)
elif filename.lower().find('.tds') != -1:

elif filename.lower().find('.tds') != -1:
return TDS(filename=filename, logger_obj=logger_obj)
elif filename.lower().find('tfl') != -1:

elif filename.lower().find('tfl') != -1:
return TFL(filename=filename, logger_obj=logger_obj)

else:
raise InvalidOptionException('Must open a Tableau file with ending of tds, tdsx, twb, twbx, tfl, tflx')



# For saving a TWB or TDS (or other) from a document object. Actually should be
@staticmethod
def create_new_tds(tableau_datasource: TableauDatasource):
Expand All @@ -307,7 +65,8 @@ def create_new_twb(tableau_workbook: TableauWorkbook):
def create_new_twbx(tableau_workbook: TableauWorkbook):
pass

class DatasourceMethods(ABC):

class DatasourceFileInterface(ABC):
@property
@abstractmethod
def datasources(self) -> List[TableauDatasource]:
Expand Down Expand Up @@ -362,7 +121,7 @@ def save_new_file(self, new_filename_no_extension: str) -> str:
# data source attributes is the primary use case for this library
# That said, there should probably be a mechanism for work on aspects of the workbook that we feel are useful to modify
# and also possibly a full String Replace implemented for translation purposes
class TWB(DatasourceMethods, TableauXmlFile):
class TWB(DatasourceFileInterface, TableauXmlFile):
def __init__(self, filename: str, logger_obj: Optional[Logger] = None):
TableauXmlFile.__init__(self, filename=filename, logger_obj=logger_obj)
self._open_and_initialize(filename=filename, logger_obj=logger_obj)
Expand Down Expand Up @@ -460,7 +219,7 @@ def save_new_file(self, filename_no_extension: str, save_to_directory: Optional[
self.end_log_block()
raise

class TDS(DatasourceMethods, TableauXmlFile):
class TDS(DatasourceFileInterface, TableauXmlFile):
def __init__(self, filename: str, logger_obj: Optional[Logger] = None):
TableauXmlFile.__init__(self, filename=filename, logger_obj=logger_obj)
self._open_and_initialize(filename=filename, logger_obj=logger_obj)
Expand Down Expand Up @@ -594,7 +353,7 @@ def save_new_file(self, new_filename_no_extension: str) -> str:
pass


class TDSX(DatasourceMethods, TableauPackagedFile):
class TDSX(DatasourceFileInterface, TableauPackagedFile):

def _open_file_and_intialize(self, filename):
try:
Expand Down Expand Up @@ -702,7 +461,7 @@ def save_new_file(self, new_filename_no_extension: str):
return save_filename


class TWBX(DatasourceMethods, TableauPackagedFile):
class TWBX(DatasourceFileInterface, TableauPackagedFile):

#self._open_file_and_intialize(filename=filename)

Expand Down Expand Up @@ -786,7 +545,7 @@ def save_new_file(self, new_filename_no_extension: str):
self.tableau_xml_file.save_new_file(filename_no_extension=self.packaged_filename)
new_zf.write(self.packaged_filename)

# Clean up the new file and the temp file
# Clean up the new file and the temp file
os.remove(self.packaged_filename)
os.remove(temp_wb_filename)

Expand Down

0 comments on commit 8d5f04c

Please sign in to comment.