8000 Merge pull request #2 from t8y8/master · Lakkichand/document-api-python@d6c27d8 · GitHub
[go: up one dir, main page]

Skip to content

Commit d6c27d8

Browse files
committed
Merge pull request tableau#2 from t8y8/master
Large PR -- Adding Tests, fixing a few bugs that revealed, and cleaning up small nits
2 parents c88e1b8 + cc94371 commit d6c27d8

File tree

5 files changed

+154
-47
lines changed

5 files changed

+154
-47
lines changed
Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import csv # so we can work with our database list (in a CSV file)
2-
import copy # to make copies
32

43
############################################################
54
# Step 1) Use Workbook object from the Document API
@@ -16,13 +15,11 @@
1615
# create new .twb's with their settings
1716
############################################################
1817
with open('databases.csv') as csvfile:
19-
next(csvfile) # Skip the first line which is our CSV header row
20-
databases = csv.reader(csvfile, delimiter=',', quotechar='"')
18+
databases = csv.DictReader(csvfile, delimiter=',', quotechar='"')
2119
for row in databases:
22-
newWB = copy.copy(sourceWB)
23-
2420
# Set our unique values for this database
25-
newWB.datasources[0].connection.server = row[1] # Server
26-
newWB.datasources[0].connection.dbname = row[2] # Database
27-
newWB.datasources[0].connection.username = row[3] # User
28-
newWB.save_as(row[0] + ' - Superstore' + '.twb') # Save our newly created .twb with the new file name
21+
sourceWB.datasources[0].connection.server = row['Server']
22+
sourceWB.datasources[0].connection.dbname = row['Database']
23+
sourceWB.datasources[0].connection.username = row['User']
24+
# Save our newly created .twb with the new file name
25+
sourceWB.save_as(row['DBFriendlyName'] + ' - Superstore' + '.twb')

