diff --git a/Examples/GetFields/show_fields.py b/Examples/GetFields/show_fields.py index b04a056..ee45f87 100644 --- a/Examples/GetFields/show_fields.py +++ b/Examples/GetFields/show_fields.py @@ -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: diff --git a/tableaudocumentapi/field.py b/tableaudocumentapi/field.py index 1eb68ef..8162cdb 100644 --- a/tableaudocumentapi/field.py +++ b/tableaudocumentapi/field.py @@ -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) @@ -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' + + @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' + + @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 ###################################### diff --git a/tableaudocumentapi/multilookup_dict.py b/tableaudocumentapi/multilookup_dict.py index 21e2736..64b742a 100644 --- a/tableaudocumentapi/multilookup_dict.py +++ b/tableaudocumentapi/multilookup_dict.py @@ -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 @@ -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] diff --git a/test/assets/datasource_test.tds b/test/assets/datasource_test.tds index 227e006..a1e78a8 100644 --- a/test/assets/datasource_test.tds +++ b/test/assets/datasource_test.tds @@ -77,7 +77,7 @@ - + diff --git a/test/test_datasource.py b/test/test_datasource.py index da956ee..0a2457e 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -1,6 +1,5 @@ import unittest import os.path -import functools from tableaudocumentapi import Datasource @@ -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) diff --git a/test/test_multidict.py b/test/test_multidict.py index abb01c5..0a78e9d 100644 --- a/test/test_multidict.py +++ b/test/test_multidict.py @@ -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'])