1
1
import functools
2
2
import xml .etree .ElementTree as ET
3
3
4
+ from tableaudocumentapi .property_decorators import argument_is_one_of
4
5
5
6
_ATTRIBUTES = [
6
7
'id' , # Name of the field as specified in the file, usually surrounded by [ ]
@@ -45,12 +46,14 @@ def __init__(self, column_xml=None, metadata_xml=None):
45
46
46
47
if column_xml is not None :
47
48
self ._initialize_from_column_xml (column_xml )
49
+ self ._xml = column_xml
48
50
# This isn't currently never called because of the way we get the data from the xml,
49
51
# but during the refactor, we might need it. This is commented out as a reminder
50
52
# if metadata_xml is not None:
51
53
# self.apply_metadata(metadata_xml)
52
54
53
55
elif metadata_xml is not None :
56
+ self ._xml = metadata_xml
54
57
self ._initialize_from_metadata_xml (metadata_xml )
55
58
56
59
else :
@@ -66,6 +69,16 @@ def _initialize_from_metadata_xml(self, xmldata):
66
69
read_name = metadata_name )
67
70
self .apply_metadata (xmldata )
68
71
72
+ @classmethod
73
+ def create_field_xml (cls , caption , datatype , role , field_type , name ):
74
+ column = ET .Element ('column' )
75
+ column .set ('caption' , caption )
76
+ column .set ('datatype' , datatype )
77
+ column .set ('role' , role )
78
+ column .set ('type' , field_type )
79
+ column .set ('name' , name )
80
+ return column
81
+
69
82
########################################
70
83
# Special Case methods for construction fields from various sources
71
84
# not intended for client use
@@ -116,52 +129,200 @@ def id(self):
116
129
""" Name of the field as specified in the file, usually surrounded by [ ] """
117
130
return self ._id
118
131
132
+ @property
133
+ def xml (self ):
134
+ """ XML representation of the field. """
135
+ return self ._xml
136
+
137
+ ########################################
138
+ # Attribute getters and setters
139
+ ########################################
140
+
119
141
@property
120
142
def caption (self ):
121
143
""" Name of the field as displayed in Tableau unless an aliases is defined """
122
144
return self ._caption
123
145
146
+ @caption .setter
147
+ def caption (self , caption ):
148
+ """ Set the caption of a field
149
+
150
+ Args:
151
+ caption: New caption. String.
152
+
153
+ Returns:
154
+ Nothing.
155
+ """
156
+ self ._caption = caption
157
+ self ._xml .set ('caption' , caption )
158
+
124
159
@property
125
160
def alias (self ):
126
161
""" Name of the field as displayed in Tableau if the default name isn't wanted """
127
162
return self ._alias
128
163
164
+ @alias .setter
165
+ def alias (self , alias ):
166
+ """ Set the alias of a field
167
+
168
+ Args:
169
+ alias: New alias. String.
170
+
171
+ Returns:
172
+ Nothing.
173
+ """
174
+ self ._alias = alias
175
+ self ._xml .set ('alias' , alias )
176
+
129
177
@property
130
178
def datatype (self ):
131
179
""" Type of the field within Tableau (string, integer, etc) """
132
180
return self ._datatype
133
181
182
+ @datatype .setter
183
+ @argument_is_one_of ('string' , 'integer' , 'date' , 'boolean' )
184
+ def datatype (self , datatype ):
185
+ """ Set the datatype of a field
186
+
187
+ Args:
188
+ datatype: New datatype. String.
189
+
190
+ Returns:
191
+ Nothing.
192
+ """
193
+ self ._datatype = datatype
194
+ self ._xml .set ('datatype' , datatype )
195
+
134
196
@property
135
197
def role (self ):
136
198
""" Dimension or Measure """
137
199
return self ._role
138
200
201
+ @role .setter
202
+ @argument_is_one_of ('dimension' , 'measure' )
203
+ def role (self , role ):
204
+ """ Set the role of a field
205
+
206
+ Args:
207
+ role: New role. String.
208
+
209
+ Returns:
210
+ Nothing.
211
+ """
212
+ self ._role = role
213
+ self ._xml .set ('role' , role )
214
+
215
+ @property
216
+ def type (self ):
217
+ """ Dimension or Measure """
218
+ return self ._type
219
+
220
+ @type .setter
221
+ @argument_is_one_of ('quantitative' , 'ordinal' , 'nominal' )
222
+ def type (self , field_type ):
223
+ """ Set the type of a field
224
+
225
+ Args:
226
+ field_type: New type. String.
227
+
228
+ Returns:
229
+ Nothing.
230
+ """
231
+ self ._type = field_type
232
+ self ._xml .set ('type' , field_type )
233
+
234
+ ########################################
235
+ # Aliases getter and setter
236
+ # Those are NOT the 'alias' field of the column,
237
+ # but instead the key-value aliases in its child elements
238
+ ########################################
239
+
240
+ def add_alias (self , key , value ):
241
+ """ Add an alias for a given display value.
242
+
243
+ Args:
244
+ key: The data value to map. Example: "1". String.
245
+ value: The display value for the key. Example: "True". String.
246
+ Returns:
247
+ Nothing.
248
+ """
249
+
250
+ # determine whether there already is an aliases-tag
251
+ aliases = self ._xml .find ('aliases' )
252
+ # and create it if there isn't
253
+ if not aliases :
254
+ aliases = ET .Element ('aliases' )
255
+ self ._xml .append (aliases )
256
+
257
+ # find out if an alias with this key already exists and use it
258
+ existing_alias = [tag for tag in aliases .findall ('alias' ) if tag .get ('key' ) == key ]
259
+ # if not, create a new ET.Element
260
+ alias = existing_alias [0 ] if existing_alias else ET .Element ('alias' )
261
+
262
+ alias .set ('key' , key )
263
+ alias .set ('value' , value )
264
+ if not existing_alias :
265
+ aliases .append (alias )
266
+
267
+ @property
268
+ def aliases (self ):
269
+ """ Returns all aliases that are registered under this field.
270
+
271
10000
+ Returns:
272
+ Key-value mappings of all registered aliases. Dict.
273
+ """
274
+ aliases_tag = self ._xml .find ('aliases' ) or []
275
+ return {a .get ('key' , 'None' ): a .get ('value' , 'None' ) for a in list (aliases_tag )}
276
+
277
+ ########################################
278
+ # Attribute getters
279
+ ########################################
280
+
139
281
@property
140
282
def is_quantitative (self ):
141
283
""" A dependent value, usually a measure of something
142
284
143
285
e.g. Profit, Gross Sales """
144
- return self ._type == 'quantitative'
286
+ return self .type == 'quantitative'
145
287
146
288
@property
147
289
def is_ordinal (self ):
148
290
""" Is this field a categorical field that has a specific order
149
291
150
292
e.g. How do you feel? 1 - awful, 2 - ok, 3 - fantastic """
151
- return self ._type == 'ordinal'
293
+ return self .type == 'ordinal'
152
294
153
295
@property
154
296
def is_nominal (self ):
155
297
""" Is this field a categorical field that does not have a specific order
156
298
157
299
e.g. What color is your hair? """
158
- return self ._type == 'nominal'
300
+ return self .type == 'nominal'
159
301
160
302
@property
161
303
def calculation (self ):
162
304
""" If this field is a calculated field, this will be the formula """
163
305
return self ._calculation
164
306
307
+ @calculation .setter
308
+ def calculation (self , new_calculation ):
309
+ """ Set the calculation of a calculated field.
310
+
311
+ Args:
312
+ new_calculation: The new calculation/formula of the field. String.
313
+ """
314
+ if self .calculation is None :
315
+ calculation = ET .Element ('calculation' )
316
+ calculation .set ('class' , 'tableau' )
317
+ calculation .set ('formula' , new_calculation )
318
+ # Append the elements to the respective structure
319
+ self ._xml .append (calculation )
320
+
321
+ else :
322
+ self ._xml .find ('calculation' ).set ('formula' , new_calculation )
323
+
324
+ self ._calculation = new_calculation
325
+
165
326
@property
166
327
def default_aggregation (self ):
167
328
""" The default type of aggregation on the field (e.g Sum, Avg)"""
0 commit comments