Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Examples/GetFields/show_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
if field.calculation:
print(' the formula is {}'.format(field.calculation))
blank_line = True
if field.aggregation:
print(' the default aggregation is {}'.format(field.aggregation))
if field.default_aggregation:
print(' the default aggregation is {}'.format(field.default_aggregation))
blank_line = True

if blank_line:
Expand Down
62 changes: 56 additions & 6 deletions tableaudocumentapi/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,6 @@ def apply_metadata(self, metadata_record):
def from_xml(cls, xmldata):
return cls(xmldata)

def __getattr__(self, item):
private_name = '_{}'.format(item)
if item in _ATTRIBUTES or item in _METADATA_ATTRIBUTES:
return getattr(self, private_name)
raise AttributeError(item)

def _apply_attribute(self, xmldata, attrib, default_func):
if hasattr(self, '_read_{}'.format(attrib)):
value = getattr(self, '_read_{}'.format(attrib))(xmldata)
Expand All @@ -71,6 +65,62 @@ def name(self):

return self.id

@property
def id(self):
""" Name of the field as specified in the file, usually surrounded by [ ] """
return self._id

@property
def caption(self):
""" Name of the field as displayed in Tableau unless an aliases is defined """
return self._caption

@property
def alias(self):
""" Name of the field as displayed in Tableau if the default name isn't wanted """
return self._alias

@property
def datatype(self):
""" Type of the field within Tableau (string, integer, etc) """
return self._datatype

@property
def role(self):
""" Dimension or Measure """
return self._role

@property
def is_quantitative(self):
""" A dependent value, usually a measure of something

e.g. Profit, Gross Sales """
return self._type == 'quantitative'
Copy link
Contributor

@t8y8 t8y8 Jul 12, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this approach, it will allow for some really clean filtering [f for f in fields if f.is_quantitative]


@property
def is_ordinal(self):
""" Is this field a categorical field that has a specific order

e.g. How do you feel? 1 - awful, 2 - ok, 3 - fantastic """
return self._type == 'ordinal'
Copy link
Contributor

@t8y8 t8y8 Jul 12, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I know which types are 'ordinal' vs 'nominal' -- can you add a docstring to these three?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, I'll have to determine how to word it to make it obvious. The best example I can think of is a date series, they define an order, but I'll dig through docs to see how it's explained


@property
def is_nominal(self):
""" Is this field a categorical field that does not have a specific order

e.g. What color is your hair? """
return self._type == 'nominal'

@property
def calculation(self):
""" If this field is a calculated field, this will be the formula """
return self._calculation

@property
def default_aggregation(self):
""" The default type of aggregation on the field (e.g Sum, Avg)"""
return self._aggregation

######################################
# Special Case handling methods for reading the values from the XML
######################################
Expand Down
18 changes: 16 additions & 2 deletions tableaudocumentapi/multilookup_dict.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import weakref


_no_default_value = object()


def _resolve_value(key, value):
retval = None
try:
retval = value.get(key, None)
if hasattr(value, 'get'):
retval = value.get(key, None)

if retval is None:
retval = value.getattr(key, None)
retval = getattr(value, key, None)
except AttributeError:
retval = None
return retval
Expand Down Expand Up @@ -43,6 +49,14 @@ def __setitem__(self, key, value):

dict.__setitem__(self, key, value)

def get(self, key, default_value=_no_default_value):
try:
return self[key]
except KeyError:
if default_value is not _no_default_value:
return default_value
raise

def __getitem__(self, key):
if key in self._indexes['alias']:
key = self._indexes['alias'][key]
Expand Down
2 changes: 1 addition & 1 deletion test/assets/datasource_test.tds
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
</column>
<column caption='A' datatype='string' name='[a]' role='dimension' type='nominal' />
<column caption='Today&apos;s Date' datatype='string' name='[Today&apos;s Date]' role='dimension' type='nominal' />
<column caption='X' datatype='integer' name='[x]' role='measure' type='quantitative' />
<column caption='X' datatype='integer' name='[x]' role='measure' type='ordinal' />
<column caption='Y' datatype='integer' name='[y]' role='measure' type='quantitative' />
<layout dim-ordering='alphabetic' dim-percentage='0.5' measure-ordering='alphabetic' measure-percentage='0.5' show-structure='true' />
<semantic-values>
Expand Down
19 changes: 17 additions & 2 deletions test/test_datasource.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import unittest
import os.path
import functools

from tableaudocumentapi import Datasource

Expand All @@ -23,7 +22,23 @@ def test_datasource_returns_calculation_from_fields(self):
self.assertEqual('1', self.ds.fields['[Number of Records]'].calculation)

def test_datasource_uses_metadata_record(self):
self.assertEqual('Sum', self.ds.fields['[x]'].aggregation)
self.assertEqual('Sum', self.ds.fields['[x]'].default_aggregation)

def test_datasource_column_name_contains_apostrophy(self):
self.assertIsNotNone(self.ds.fields.get("[Today's Date]", None))

def test_datasource_field_can_get_caption(self):
self.assertEqual(self.ds.fields['[a]'].caption, 'A')
self.assertEqual(getattr(self.ds.fields['[a]'], 'caption', None), 'A')

def test_datasource_field_caption_can_be_used_to_query(self):
self.assertIsNotNone(self.ds.fields.get('A', None))

def test_datasource_field_is_nominal(self):
self.assertTrue(self.ds.fields['[a]'].is_nominal)

def test_datasource_field_is_quantitative(self):
self.assertTrue(self.ds.fields['[y]'].is_quantitative)

def test_datasource_field_is_ordinal(self):
self.assertTrue(self.ds.fields['[x]'].is_ordinal)
16 changes: 16 additions & 0 deletions test/test_multidict.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,19 @@ def test_mutlilookupdict_can_still_find_caption_even_with_alias(self):
def test_mutlilookupdict_can_still_find_id_even_with_caption(self):
actual = self.mld['[bar]']
self.assertEqual(2, actual['value'])

def test_multilookupdict_gives_key_error_on_invalid_key(self):
try:
self.mld.get('foobar')
self.fail('should have thrown key error')
except KeyError as ex:
self.assertEqual(str(ex), "'foobar'")

def test_multilookupdict_get_returns_default_value(self):
default_value = ('default', 'return', 'value')
actual = self.mld.get('foobar', default_value)
self.assertEqual(actual, default_value)

def test_multilookupdict_get_returns_value(self):
actual = self.mld.get('baz')
self.assertEqual(1, actual['value'])