Document API/tableaudocumentapi/connection.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
# Connection - A class for writing connections to Tableau files
44
#
55
###############################################################################
6+
7+
68
class Connection(object):
79
"""
810
A class for writing connections to Tableau files.
@@ -36,13 +38,13 @@ def dbname(self):
3638
def dbname(self, value):
3739
"""
3840
Set the connection's database name property.
39-
41+
4042
Args:
4143
value: New name of the database. String.
42-
44+
4345
Returns:
4446
Nothing.
45-
47+
4648
"""
4749
self._dbname = value
4850
self._connectionXML.set('dbname', value)
@@ -58,17 +60,17 @@ def server(self):
5860
def server(self, value):
5961
"""
6062
Set the connection's server property.
61-
63+
6264
Args:
6365
value: New server. String.
64-
66+
6567
Returns:
6668
Nothing.
67-
69+
6870
"""
6971
self._server = value
7072
self._connectionXML.set('server', value)
71-
73+
7274
###########
7375
# username
7476
###########
@@ -80,13 +82,13 @@ def username(self):
8082
def username(self, value):
8183
"""
8284
Set the connection's username property.
83-
85+
8486
Args:
8587
value: New username value. String.
86-
88+
8789
Returns:
8890
Nothing.
89-
91+
9092
"""
9193
self._username = value
9294
self._connectionXML.set('username', value)

Document API/tableaudocumentapi/datasource.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import xml.etree.ElementTree as ET
77
from tableaudocumentapi import Connection
88

9+
910
class Datasource(object):
1011
"""
1112
A class for writing datasources to Tableau files.
@@ -23,23 +24,23 @@ def __init__(self, dsxml):
2324
2425
"""
2526
self._datasourceXML = dsxml
26-
self._name = self._datasourceXML.get('name')
27+
self._name = self._datasourceXML.get('name') or self._datasourceXML.get('formatted-name') # TDS files don't have a name attribute
2728
self._version = self._datasourceXML.get('version')
2829
self._connection = Connection(self._datasourceXML.find('connection'))
29-
30+
3031
@classmethod
3132
def from_file(cls, filename):
3233
"Initialize datasource from file (.tds)"
3334
dsxml = ET.parse(filename).getroot()
3435
return cls(dsxml)
35-
36+
3637
###########
3738
# name
3839
###########
3940
@property
4041
def name(self):
4142
return self._name
42-
43+
4344
###########
4445
# version
4546
###########

Document API/tableaudocumentapi/workbook.py

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import xml.etree.ElementTree as ET
88
from tableaudocumentapi import Datasource
99

10+
1011
class Workbook(object):
1112
"""
1213
A class for writing Tableau workbook files.
@@ -29,13 +30,14 @@ def __init__(self, filename):
2930
self._filename = filename
3031
self._workbookTree = ET.parse(filename)
3132
self._workbookRoot = self._workbookTree.getroot()
32-
33+
3334
# prepare our datasource objects
34-
self._datasources = self._prepare_datasources(self._workbookRoot) #self.workbookRoot.find('datasources')
35+
self._datasources = self._prepare_datasources(
36+
self._workbookRoot) # self.workbookRoot.find('datasources')
3537
else:
3638
print('Invalid file type. Must be .twb or .tds.')
3739
raise Exception()
38-
40+
3941
@classmethod
4042
def from_file(cls, filename):
4143
"Initialize datasource from file (.tds)"
@@ -46,14 +48,14 @@ def from_file(cls, filename):
4648
else:
4749
print('Invalid file type. Must be .twb or .tds.')
4850
raise Exception()
49-
51+
5052
###########
5153
# datasources
5254
###########
5355
@property
5456
def datasources(self):
5557
return self._datasources
56-
58+
5759
###########
5860
# filename
5961
###########
@@ -72,26 +74,26 @@ def save(self):
7274
Nothing.
7375
7476
"""
75-
77+
7678
# save the file
7779
self._workbookTree.write(self._filename)
78-
79-
def save_as(self, value):
80+
81+
def save_as(self, new_filename):
8082
"""
8183
Save our file with the name provided.
8284
8385
Args:
84-
value: New name for the workbook file. String.
86+
new_filename: New name for the workbook file. String.
8587
8688
Returns:
8789
Nothing.
8890
8991
"""
90-
92+
9193
# We have a valid type of input file
92-
if self._is_valid_file(value):
94+
if self._is_valid_file(new_filename):
9395
# save the file
94-
self._workbookTree.write(value)
96+
self._workbookTree.write(new_filename)
9597
else:
9698
print('Invalid file type. Must be .twb or .tds.')
9799
raise Exception()
@@ -103,21 +105,16 @@ def save_as(self, value):
103105
###########################################################################
104106
def _prepare_datasources(self, xmlRoot):
105107
datasources = []
106-
108+
107109
# loop through our datasources and append
108110
for datasource in xmlRoot.find('datasources'):
109111
ds = Datasource(datasource)
110112
datasources.append(ds)
111-
113+
112114
return datasources
113-
114-
def _is_valid_file(self, filename):
115-
valid = 0
115+
116+
@staticmethod
117+
def _is_valid_file(filename):
116118
fileExtension = os.path.splitext(filename)[-1].lower()
117-
118-
if fileExtension == ".twb":
119-
valid = 1
120-
elif fileExtension == ".tds":
121-
valid = 1
122-
123-
return valid
119+
return fileExtension in ('.twb', '.tds')
120+
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import unittest
2+
import io
3+
import os
4+
import xml.etree.ElementTree as ET
5+
6+
from tableaudocumentapi import Workbook, Datasource, Connection
7+
8+
TABLEAU_93_WORKBOOK = '''<?xml version='1.0' encoding='utf-8' ?>
9+
<workbook source-build='9.3.1 (9300.16.0510.0100)' source-platform='mac' version='9.3' xmlns:user='http://www.tableausoftware.com/xml/user'>
10+
<datasources>
11+
<datasource caption='xy (TestV1)' inline='true' name='sqlserver.17u3bqc16tjtxn14e2hxh19tyvpo' version='9.3'>
12+
<connection authentication='sspi' class='sqlserver' dbname='TestV1' odbc-native-protocol='yes' one-time-sql='' server='mssql2012.test.tsi.lan' username=''>
13+
</connection>
14+
</datasource>
15+
</datasources>
16+
</workbook>'''
17+
18+
TABLEAU_93_TDS = '''<?xml version='1.0' encoding='utf-8' ?>
19+
<datasource formatted-name='sqlserver.17u3bqc16tjtxn14e2hxh19tyvpo' inline='true' source-platform='mac' version='9.3' xmlns:user='http://www.tableausoftware.com/xml/user'>
20+
<connection authentication='sspi' class='sqlserver' dbname='TestV1' odbc-native-protocol='yes' one-time-sql='' server='mssql2012.test.tsi.lan' username=''>
21+
</connection>
22+
</datasource>'''
23+
24+
TABLEAU_CONNECTION_XML = ET.fromstring(
25+
'''<connection authentication='sspi' class='sqlserver' dbname='TestV1' odbc-native-protocol='yes' one-time-sql='' server='mssql2012.test.tsi.lan' username=''></connection>''')
26+
27+
28+
class HelperMethodTests(unittest.TestCase):
29+
30+
def test_is_valid_file_with_valid_inputs(self):
31+
self.assertTrue(Workbook._is_valid_file('file1.tds'))
32+
self.assertTrue(Workbook._is_valid_file('file2.twb'))
33+
self.assertTrue(Workbook._is_valid_file('tds.twb'))
34+
35+
def test_is_valid_file_with_invalid_inputs(self):
36+
self.assertFalse(Workbook._is_valid_file(''))
37+
self.assertFalse(Workbook._is_valid_file('file1.tds2'))
38+
self.assertFalse(Workbook._is_valid_file('file2.twb3'))
39+
40+
41+
class ConnectionModelTests(unittest.TestCase):
42+
43+
def setUp(self):
44+
self.connection = TABLEAU_CONNECTION_XML
45+
46+
def test_can_read_attributes_from_connection(self):
47+
conn = Connection(self.connection)
48+
self.assertEqual(conn.dbname, 'TestV1')
49+
self.assertEqual(conn.username, '')
50+
self.assertEqual(conn.server, 'mssql2012.test.tsi.lan')
51+
52+
def test_can_write_attributes_to_connection(self):
53+
conn = Connection(self.connection)
54+
conn.dbname = 'BubblesInMyDrink'
55+
conn.server = 'mssql2014.test.tsi.lan'
56+
self.assertEqual(conn.dbname, 'BubblesInMyDrink')
57+
self.assertEqual(conn.username, '')
58+
self.assertEqual(conn.server, 'mssql2014.test.tsi.lan')
59+
60+
61+
class DatasourceModelTests(unittest.TestCase):
62+
63+
def setUp(self):
64+
self.tds_file = io.FileIO('test.tds', 'w')
65+
self.tds_file.write(TABLEAU_93_TDS.encode('utf8'))
66+
self.tds_file.seek(0)
67+
68+
def tearDown(self):
69+
self.tds_file.close()
70+
os.unlink(self.tds_file.name)
71+
72+
def test_can_extract_datasource_from_file(self):
73+
ds = Datasource.from_file(self.tds_file.name)
74+
self.assertEqual(ds.name, 'sqlserver.17u3bqc16tjtxn14e2hxh19tyvpo')
75+
self.assertEqual(ds.version, '9.3')
76+
77+
def test_can_extract_connection(self):
78+
ds = Datasource.from_file(self.tds_file.name)
79+
self.assertIsInstance(ds.connection, Connection)
80+
81+
82+
class WorkbookModelTests(unittest.TestCase):
83+
84+
def setUp(self):
85+
self.workbook_file = io.FileIO('test.twb', 'w')
86+
self.workbook_file.write(TABLEAU_93_WORKBOOK.encode('utf8'))
87+
self.workbook_file.seek(0)
88+
89+
def tearDown(self):
90+
self.workbook_file.close()
91+
os.unlink(self.workbook_file.name)
92+
93+
def test_can_extract_datasource(self):
94+
wb = Workbook(self.workbook_file.name)
95+
self.assertEqual(len(wb.datasources), 1)
96+
self.assertIsInstance(wb.datasources[0], Datasource)
97+
self.assertEqual(wb.datasources[0].name,
98+
'sqlserver.17u3bqc16tjtxn14e2hxh19tyvpo')
99+
100+
def test_can_update_datasource_connection_and_save(self):
101+
original_wb = Workbook(self.workbook_file.name)
102+
original_wb.datasources[0].connection.dbname = 'newdb.test.tsi.lan'
103+
original_wb.save()
104+
105+
new_wb = Workbook(self.workbook_file.name)
106+
self.assertEqual(new_wb.datasources[0].connection.dbname, 'newdb.test.tsi.lan')
107+
108+
109+
if __name__ == '__main__':
110+
unittest.main()

0 commit comments

Comments
 (0)
0