diff --git a/docs/docs/api-ref.md b/docs/docs/api-ref.md index cf6bc96..6b150fd 100644 --- a/docs/docs/api-ref.md +++ b/docs/docs/api-ref.md @@ -38,10 +38,39 @@ Saves any changes to the workbook to a new file specified by the `new_file` para `self.filename:` Returns the filename of the workbook. +`self.shapes` Returns a list of strings with the names of shapes found in the workbook. + ## Datasources ```python class Datasource(dsxml, filename=None) ``` +A class representing Tableau Data Sources, embedded in workbook files or in TDS files. + +**Params:** + +**Raises:** + +**Methods:** + +save + +save_as + +add_calculation + +**Properities:** + +`self.name` Returns string with the name of datasource. + +`self.version` Returns string of daatasource's version. + +`self.caption` Returns string of user defined name of datasource if exists. + +`self.connections` Returns list of connections are used in workbook. + +`self.fileds` Returns key-value result of field name and their attributes. + +`self.calculations` Returns calculated field of the workbook. ## Connections ```python @@ -74,5 +103,48 @@ The Connection class represents a tableau data connection. It can be from any ty ## Fields ```python -class Workbook(column_xml=None, metadata_xml=None) +class Field(column_xml=None, metadata_xml=None) ``` + +Represents a field in a datasource + +**Raises:** + +**Methods:** +`Field.create_field_xml()` Create field from scratch. + +`Field.add_alias(self, key, value)` Add an alias for a given display value. + +**Properities:** + +`self.name` Returns a string providing a nice name for the field which is derived from the alias, caption, or the id. + +`self.id` Returns a string with name of the field as specified in the file, usually surrounded by [ ]. + +`self.xml` Returns a ElementTree object which represents an XML of the field. + +`self.caption` Returns a string with the name of the field as displayed in Tableau unless an aliases is defined. + +`self.alias` Returns a string with the name of the field as displayed in Tableau if the default name isn't wanted. + +`self.datatype` Returns a string with the type of the field within Tableau (string, integer, etc). + +`self.role` Returns a string which identify field as a Dimension or Measure. + +`self.type` Returns a string with type of field (quantitative, ordinal, nominal). + +`self.aliases` Returns Key-value mappings of all aliases that are registered under this field. + +`self.is_quantitative` Returns a boolean if field is quantitative. + +`self.is_ordinal` Returns a boolean if field is categorical that has a specific order. + +`self.is_nominal` Returns a boolean if field is categorical that does not have a specific order. + +`self.calculation` Returns a string with the formula if this field is a calculated field. + +`self.default_aggregation` Returns a string with he default type of aggregation on the field (e.g Sum, Avg). + +`self.description` Returns a string with contents of the tag on a field. + +`self.worksheets` Returns a list of strings with the worksheet's names uses this field. diff --git a/docs/docs/contributing.md b/docs/docs/contributing.md index ee94e99..a0b832f 100644 --- a/docs/docs/contributing.md +++ b/docs/docs/contributing.md @@ -56,3 +56,12 @@ For all other things, please submit a PR that includes the fix, documentation, o If the feature is complex or has multiple solutions that could be equally appropriate approaches, it would be helpful to file an issue to discuss the design trade-offs of each solution before implementing, to allow us to collectively arrive at the best solution, which most likely exists in the middle somewhere. + + +## Release process + +We expect that everything merged into the development branch is ready to release on master. Releases can be made at any time - ideally it would be on a regular cadence but currently it is basically on request. +- bump the version in setup.py +- update the changelog: all commits to development should contain a meaningful message, the changelog can be built from these +- merge to master. +This kicks off an automated release in github and then publish to pypi. diff --git a/samples/list-tds-info/list_tds_info.py b/samples/list-tds-info/list_tds_info.py deleted file mode 100644 index 129f85c..0000000 --- a/samples/list-tds-info/list_tds_info.py +++ /dev/null @@ -1,18 +0,0 @@ -############################################################ -# Step 1) Use Datasource object from the Document API -############################################################ -from tableaudocumentapi import Datasource - -############################################################ -# Step 2) Open the .tds we want to replicate -############################################################ -sourceTDS = Datasource.from_file('world.tds') - -############################################################ -# Step 3) List out info from the TDS -############################################################ -print('----------------------------------------------------------') -print('-- Info for our .tds:') -print('-- name:\t{0}'.format(sourceTDS.name)) -print('-- version:\t{0}'.format(sourceTDS.version)) -print('----------------------------------------------------------') diff --git a/samples/preserve-namespaces/filtering.twb b/samples/preserve-namespaces/filtering.twb index 68cfcd3..0cf031d 100644 --- a/samples/preserve-namespaces/filtering.twb +++ b/samples/preserve-namespaces/filtering.twb @@ -54,6 +54,18 @@ <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + SPAM + 1 + SPAM + 19 + Count + true + + "xml" + "SQL_C_DEFAULT" + + Account Number 5 diff --git a/samples/show-fields/nested.tds b/samples/show-fields/nested.tds new file mode 100644 index 0000000..ed5c3dd --- /dev/null +++ b/samples/show-fields/nested.tds @@ -0,0 +1,780 @@ + + + + <_.fcp.ObjectModelEncapsulateLegacy.true...ObjectModelEncapsulateLegacy/> + <_.fcp.ObjectModelTableType.true...ObjectModelTableType/> + <_.fcp.SchemaViewerObjectModel.true...SchemaViewerObjectModel/> + + + + + + + + <_.fcp.ObjectModelEncapsulateLegacy.false...relation connection="sqlserver.1nzmabo1alszdd1dqm0c11g0qr0m" name="TestData" table="[dbo].[TestData]" type="table"/> + <_.fcp.ObjectModelEncapsulateLegacy.true...relation connection="sqlserver.1nzmabo1alszdd1dqm0c11g0qr0m" name="TestData" table="[dbo].[TestData]" type="table"/> + + + Account Account Name + 130 + [Account Account Name] + [TestData] + Account Account Name + 1 + string + Count + 255 + true + true + + + "SQL_WVARCHAR" + "SQL_C_WCHAR" + "true" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Account Number + 5 + [Account Number] + [TestData] + Account Number + 2 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Account Number Burst Out Account + 130 + [Account Number Burst Out Account] + [TestData] + Account Number Burst Out Account + 3 + string + Count + 255 + true + true + + + "SQL_WVARCHAR" + "SQL_C_WCHAR" + "true" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Acct Name + 130 + [Acct Name] + [TestData] + Acct Name + 4 + string + Count + 255 + true + true + + + "SQL_WVARCHAR" + "SQL_C_WCHAR" + "true" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Burst Out + 130 + [Burst Out] + [TestData] + Burst Out + 5 + string + Count + 255 + true + true + + + "SQL_WVARCHAR" + "SQL_C_WCHAR" + "true" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Burst Out Join + 130 + [Burst Out Join] + [TestData] + Burst Out Join + 6 + string + Count + 255 + true + true + + + "SQL_WVARCHAR" + "SQL_C_WCHAR" + "true" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Burst Out Set list + 5 + [Burst Out Set list] + [TestData] + Burst Out Set list + 7 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Burst Out View + 5 + [Burst Out View] + [TestData] + Burst Out View + 8 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Count JE Number + 5 + [Count JE Number] + [TestData] + Count JE Number + 9 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Entity ID + 130 + [Entity ID] + [TestData] + Entity ID + 10 + string + Count + 255 + true + true + + + "SQL_WVARCHAR" + "SQL_C_WCHAR" + "true" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Filter + 5 + [Filter] + [TestData] + Filter + 11 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Fiscal Year + 130 + [Fiscal Year] + [TestData] + Fiscal Year + 12 + string + Count + 255 + true + true + + + "SQL_WVARCHAR" + "SQL_C_WCHAR" + "true" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Flag + 11 + [Flag] + [TestData] + Flag + 13 + boolean + Count + false + + "SQL_BIT" + "SQL_C_BIT" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Flag__copy_ + 11 + [Flag__copy_] + [TestData] + Flag__copy_ + 14 + boolean + Count + false + + "SQL_BIT" + "SQL_C_BIT" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + FS Line + 5 + [FS Line] + [TestData] + FS Line + 15 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + FS Line Burst Out Account + 130 + [FS Line Burst Out Account] + [TestData] + FS Line Burst Out Account + 16 + string + Count + 255 + true + true + + + "SQL_WVARCHAR" + "SQL_C_WCHAR" + "true" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Group By + 5 + [Group By] + [TestData] + Group By + 17 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Image + 130 + [Image] + [TestData] + Image + 18 + string + Count + 255 + true + true + + + "SQL_WVARCHAR" + "SQL_C_WCHAR" + "true" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Note Line + 5 + [Note Line] + [TestData] + Note Line + 19 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Note Line Burst Out Account + 130 + [Note Line Burst Out Account] + [TestData] + Note Line Burst Out Account + 20 + string + Count + 255 + true + true + + + "SQL_WVARCHAR" + "SQL_C_WCHAR" + "true" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Selection + 5 + [Selection] + [TestData] + Selection + 21 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + show + 130 + [show] + [TestData] + show + 22 + string + Count + 255 + true + true + + + "SQL_WVARCHAR" + "SQL_C_WCHAR" + "true" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Show Cycle Based + 130 + [Show Cycle Based] + [TestData] + Show Cycle Based + 23 + string + Count + 255 + true + true + + + "SQL_WVARCHAR" + "SQL_C_WCHAR" + "true" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Sub Class + 5 + [Sub Class] + [TestData] + Sub Class + 24 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + SubClass Burst Out Account + 130 + [SubClass Burst Out Account] + [TestData] + SubClass Burst Out Account + 25 + string + Count + 255 + true + true + + + "SQL_WVARCHAR" + "SQL_C_WCHAR" + "true" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Type + 130 + [Type] + [TestData] + Type + 26 + string + Count + 255 + true + true + + + "SQL_WVARCHAR" + "SQL_C_WCHAR" + "true" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Amount + 130 + [Amount] + [TestData] + Amount + 27 + string + Count + 255 + true + true + + + "SQL_WVARCHAR" + "SQL_C_WCHAR" + "true" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Amount1 + 130 + [Amount1] + [TestData] + Amount1 + 28 + string + Count + 255 + true + true + + + "SQL_WVARCHAR" + "SQL_C_WCHAR" + "true" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Count of Amount Calculation + 5 + [Count of Amount Calculation] + [TestData] + Count of Amount Calculation + 29 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Number of Records + 5 + [Number of Records] + [TestData] + Number of Records + 30 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Number of Records1 + 5 + [Number of Records1] + [TestData] + Number of Records1 + 31 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Select Burst Out + 5 + [Select Burst Out] + [TestData] + Select Burst Out + 32 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Select Transaction Analysis view + 5 + [Select Transaction Analysis view] + [TestData] + Select Transaction Analysis view + 33 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Sum Cy + 5 + [Sum Cy] + [TestData] + Sum Cy + 34 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Sum Py1 + 5 + [Sum Py1] + [TestData] + Sum Py1 + 35 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Sum Py2 + 5 + [Sum Py2] + [TestData] + Sum Py2 + 36 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Sum Py3 + 5 + [Sum Py3] + [TestData] + Sum Py3 + 37 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Sum Py4 + 5 + [Sum Py4] + [TestData] + Sum Py4 + 38 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Total Credits + 5 + [Total Credits] + [TestData] + Total Credits + 39 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + Total Debits + 5 + [Total Debits] + [TestData] + Total Debits + 40 + real + Sum + 15 + true + + "SQL_FLOAT" + "SQL_C_DOUBLE" + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[TestData_44D2C885FAEF453C846AC2CCD3577055] + + + + + + + + + + + + + + + + + + + + <_.fcp.ObjectModelTableType.true...column caption="TestData" datatype="table" name="[__tableau_internal_object_id__].[TestData_44D2C885FAEF453C846AC2CCD3577055]" role="measure" type="quantitative"/> + + + + + + + + + + <_.fcp.ObjectModelEncapsulateLegacy.true...object-graph> + + + + + + + + + \ No newline at end of file diff --git a/samples/show-fields/show_fields.py b/samples/show-fields/show_fields.py index 84cdd44..f0fe493 100644 --- a/samples/show-fields/show_fields.py +++ b/samples/show-fields/show_fields.py @@ -6,26 +6,34 @@ ############################################################ # Step 2) Open the .tds we want to inspect ############################################################ -sourceTDS = Datasource.from_file('world.tds') +datasources = [Datasource.from_file('world.tds'), Datasource.from_file('nested.tds')] +for sourceTDS in datasources: -############################################################ -# Step 3) Print out all of the fields and what type they are -############################################################ -print('----------------------------------------------------------') -print('--- {} total fields in this datasource'.format(len(sourceTDS.fields))) -print('----------------------------------------------------------') -for count, field in enumerate(sourceTDS.fields.values()): - print('{:>4}: {} is a {}'.format(count+1, field.name, field.datatype)) - blank_line = False - if field.calculation: - print(' the formula is {}'.format(field.calculation)) - blank_line = True - if field.default_aggregation: - print(' the default aggregation is {}'.format(field.default_aggregation)) - blank_line = True - if field.description: - print(' the description is {}'.format(field.description)) + ############################################################ + # Step 3) Print out all of the fields and what type they are + ############################################################ + print('----------------------------------------------------------') + print('-- Info for our .tds:') + print('-- name:\t{0}'.format(sourceTDS.name)) + print('-- version:\t{0}'.format(sourceTDS.version)) + print('----------------------------------------------------------') + print('--- {} total fields in this datasource'.format(len(sourceTDS.fields))) + print('----------------------------------------------------------') + for count, field in enumerate(sourceTDS.fields.values()): + blank_line = False + if field.calculation: + print('{:>4}: field named `{}` is a `{}`'.format(count+1, field.name, field.datatype)) + print(' field id, caption, calculation: `{}`, `{}`, `{}`'.format(field.id, field.caption, + field.calculation)) + blank_line = True + if field.default_aggregation: + print('{:>4}: `{}` is a `{}`, default aggregation is `{}`'.format(count+1, field.name, field.datatype, + field.default_aggregation)) + + if field.description: + print('{:>4}: `{}` is a `{}`, description is `{}`'.format(count+1, field.name, field.datatype, + field.description)) - if blank_line: - print('') -print('----------------------------------------------------------') + if blank_line: + print('') + print('----------------------------------------------------------') diff --git a/samples/show_workbook_info/geocoding.twbx b/samples/show_workbook_info/geocoding.twbx new file mode 100644 index 0000000..2b99b6d Binary files /dev/null and b/samples/show_workbook_info/geocoding.twbx differ diff --git a/samples/show_workbook_info/show_workbook_info.py b/samples/show_workbook_info/show_workbook_info.py new file mode 100644 index 0000000..d83d254 --- /dev/null +++ b/samples/show_workbook_info/show_workbook_info.py @@ -0,0 +1,43 @@ +############################################################ +# Step 1) Use Datasource object from the Document API +############################################################ +from tableaudocumentapi import Workbook +from lxml import etree as ET + +############################################################ +# Step 2) Open the .tds we want to explore +############################################################ +sourceTWBX = Workbook('geocoding.twbx') + +############################################################ +# Step 3) List out info from the TWBX +############################################################ +print('----------------------------------------------------------') +print('-- Info for our .twbx:') +print('-- name:\t{0}'.format(sourceTWBX.filename)) +print('-- CONTENTS') +print('-- dashboards:\t{0}'.format(len(sourceTWBX.dashboards))) +for dash in sourceTWBX.dashboards: + print("-- {}".format(dash)) + +print('-- datasources:\t{0}'.format(len(sourceTWBX.datasources))) +for data in sourceTWBX.datasources: + print("-- {}".format(data.name)) + +print('-- worksheets:\t{0}'.format(len(sourceTWBX.worksheets))) +for data in sourceTWBX.worksheets: + print("-- {}".format(data)) + +print('-- shapes:\t{0}'.format(len(sourceTWBX.shapes))) +for shape in sourceTWBX.shapes: + print("-- {}".format(shape)) +print('----------------------------------------------------------') + +worksheets = sourceTWBX.worksheets +for worksheet in worksheets: + print("worksheet: {}".format(worksheet)) + for datasource in sourceTWBX.datasources: + print("-- datasource: {}".format(datasource.name)) + for count, field in enumerate(datasource.fields.values()): + if worksheet in field.worksheets: + print(field) diff --git a/samples/list-tds-info/world.tds b/samples/show_workbook_info/world.tds similarity index 98% rename from samples/list-tds-info/world.tds rename to samples/show_workbook_info/world.tds index 25013e1..2b46609 100644 --- a/samples/list-tds-info/world.tds +++ b/samples/show_workbook_info/world.tds @@ -1,871 +1,871 @@ - - - - - - - - - - - - - - Birth Rate - 5 - [Birth Rate] - [Extract] - Birth Rate - 0 - English$ - real - Sum - 48 - true - - 0.0070000000000000001 - 0.052999999999999999 - - - true - "array" - true - 8 - 3 - "asc" - 1 - "double" - - - - Business Tax Rate - 5 - [Business Tax Rate] - [Extract] - Business Tax Rate - 1 - English$ - real - Sum - 448 - true - - 0.082000000000000003 - 3.391 - - - true - "array" - true - 8 - 2 - "double" - - - - CO2 Emissions - 3 - [CO2 Emissions] - [Extract] - CO2 Emissions - 2 - English$ - integer - Sum - 1744 - true - - 7 - 8286892 - - - 4 - "sint32" - - - - Country - 129 - [Country] - [Extract] - Country - 3 - English$ - string - Count - 208 - 1 - 1073741823 - false - - - "Afghanistan" - "Zimbabwe" - - - "en_US_CI" - true - "heap" - true - 4294967292 - 2 - "asc" - 2 - "str" - - - - Days to Start Business - 2 - [Days to Start Business] - [Extract] - Days to Start Business - 4 - English$ - integer - Sum - 127 - true - - 1 - 694 - - - true - "array" - true - 2 - 1 - "sint16" - - - - Ease of Business - 2 - [Ease of Business] - [Extract] - Ease of Business - 5 - English$ - integer - Sum - 186 - true - - 1 - 189 - - - true - "array" - true - 2 - 1 - "sint16" - - - - Energy Usage - 3 - [Energy Usage] - [Extract] - Energy Usage - 6 - English$ - integer - Sum - 1722 - true - - 8 - 2727728 - - - 4 - "sint32" - - - - GDP - 20 - [GDP] - [Extract] - GDP - 7 - English$ - integer - Sum - 2495 - true - - 63101272 - 2147483647 - - - 8 - "sint64" - - - - Health Exp % GDP - 5 - [Health Exp % GDP] - [Extract] - Health Exp % GDP - 8 - English$ - real - Sum - 146 - true - - 0.0080000000000000002 - 0.22500000000000001 - - - true - "array" - true - 8 - 1 - "double" - - - - Health Exp/Capita - 2 - [Health Exp/Capita] - [Extract] - Health Exp/Capita - 9 - English$ - integer - Sum - 1070 - true - - 2 - 9908 - - - 2 - "sint16" - - - - Hours to do Tax - 2 - [Hours to do Tax] - [Extract] - Hours to do Tax - 10 - English$ - integer - Sum - 281 - true - - 12 - 2600 - - - 2 - "sint16" - - - - Infant Mortality Rate - 5 - [Infant Mortality Rate] - [Extract] - Infant Mortality Rate - 11 - English$ - real - Sum - 130 - true - - 0.002 - 0.14099999999999999 - - - true - "array" - true - 8 - 1 - "double" - - - - Internet Usage - 5 - [Internet Usage] - [Extract] - Internet Usage - 12 - English$ - real - Sum - 709 - true - - 0.0 - 0.96199999999999997 - - - true - "array" - true - 8 - 2 - "double" - - - - Lending Interest - 5 - [Lending Interest] - [Extract] - Lending Interest - 13 - English$ - real - Sum - 352 - true - - 0.0050000000000000001 - 4.9649999999999999 - - - true - "array" - true - 8 - 2 - "double" - - - - Life Expectancy Female - 16 - [Life Expectancy Female] - [Extract] - Life Expectancy Female - 14 - English$ - integer - Sum - 50 - true - - 39 - 87 - - - 1 - "sint8" - - - - Life Expectancy Male - 16 - [Life Expectancy Male] - [Extract] - Life Expectancy Male - 15 - English$ - integer - Sum - 48 - true - - 37 - 88 - - - 1 - "sint8" - - - - Mobile Phone Usage - 5 - [Mobile Phone Usage] - [Extract] - Mobile Phone Usage - 16 - English$ - real - Sum - 1179 - true - - 0.0 - 2.8980000000000001 - - - true - "array" - true - 8 - 2 - "double" - - - - Number of Records - 16 - [Number of Records] - [Extract] - Number of Records - 17 - integer - Sum - 1 - false - - 1 - 1 - - - "asc" - 1 - "sint8" - - - - Population 0-14 - 5 - [Population 0-14] - [Extract] - Population 0-14 - 18 - English$ - real - Sum - 377 - true - - 0.11799999999999999 - 0.5 - - - true - "array" - true - 8 - 2 - "double" - - - - Population 15-64 - 5 - [Population 15-64] - [Extract] - Population 15-64 - 19 - English$ - real - Sum - 299 - true - - 0.47399999999999998 - 0.85799999999999998 - - - true - "array" - true - 8 - 2 - "double" - - - - Population 65+ - 5 - [Population 65+] - [Extract] - Population 65+ - 20 - English$ - real - Sum - 209 - true - - 0.0030000000000000001 - 0.24399999999999999 - - - true - "array" - true - 8 - 1 - "double" - - - - Population Total - 3 - [Population Total] - [Extract] - Population Total - 21 - English$ - integer - Sum - 2699 - false - - 18876 - 1350695000 - - - 4 - "sint32" - - - - Population Urban - 5 - [Population Urban] - [Extract] - Population Urban - 22 - English$ - real - Sum - 823 - true - - 0.082000000000000003 - 1.0 - - - true - "array" - true - 8 - 2 - "double" - - - - Region - 129 - [Region] - [Extract] - Region - 23 - English$ - string - Count - 6 - 1 - 1073741823 - false - - - "Africa" - "The Americas" - - - "en_US_CI" - true - "heap" - true - 4294967292 - 1 - "asc" - 1 - "str" - - - - Tourism Inbound - 20 - [Tourism Inbound] - [Extract] - Tourism Inbound - 24 - English$ - integer - Sum - 1651 - true - - 700000 - 2147483647 - - - true - "array" - true - 8 - 2 - "sint64" - - - - Tourism Outbound - 20 - [Tourism Outbound] - [Extract] - Tourism Outbound - 25 - English$ - integer - Sum - 1458 - true - - 200000 - 2147483647 - - - true - "array" - true - 8 - 2 - "sint64" - - - - Year - 133 - [Year] - [Extract] - Year - 26 - English$ - date - Year - 13 - false - - #2000-12-01# - #2012-12-01# - - - true - "array" - true - "asc" - 4 - 0 - "asc" - 1 - "date" - - - - - - - - % of population - - - - - - - % of Commercial profit - - - - - - - Kilotonnes of oil equivalent - - - - - - - - Time required to start a business - - - - - - - 1=Ease - - - - - - - Kilotonnes - - - - - - - Gross domestic product - - - - - - - Healthcare expenditure as % of GDP - - - - - - - Healthcare expenditure per capita - - - - - - - Time to prepare and pay taxes for business - - - - - - - % of Population - - - - - - - Per Capita - - - - - - - A bank rate that meets private sectors' needs. - - - - - - - Years a newborn would live if prevailing patterns are the same - - - - - - - Years a newborn would live if prevailing patterns are the same - - - - - - - Per Capita - - - - - - - - - - % of population - - - - - - - % of population - - - - - - - % of population - - - - - - - Total number of people in a country - - - - - - - % of population - - - - - - - Income from inbound tourism - - - - - - - Expenditure for outbound tourism - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + Birth Rate + 5 + [Birth Rate] + [Extract] + Birth Rate + 0 + English$ + real + Sum + 48 + true + + 0.0070000000000000001 + 0.052999999999999999 + + + true + "array" + true + 8 + 3 + "asc" + 1 + "double" + + + + Business Tax Rate + 5 + [Business Tax Rate] + [Extract] + Business Tax Rate + 1 + English$ + real + Sum + 448 + true + + 0.082000000000000003 + 3.391 + + + true + "array" + true + 8 + 2 + "double" + + + + CO2 Emissions + 3 + [CO2 Emissions] + [Extract] + CO2 Emissions + 2 + English$ + integer + Sum + 1744 + true + + 7 + 8286892 + + + 4 + "sint32" + + + + Country + 129 + [Country] + [Extract] + Country + 3 + English$ + string + Count + 208 + 1 + 1073741823 + false + + + "Afghanistan" + "Zimbabwe" + + + "en_US_CI" + true + "heap" + true + 4294967292 + 2 + "asc" + 2 + "str" + + + + Days to Start Business + 2 + [Days to Start Business] + [Extract] + Days to Start Business + 4 + English$ + integer + Sum + 127 + true + + 1 + 694 + + + true + "array" + true + 2 + 1 + "sint16" + + + + Ease of Business + 2 + [Ease of Business] + [Extract] + Ease of Business + 5 + English$ + integer + Sum + 186 + true + + 1 + 189 + + + true + "array" + true + 2 + 1 + "sint16" + + + + Energy Usage + 3 + [Energy Usage] + [Extract] + Energy Usage + 6 + English$ + integer + Sum + 1722 + true + + 8 + 2727728 + + + 4 + "sint32" + + + + GDP + 20 + [GDP] + [Extract] + GDP + 7 + English$ + integer + Sum + 2495 + true + + 63101272 + 2147483647 + + + 8 + "sint64" + + + + Health Exp % GDP + 5 + [Health Exp % GDP] + [Extract] + Health Exp % GDP + 8 + English$ + real + Sum + 146 + true + + 0.0080000000000000002 + 0.22500000000000001 + + + true + "array" + true + 8 + 1 + "double" + + + + Health Exp/Capita + 2 + [Health Exp/Capita] + [Extract] + Health Exp/Capita + 9 + English$ + integer + Sum + 1070 + true + + 2 + 9908 + + + 2 + "sint16" + + + + Hours to do Tax + 2 + [Hours to do Tax] + [Extract] + Hours to do Tax + 10 + English$ + integer + Sum + 281 + true + + 12 + 2600 + + + 2 + "sint16" + + + + Infant Mortality Rate + 5 + [Infant Mortality Rate] + [Extract] + Infant Mortality Rate + 11 + English$ + real + Sum + 130 + true + + 0.002 + 0.14099999999999999 + + + true + "array" + true + 8 + 1 + "double" + + + + Internet Usage + 5 + [Internet Usage] + [Extract] + Internet Usage + 12 + English$ + real + Sum + 709 + true + + 0.0 + 0.96199999999999997 + + + true + "array" + true + 8 + 2 + "double" + + + + Lending Interest + 5 + [Lending Interest] + [Extract] + Lending Interest + 13 + English$ + real + Sum + 352 + true + + 0.0050000000000000001 + 4.9649999999999999 + + + true + "array" + true + 8 + 2 + "double" + + + + Life Expectancy Female + 16 + [Life Expectancy Female] + [Extract] + Life Expectancy Female + 14 + English$ + integer + Sum + 50 + true + + 39 + 87 + + + 1 + "sint8" + + + + Life Expectancy Male + 16 + [Life Expectancy Male] + [Extract] + Life Expectancy Male + 15 + English$ + integer + Sum + 48 + true + + 37 + 88 + + + 1 + "sint8" + + + + Mobile Phone Usage + 5 + [Mobile Phone Usage] + [Extract] + Mobile Phone Usage + 16 + English$ + real + Sum + 1179 + true + + 0.0 + 2.8980000000000001 + + + true + "array" + true + 8 + 2 + "double" + + + + Number of Records + 16 + [Number of Records] + [Extract] + Number of Records + 17 + integer + Sum + 1 + false + + 1 + 1 + + + "asc" + 1 + "sint8" + + + + Population 0-14 + 5 + [Population 0-14] + [Extract] + Population 0-14 + 18 + English$ + real + Sum + 377 + true + + 0.11799999999999999 + 0.5 + + + true + "array" + true + 8 + 2 + "double" + + + + Population 15-64 + 5 + [Population 15-64] + [Extract] + Population 15-64 + 19 + English$ + real + Sum + 299 + true + + 0.47399999999999998 + 0.85799999999999998 + + + true + "array" + true + 8 + 2 + "double" + + + + Population 65+ + 5 + [Population 65+] + [Extract] + Population 65+ + 20 + English$ + real + Sum + 209 + true + + 0.0030000000000000001 + 0.24399999999999999 + + + true + "array" + true + 8 + 1 + "double" + + + + Population Total + 3 + [Population Total] + [Extract] + Population Total + 21 + English$ + integer + Sum + 2699 + false + + 18876 + 1350695000 + + + 4 + "sint32" + + + + Population Urban + 5 + [Population Urban] + [Extract] + Population Urban + 22 + English$ + real + Sum + 823 + true + + 0.082000000000000003 + 1.0 + + + true + "array" + true + 8 + 2 + "double" + + + + Region + 129 + [Region] + [Extract] + Region + 23 + English$ + string + Count + 6 + 1 + 1073741823 + false + + + "Africa" + "The Americas" + + + "en_US_CI" + true + "heap" + true + 4294967292 + 1 + "asc" + 1 + "str" + + + + Tourism Inbound + 20 + [Tourism Inbound] + [Extract] + Tourism Inbound + 24 + English$ + integer + Sum + 1651 + true + + 700000 + 2147483647 + + + true + "array" + true + 8 + 2 + "sint64" + + + + Tourism Outbound + 20 + [Tourism Outbound] + [Extract] + Tourism Outbound + 25 + English$ + integer + Sum + 1458 + true + + 200000 + 2147483647 + + + true + "array" + true + 8 + 2 + "sint64" + + + + Year + 133 + [Year] + [Extract] + Year + 26 + English$ + date + Year + 13 + false + + #2000-12-01# + #2012-12-01# + + + true + "array" + true + "asc" + 4 + 0 + "asc" + 1 + "date" + + + + + + + + % of population + + + + + + + % of Commercial profit + + + + + + + Kilotonnes of oil equivalent + + + + + + + + Time required to start a business + + + + + + + 1=Ease + + + + + + + Kilotonnes + + + + + + + Gross domestic product + + + + + + + Healthcare expenditure as % of GDP + + + + + + + Healthcare expenditure per capita + + + + + + + Time to prepare and pay taxes for business + + + + + + + % of Population + + + + + + + Per Capita + + + + + + + A bank rate that meets private sectors' needs. + + + + + + + Years a newborn would live if prevailing patterns are the same + + + + + + + Years a newborn would live if prevailing patterns are the same + + + + + + + Per Capita + + + + + + + + + + % of population + + + + + + + % of population + + + + + + + % of population + + + + + + + Total number of people in a country + + + + + + + % of population + + + + + + + Income from inbound tourism + + + + + + + Expenditure for outbound tourism + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup.py b/setup.py index 3ea2643..3f5ba92 100644 --- a/setup.py +++ b/setup.py @@ -9,5 +9,6 @@ packages=['tableaudocumentapi'], license='MIT', description='A Python module for working with Tableau files.', - test_suite='test' + test_suite='test', + install_requires=['lxml'] ) diff --git a/tableaudocumentapi/connection.py b/tableaudocumentapi/connection.py index 2fb13ed..caa5a61 100644 --- a/tableaudocumentapi/connection.py +++ b/tableaudocumentapi/connection.py @@ -1,4 +1,4 @@ -import xml.etree.ElementTree as ET +from lxml import etree as ET from tableaudocumentapi.dbclass import is_valid_dbclass diff --git a/tableaudocumentapi/datasource.py b/tableaudocumentapi/datasource.py index f108f6d..abeb603 100644 --- a/tableaudocumentapi/datasource.py +++ b/tableaudocumentapi/datasource.py @@ -1,6 +1,6 @@ import collections import itertools -import xml.etree.ElementTree as ET +from lxml import etree as ET import xml.sax.saxutils as sax from uuid import uuid4 @@ -184,14 +184,17 @@ def save_as(self, new_filename): @property def name(self): + """ Name of the datasource. """ return self._name @property def version(self): + """ Version of the datasource. """ return self._version @property def caption(self): + """ User defined name for the datasourse. """ return self._caption @caption.setter @@ -206,6 +209,7 @@ def caption(self): @property def connections(self): + """ List of connections are used in workbook. """ return self._connections def clear_repository_location(self): @@ -215,6 +219,7 @@ def clear_repository_location(self): @property def fields(self): + """ Key-value result of field's names and its attributes. Dict. """ if not self._fields: self._refresh_fields() return self._fields diff --git a/tableaudocumentapi/field.py b/tableaudocumentapi/field.py index 88d5b3b..8df87bf 100644 --- a/tableaudocumentapi/field.py +++ b/tableaudocumentapi/field.py @@ -1,5 +1,6 @@ import functools -import xml.etree.ElementTree as ET +from lxml import etree as ET +from xml.dom import minidom from tableaudocumentapi.property_decorators import argument_is_one_of @@ -47,7 +48,7 @@ def __init__(self, column_xml=None, metadata_xml=None): if column_xml is not None: self._initialize_from_column_xml(column_xml) self._xml = column_xml - # This isn't currently never called because of the way we get the data from the xml, + # This isn't currently called because of the way we get the data from the xml, # but during the refactor, we might need it. This is commented out as a reminder # if metadata_xml is not None: # self.apply_metadata(metadata_xml) @@ -135,6 +136,31 @@ def xml(self): """ XML representation of the field. """ return self._xml + def pretty_xml(self): + """Return a pretty-printed XML string for the Element. + """ + rough_string = ET.tostring(self._xml, 'utf-8') + prepared_string = minidom.parseString(rough_string) + print_string = prepared_string.toprettyxml(indent=" ", newl="") + return print_string.lstrip('') + + def __str__(self): + """ String representation of the field (only includes usable attributes) """ + # TODO: this should just loop through the ATTRIBUTES so it doesn't need touching for new ones + output = "------ FIELD {}: {}(type), {}(datatype), {}(role), {}(aggregation)".format( + self.name, self.type, self.datatype, self.role, self.default_aggregation) + return output + + def detailed_str(self): + calc = "" + if self.calculation: + calc = "\ncalc: `{}`".format(self.calculation) + else: + calc = "" + return "------FIELD {} ({}/{}/{})\n{}(type), {}(datatype), {}(role), {}(aggregation)\n`{}`"\ + .format(self.name, self.caption, self.alias, self.id, self.type, self.datatype, self.role, + self.default_aggregation, self.description, calc) + ######################################## # Attribute getters and setters ######################################## @@ -215,7 +241,7 @@ def role(self, role): @property def type(self): - """ Dimension or Measure """ + """ Type of field (quantitative, ordinal, nominal) """ return self._type @type.setter @@ -251,7 +277,7 @@ def add_alias(self, key, value): # determine whether there already is an aliases-tag aliases = self._xml.find('aliases') # and create it if there isn't - if not aliases: + if not aliases: # ignore the FutureWarning, does not apply to our usage aliases = ET.Element('aliases') self._xml.append(aliases) @@ -272,7 +298,7 @@ def aliases(self): Returns: Key-value mappings of all registered aliases. Dict. """ - aliases_tag = self._xml.find('aliases') or [] + aliases_tag = self._xml.find('aliases') or [] # ignore the FutureWarning, does not apply to our usage return {a.get('key', 'None'): a.get('value', 'None') for a in list(aliases_tag)} ######################################## @@ -336,6 +362,7 @@ def description(self): @property def worksheets(self): + """ Worksheets which uses field. """ return list(self._worksheets) ###################################### diff --git a/tableaudocumentapi/workbook.py b/tableaudocumentapi/workbook.py index 950c83a..4c425da 100644 --- a/tableaudocumentapi/workbook.py +++ b/tableaudocumentapi/workbook.py @@ -1,8 +1,7 @@ import weakref - from tableaudocumentapi import Datasource, xfile -from tableaudocumentapi.xfile import xml_open +from tableaudocumentapi.xfile import xml_open, TableauInvalidFileException class Workbook(object): @@ -18,6 +17,8 @@ def __init__(self, filename): self._filename = filename self._workbookTree = xml_open(self._filename, 'workbook') + if not self._workbookTree: + raise TableauInvalidFileException("Workbook file must have a workbook element at root") self._workbookRoot = self._workbookTree.getroot() diff --git a/tableaudocumentapi/xfile.py b/tableaudocumentapi/xfile.py index 12d7d6e..8d2bb9f 100644 --- a/tableaudocumentapi/xfile.py +++ b/tableaudocumentapi/xfile.py @@ -3,7 +3,7 @@ import shutil import tempfile import zipfile -import xml.etree.ElementTree as ET +from lxml import etree as ET from distutils.version import LooseVersion as Version @@ -38,6 +38,8 @@ def xml_open(filename, expected_root=None): # Does the root tag match the object type (workbook or data source) if expected_root and (expected_root != tree_root.tag): + if expected_root == 'workbook' and tree_root.tag == 'datasource': + return # A .twbx can contain .tds files if it contains custom geocoding. raise TableauInvalidFileException( "'{}'' is not a valid '{}' file".format(filename, expected_root)) diff --git a/test/bvt.py b/test/bvt.py index c2854ad..f8fd43b 100644 --- a/test/bvt.py +++ b/test/bvt.py @@ -1,7 +1,7 @@ import os import unittest -import xml.etree.ElementTree as ET +from lxml import etree as ET from test.assets.index import * from tableaudocumentapi import Workbook, Datasource, Connection, ConnectionParser from tableaudocumentapi.xfile import TableauInvalidFileException, TableauVersionNotSupportedException @@ -160,7 +160,7 @@ def test_save_has_xml_declaration(self): with open(self.tds_file.name) as f: first_line = f.readline().strip() # first line should be xml tag self.assertEqual( - first_line, "") + first_line, "") class DatasourceModelV10Tests(unittest.TestCase): @@ -323,7 +323,7 @@ def test_save_has_xml_declaration(self): with open(self.workbook_file.name) as f: first_line = f.readline().strip() # first line should be xml tag self.assertEqual( - first_line, "") + first_line, "") class WorkbookModelV10TWBXTests(unittest.TestCase): diff --git a/test/test_field_change.py b/test/test_field_change.py index 7a84f1e..ae64259 100644 --- a/test/test_field_change.py +++ b/test/test_field_change.py @@ -2,7 +2,7 @@ import os.path from tableaudocumentapi import Datasource -import xml.etree.ElementTree as ET +from lxml import etree as ET TEST_ASSET_DIR = os.path.join( diff --git a/test/test_xfile.py b/test/test_xfile.py index 3da133a..c9686f5 100644 --- a/test/test_xfile.py +++ b/test/test_xfile.py @@ -53,21 +53,10 @@ def test_save_preserves_namespace_twb(self): wb.save_as(new_name) self.assertContainsUserNamespace(new_name) - ''' def demo_bug_ns_not_preserved_if_not_used(self): filename = TABLEAU_10_TDS self.assertContainsUserNamespace(filename) wb = Datasource.from_file(filename) - #wb.save() new_name = 'saved-as-tds.tds' wb.save_as(new_name) - self.assertContainsUserNamespace(new_name) <- throws - - If there is no namespace in the document when you begin working with it, - none will be added. - If there is a namespace but it *is not used* in the document, it will be stripped - - Fix will be something like - https://stackoverflow.com/questions/41937624/elementtree-why-are-my-namespace-declarations-stripped-out - - ''' + self.assertContainsUserNamespace(new_name)