diff --git a/Document API/Examples/Replicate Workbook/replicateWorkbook.py b/Document API/Examples/Replicate Workbook/replicateWorkbook.py index 6e60e95..5fc5227 100644 --- a/Document API/Examples/Replicate Workbook/replicateWorkbook.py +++ b/Document API/Examples/Replicate Workbook/replicateWorkbook.py @@ -1,5 +1,4 @@ import csv # so we can work with our database list (in a CSV file) -import copy # to make copies ############################################################ # Step 1) Use Workbook object from the Document API @@ -16,13 +15,11 @@ # create new .twb's with their settings ############################################################ with open('databases.csv') as csvfile: - next(csvfile) # Skip the first line which is our CSV header row - databases = csv.reader(csvfile, delimiter=',', quotechar='"') + databases = csv.DictReader(csvfile, delimiter=',', quotechar='"') for row in databases: - newWB = copy.copy(sourceWB) - # Set our unique values for this database - newWB.datasources[0].connection.server = row[1] # Server - newWB.datasources[0].connection.dbname = row[2] # Database - newWB.datasources[0].connection.username = row[3] # User - newWB.save_as(row[0] + ' - Superstore' + '.twb') # Save our newly created .twb with the new file name + sourceWB.datasources[0].connection.server = row['Server'] + sourceWB.datasources[0].connection.dbname = row['Database'] + sourceWB.datasources[0].connection.username = row['User'] + # Save our newly created .twb with the new file name + sourceWB.save_as(row['DBFriendlyName'] + ' - Superstore' + '.twb') diff --git a/Document API/tableaudocumentapi/connection.py b/Document API/tableaudocumentapi/connection.py index 4f703ee..10096dd 100644 --- a/Document API/tableaudocumentapi/connection.py +++ b/Document API/tableaudocumentapi/connection.py @@ -3,6 +3,8 @@ # Connection - A class for writing connections to Tableau files # ############################################################################### + + class Connection(object): """ A class for writing connections to Tableau files. @@ -36,13 +38,13 @@ def dbname(self): def dbname(self, value): """ Set the connection's database name property. - + Args: value: New name of the database. String. - + Returns: Nothing. - + """ self._dbname = value self._connectionXML.set('dbname', value) @@ -58,17 +60,17 @@ def server(self): def server(self, value): """ Set the connection's server property. - + Args: value: New server. String. - + Returns: Nothing. - + """ self._server = value self._connectionXML.set('server', value) - + ########### # username ########### @@ -80,13 +82,13 @@ def username(self): def username(self, value): """ Set the connection's username property. - + Args: value: New username value. String. - + Returns: Nothing. - + """ self._username = value self._connectionXML.set('username', value) diff --git a/Document API/tableaudocumentapi/datasource.py b/Document API/tableaudocumentapi/datasource.py index 60dc2c0..28f735f 100644 --- a/Document API/tableaudocumentapi/datasource.py +++ b/Document API/tableaudocumentapi/datasource.py @@ -6,6 +6,7 @@ import xml.etree.ElementTree as ET from tableaudocumentapi import Connection + class Datasource(object): """ A class for writing datasources to Tableau files. @@ -23,23 +24,23 @@ def __init__(self, dsxml): """ self._datasourceXML = dsxml - self._name = self._datasourceXML.get('name') + self._name = self._datasourceXML.get('name') or self._datasourceXML.get('formatted-name') # TDS files don't have a name attribute self._version = self._datasourceXML.get('version') self._connection = Connection(self._datasourceXML.find('connection')) - + @classmethod def from_file(cls, filename): "Initialize datasource from file (.tds)" dsxml = ET.parse(filename).getroot() return cls(dsxml) - + ########### # name ########### @property def name(self): return self._name - + ########### # version ########### diff --git a/Document API/tableaudocumentapi/workbook.py b/Document API/tableaudocumentapi/workbook.py index 55bd8ee..e2e0c75 100644 --- a/Document API/tableaudocumentapi/workbook.py +++ b/Document API/tableaudocumentapi/workbook.py @@ -7,6 +7,7 @@ import xml.etree.ElementTree as ET from tableaudocumentapi import Datasource + class Workbook(object): """ A class for writing Tableau workbook files. @@ -29,13 +30,14 @@ def __init__(self, filename): 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') + 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)" @@ -46,14 +48,14 @@ def from_file(cls, filename): else: print('Invalid file type. Must be .twb or .tds.') raise Exception() - + ########### # datasources ########### @property def datasources(self): return self._datasources - + ########### # filename ########### @@ -72,26 +74,26 @@ def save(self): Nothing. """ - + # save the file self._workbookTree.write(self._filename) - - def save_as(self, value): + + def save_as(self, new_filename): """ Save our file with the name provided. Args: - value: New name for the workbook file. String. + new_filename: New name for the workbook file. String. Returns: Nothing. """ - + # We have a valid type of input file - if self._is_valid_file(value): + if self._is_valid_file(new_filename): # save the file - self._workbookTree.write(value) + self._workbookTree.write(new_filename) else: print('Invalid file type. Must be .twb or .tds.') raise Exception() @@ -103,21 +105,16 @@ def save_as(self, value): ########################################################################### def _prepare_datasources(self, xmlRoot): datasources = [] - + # loop through our datasources and append for datasource in xmlRoot.find('datasources'): ds = Datasource(datasource) datasources.append(ds) - + return datasources - - def _is_valid_file(self, filename): - valid = 0 + + @staticmethod + def _is_valid_file(filename): fileExtension = os.path.splitext(filename)[-1].lower() - - if fileExtension == ".twb": - valid = 1 - elif fileExtension == ".tds": - valid = 1 - - return valid + return fileExtension in ('.twb', '.tds') + diff --git a/Document API/test.py b/Document API/test.py new file mode 100644 index 0000000..baf95c3 --- /dev/null +++ b/Document API/test.py @@ -0,0 +1,110 @@ +import unittest +import io +import os +import xml.etree.ElementTree as ET + +from tableaudocumentapi import Workbook, Datasource, Connection + +TABLEAU_93_WORKBOOK = ''' + + + + + + + +''' + +TABLEAU_93_TDS = ''' + + + +''' + +TABLEAU_CONNECTION_XML = ET.fromstring( + '''''') + + +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 ConnectionModelTests(unittest.TestCase): + + def setUp(self): + self.connection = TABLEAU_CONNECTION_XML + + def test_can_read_attributes_from_connection(self): + conn = Connection(self.connection) + self.assertEqual(conn.dbname, 'TestV1') + self.assertEqual(conn.username, '') + self.assertEqual(conn.server, 'mssql2012.test.tsi.lan') + + def test_can_write_attributes_to_connection(self): + conn = Connection(self.connection) + conn.dbname = 'BubblesInMyDrink' + conn.server = 'mssql2014.test.tsi.lan' + self.assertEqual(conn.dbname, 'BubblesInMyDrink') + self.assertEqual(conn.username, '') + self.assertEqual(conn.server, 'mssql2014.test.tsi.lan') + + +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) + + def tearDown(self): + self.tds_file.close() + os.unlink(self.tds_file.name) + + def test_can_extract_datasource_from_file(self): + ds = Datasource.from_file(self.tds_file.name) + self.assertEqual(ds.name, 'sqlserver.17u3bqc16tjtxn14e2hxh19tyvpo') + self.assertEqual(ds.version, '9.3') + + def test_can_extract_connection(self): + ds = Datasource.from_file(self.tds_file.name) + self.assertIsInstance(ds.connection, Connection) + + +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) + + def tearDown(self): + self.workbook_file.close() + os.unlink(self.workbook_file.name) + + def test_can_extract_datasource(self): + wb = Workbook(self.workbook_file.name) + self.assertEqual(len(wb.datasources), 1) + self.assertIsInstance(wb.datasources[0], Datasource) + self.assertEqual(wb.datasources[0].name, + 'sqlserver.17u3bqc16tjtxn14e2hxh19tyvpo') + + def test_can_update_datasource_connection_and_save(self): + original_wb = Workbook(self.workbook_file.name) + original_wb.datasources[0].connection.dbname = 'newdb.test.tsi.lan' + original_wb.save() + + new_wb = Workbook(self.workbook_file.name) + self.assertEqual(new_wb.datasources[0].connection.dbname, 'newdb.test.tsi.lan') + + +if __name__ == '__main__': + unittest.main()