1- ###############################################################################
2- #
3- # Datasource - A class for writing datasources to Tableau files
4- #
5- ###############################################################################
61import collections
72import itertools
83import xml .etree .ElementTree as ET
1611
1712########
1813# This is needed in order to determine if something is a string or not. It is necessary because
19- # of differences between python2 (basestring) and python3 (str). If python2 support is every
14+ # of differences between python2 (basestring) and python3 (str). If python2 support is ever
2015# dropped, remove this and change the basestring references below to str
2116try :
2217 basestring
@@ -35,7 +30,7 @@ def _get_metadata_xml_for_field(root_xml, field_name):
3530
3631
3732def _is_used_by_worksheet (names , field ):
38- return any (( y for y in names if y in field .worksheets ) )
33+ return any (y for y in names if y in field .worksheets )
3934
4035
4136class FieldDictionary (MultiLookupDict ):
@@ -87,27 +82,32 @@ def base36encode(number):
8782 return sign + base36
8883
8984
90- def make_unique_name (dbclass ):
85+ def _make_unique_name (dbclass ):
9186 rand_part = base36encode (uuid4 ().int )
9287 name = dbclass + '.' + rand_part
9388 return name
9489
9590
9691class ConnectionParser (object ):
92+ """Parser for detecting and extracting connections from differing Tableau file formats."""
9793
9894 def __init__ (self , datasource_xml , version ):
9995 self ._dsxml = datasource_xml
10096 self ._dsversion = version
10197
10298 def _extract_federated_connections (self ):
10399 connections = list (map (Connection , self ._dsxml .findall ('.//named-connections/named-connection/*' )))
100+ # 'sqlproxy' connections (Tableau Server Connections) are not embedded into named-connection elements
101+ # extract them manually for now
104102 connections .extend (map (Connection , self ._dsxml .findall ("./connection[@class='sqlproxy']" )))
105103 return connections
106104
107105 def _extract_legacy_connection (self ):
108106 return list (map (Connection , self ._dsxml .findall ('connection' )))
109107
110108 def get_connections (self ):
109+ """Find and return all connections based on file format version."""
110+
111111 if float (self ._dsversion ) < 10 :
112112 connections = self ._extract_legacy_connection ()
113113 else :
@@ -116,16 +116,11 @@ def get_connections(self):
116116
117117
118118class Datasource (object ):
119- """
120- A class for writing datasources to Tableau files.
119+ """A class representing Tableau Data Sources, embedded in workbook files or
120+ in TDS files.
121121
122122 """
123123
124- ###########################################################################
125- #
126- # Public API.
127- #
128- ###########################################################################
129124 def __init__ (self , dsxml , filename = None ):
130125 """
131126 Constructor. Default is to create datasource from xml.
@@ -145,21 +140,23 @@ def __init__(self, dsxml, filename=None):
145140
146141 @classmethod
147142 def from_file (cls , filename ):
148- """Initialize datasource from file (.tds)"""
143+ """Initialize datasource from file (.tds ot .tdsx )"""
149144
150- dsxml = xml_open (filename , cls . __name__ . lower () ).getroot ()
145+ dsxml = xml_open (filename , 'datasource' ).getroot ()
151146 return cls (dsxml , filename )
152147
153148 @classmethod
154149 def from_connections (cls , caption , connections ):
150+ """Create a new Data Source give a list of Connections."""
151+
155152 root = ET .Element ('datasource' , caption = caption , version = '10.0' , inline = 'true' )
156153 outer_connection = ET .SubElement (root , 'connection' )
157154 outer_connection .set ('class' , 'federated' )
158155 named_conns = ET .SubElement (outer_connection , 'named-connections' )
159156 for conn in connections :
160157 nc = ET .SubElement (named_conns ,
161158 'named-connection' ,
162- name = make_unique_name (conn .dbclass ),
159+ name = _make_unique_name (conn .dbclass ),
163160 caption = conn .server )
164161 nc .append (conn ._connectionXML )
165162 return cls (root )
@@ -194,16 +191,10 @@ def save_as(self, new_filename):
194191
195192 xfile ._save_file (self ._filename , self ._datasourceTree , new_filename )
196193
197- ###########
198- # name
199- ###########
200194 @property
201195 def name (self ):
202196 return self ._name
203197
204- ###########
205- # version
206- ###########
207198 @property
208199 def version (self ):
209200 return self ._version
@@ -222,9 +213,6 @@ def caption(self):
222213 del self ._datasourceXML .attrib ['caption' ]
223214 self ._caption = ''
224215
225- ###########
226- # connections
227- ###########
228216 @property
229217 def connections (self ):
230218 return self ._connections
@@ -234,16 +222,15 @@ def clear_repository_location(self):
234222 if tag is not None :
235223 self ._datasourceXML .remove (tag )
236224
237- ###########
238- # fields
239- ###########
240225 @property
241226 def fields (self ):
242227 if not self ._fields :
243228 self ._fields = self ._get_all_fields ()
244229 return self ._fields
245230
246231 def _get_all_fields (self ):
232+ # Some columns are represented by `column` tags and others as `metadata-record` tags
233+ # Find them all and chain them into one dictionary
247234 column_field_objects = self ._get_column_objects ()
248235 existing_column_fields = [x .id for x in column_field_objects ]
249236 metadata_only_field_objects = (x for x in self ._get_metadata_objects () if x .id not in existing_column_fields )
0 commit comments