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()