10000 Create New Connections per #68 (#69) · randyoswald/document-api-python@8b8f351 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8b8f351

Browse files
authored
Create New Connections per tableau#68 (tableau#69)
Enable a prototypical API for creating new connections and datasources from scratch. This uses ET to do XML manipulation, and is hard coded to "10.0" style integrated connections. This will go away with the new editor/physical/logical model work underway, but it fixes a bug and will let us play around for now.
1 parent 5fe8c9b commit 8b8f351

File tree

4 files changed

+83
-1
lines changed

4 files changed

+83
-1
lines changed

tableaudocumentapi/connection.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Connection - A class for writing connections to Tableau files
44
#
55
###############################################################################
6+
import xml.etree.ElementTree as ET
67
from tableaudocumentapi.dbclass import is_valid_dbclass
78

89

@@ -33,6 +34,17 @@ def __init__(self, connxml):
3334
def __repr__(self):
3435
return "'<Connection server='{}' dbname='{}' @ {}>'".format(self._server, self._dbname, hex(id(self)))
3536

37+
@classmethod
38+
def from_attributes(cls, server, dbname, username, dbclass, authentication=''):
39+
root = ET.Element('connection', authentication=authentication)
40+
xml = cls(root)
41+
xml.server = server
42+
xml.dbname = dbname
43+
xml.username = username
44+
xml.dbclass = dbclass
45+
46+
return xml
47+
3648
###########
3749
# dbname
3850
###########
@@ -120,4 +132,4 @@ def dbclass(self, value):
120132
raise AttributeError("'{}' is not a valid database type".format(value))
121133

122134
self._class = value
123-
self._connectionXML.set('dbclass', value)
135+
self._connectionXML.set('class', value)

tableaudocumentapi/datasource.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
###############################################################################
66
import collections
77
import itertools
8+
import random
89
import xml.etree.ElementTree as ET
910
import xml.sax.saxutils as sax
11+
from uuid import uuid4
1012

1113
from tableaudocumentapi import Connection, xfile
1214
from tableaudocumentapi import Field
@@ -38,6 +40,7 @@ def _is_used_by_worksheet(names, field):
3840

3941

4042
class FieldDictionary(MultiLookupDict):
43+
4144
def used_by_sheet(self, name):
4245
# If we pass in a string, no need to get complicated, just check to see if name is in
4346
# the field's list of worksheets
@@ -63,7 +66,36 @@ def _column_object_from_metadata_xml(metadata_xml):
6366
return _ColumnObjectReturnTuple(field_object.id, field_object)
6467

6568

69+
def base36encode(number):
70+
"""Converts an integer into a base36 string."""
71+
72+
ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz"
73+
74+
base36 = ''
75+
sign = ''
76+
77+
if number < 0:
78+
sign = '-'
79+
number = -number
80+
81+
if 0 <= number < len(ALPHABET):
82+
return sign + ALPHABET[number]
83+
84+
while number != 0:
85+
number, i = divmod(number, len(ALPHABET))
86+
base36 = ALPHABET[i] + base36
87+
88+
return sign + base36
89+
90+
91+
def make_unique_name(dbclass):
92+
rand_part = base36encode(uuid4().int)
93+
name = dbclass + '.' + rand_part
94+
return name
95+
96+
6697
class ConnectionParser(object):
98+
6799
def __init__(self, datasource_xml, version):
68100
self._dsxml = datasource_xml
69101
self._dsversion = version
@@ -116,6 +148,20 @@ def from_file(cls, filename):
116148
dsxml = xml_open(filename, cls.__name__.lower()).getroot()
117149
return cls(dsxml, filename)
118150

151+
@classmethod
152+
def from_connections(cls, caption, connections):
153+
root = ET.Element('datasource', caption=caption, version='10.0', inline='true')
154+
outer_connection = ET.SubElement(root, 'connection')
155+
outer_connection.set('class', 'federated')
156+
named_conns = ET.SubElement(outer_connection, 'named-connections')
157+
for conn in connections:
158+
nc = ET.SubElement(named_conns,
159+
'named-connection',
160+
name=make_unique_name(conn.dbclass),
161+
caption=conn.server)
162+
nc.append(conn._connectionXML)
163+
return cls(root)
164+
119165
def save(self):
120166
"""
121167
Call finalization code and save file.
@@ -143,6 +189,7 @@ def save_as(self, new_filename):
143189
Nothing.
144190
145191
"""
192+
146193
xfile._save_file(self._filename, self._datasourceTree, new_filename)
147194

148195
###########

tableaudocumentapi/xfile.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ def save_into_archive(xml_tree, filename, new_filename=None):
104104

105105

106106
def _save_file(container_file, xml_tree, new_filename=None):
107+
108+
if container_file is None:
109+
container_file = new_filename
110+
107111
if zipfile.is_zipfile(container_file):
108112
save_into_archive(xml_tree, container_file, new_filename)
109113
else:

test/bvt.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,25 @@ def test_bad_dbclass_rasies_attribute_error(self):
7474
with self.assertRaises(AttributeError):
7575
conn.dbclass = 'NotReal'
7676

77+
def test_can_create_connection_from_scratch(self):
78+
conn = Connection.from_attributes(
79+
server='a', dbname='b', username='c', dbclass='mysql', authentication='d')
80+
self.assertEqual(conn.server, 'a')
81+
self.assertEqual(conn.dbname, 'b')
82+
self.assertEqual(conn.username, 'c')
83+
self.assertEqual(conn.dbclass, 'mysql')
84+
self.assertEqual(conn.authentication, 'd')
85+
86+
def test_can_create_datasource_from_connections(self):
87+
conn1 = Connection.from_attributes(
88+
server='a', dbname='b', username='c', dbclass='mysql', authentication='d')
89+
conn2 = Connection.from_attributes(
90+
server='1', dbname='2', username='3', dbclass='mysql', authentication='7')
91+
ds = Datasource.from_connections('test', connections=[conn1, conn2])
92+
93+
self.assertEqual(ds.connections[0].server, 'a')
94+
self.assertEqual(ds.connections[1].server, '1')
95+
7796

7897
class DatasourceModelTests(unittest.TestCase):
7998

0 commit comments

Comments
 (0)
0