8000 Release 0.3 by graysonarts · Pull Request #78 · tableau/document-api-python · GitHub
[go: up one dir, main page]

Skip to content

Release 0.3 #78

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Aug 31, 2016
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
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ script:
# pep8
- pep8 .
# Examples
- (cd "Examples/Replicate Workbook" && python replicateWorkbook.py)
- (cd "Examples/List TDS Info" && python listTDSInfo.py)
- (cd "Examples/GetFields" && python show_fields.py)
- (cd "samples/replicate-workbook" && python replicate_workbook.py)
- (cd "samples/list-tds-info" && python list_tds_info.py)
- (cd "samples/show-fields" && python show_fields.py)

7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 0.3 (31 August 2016)

* Added basic connection class retargeting (#65)
* Added ability to create a new connection (#69)
* Added description to the field object (#73)
* Improved Test Coverage (#62, #67)

## 0.2 (22 July 2016)

* Added support for loading twbx and tdsx files (#43, #44)
Expand Down
1 change: 0 additions & 1 deletion Examples/GetFields/World.tds

This file was deleted.

10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Features include:
- Database Name
- Getting Field information from data sources and workbooks
- Get all fields in a data source
- Get all feilds in use by certain sheets in a workbook
- Get all fields in use by certain sheets in a workbook

We don't yet support creating files from scratch, adding extracts into workbooks or data sources, or updating field information

Expand All @@ -45,7 +45,7 @@ Download the `.zip` file that contains the SDK. Unzip the file and then run the
pip install -e <directory containing setup.py>
```

#### Installing the Development Version From Git
#### Installing the Development Version from Git

*Only do this if you know you want the development version, no guarantee that we won't break APIs during development*

Expand Down Expand Up @@ -74,7 +74,7 @@ sourceWB.datasources[0].connections[0].username = "benl"
sourceWB.save()
```

With Data Integration in Tableau 10, a data source can have multiple connections. To access the connections simply index them like you would datasources
With Data Integration in Tableau 10, a data source can have multiple connections. To access the connections simply index them like you would datasources.

```python
from tableaudocumentapi import Workbook
Expand Down Expand Up @@ -104,6 +104,6 @@ sourceWB.save()



###Examples
###[Examples](Examples)

The downloadable package contains several example scripts that show more detailed usage of the Document API
The downloadable package contains several example scripts that show more detailed usage of the Document API.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
############################################################
# Step 2) Open the .tds we want to replicate
############################################################
sourceTDS = Datasource.from_file('World.tds')
sourceTDS = Datasource.from_file('world.tds')

############################################################
# Step 3) List out info from the TDS
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
############################################################
# Step 2) Open the .twb we want to replicate
############################################################
sourceWB = Workbook('Sample - Superstore.twb')
sourceWB = Workbook('sample-superstore.twb')

############################################################
# Step 3) Use a database list (in CSV), loop thru and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
############################################################
# Step 2) Open the .tds we want to inspect
############################################################
sourceTDS = Datasource.from_file('World.tds')
sourceTDS = Datasource.from_file('world.tds')

############################################################
# Step 3) Print out all of the fields and what type they are
Expand All @@ -23,6 +23,8 @@
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))

if blank_line:
print('')
Expand Down
1 change: 1 addition & 0 deletions samples/show-fields/world.tds
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name='tableaudocumentapi',
version='0.2',
version='0.3',
author='Tableau Software',
author_email='github@tableau.com',
url='https://github.com/tableau/document-api-python',
Expand Down
22 changes: 22 additions & 0 deletions tableaudocumentapi/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# Connection - A class for writing connections to Tableau files
#
###############################################################################
import xml.etree.ElementTree as ET
from tableaudocumentapi.dbclass import is_valid_dbclass


class Connection(object):
Expand Down Expand Up @@ -32,6 +34,17 @@ def __init__(self, connxml):
def __repr__(self):
return "'<Connection server='{}' dbname='{}' @ {}>'".format(self._server, self._dbname, hex(id(self)))

@classmethod
def from_attributes(cls, server, dbname, username, dbclass, authentication=''):
root = ET.Element('connection', authentication=authentication)
xml = cls(root)
xml.server = server
xml.dbname = dbname
xml.username = username
xml.dbclass = dbclass

return xml

###########
# dbname
###########
Expand Down Expand Up @@ -111,3 +124,12 @@ def authentication(self):
@property
def dbclass(self):
return self._class

@dbclass.setter
def dbclass(self, value):

if not is_valid_dbclass(value):
raise AttributeError("'{}' is not a valid database type".format(value))

self._class = value
self._connectionXML.set('class', value)
51 changes: 49 additions & 2 deletions tableaudocumentapi/datasource.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
###############################################################################
import collections
import itertools
import random
import xml.etree.ElementTree as ET
import xml.sax.saxutils as sax
from uuid import uuid4

from tableaudocumentapi import Connection, xfile
from tableaudocumentapi import Field
Expand All @@ -19,7 +21,7 @@
# dropped, remove this and change the basestring references below to str
try:
basestring
except NameError:
except NameError: # pragma: no cover
basestring = str
########

Expand All @@ -38,6 +40,7 @@ def _is_used_by_worksheet(names, field):


class FieldDictionary(MultiLookupDict):

def used_by_sheet(self, name):
# If we pass in a string, no need to get complicated, just check to see if name is in
# the field's list of worksheets
Expand All @@ -63,7 +66,36 @@ def _column_object_from_metadata_xml(metadata_xml):
return _ColumnObjectReturnTuple(field_object.id, field_object)


def base36encode(number):
"""Converts an integer into a base36 string."""

ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz"

base36 = ''
sign = ''

if number < 0:
sign = '-'
number = -number

if 0 <= number < len(ALPHABET):
return sign + ALPHABET[number]

while number != 0:
number, i = divmod(number, len(ALPHABET))
base36 = ALPHABET[i] + base36

return sign + base36


def make_unique_name(dbclass):
rand_part = base36encode(uuid4().int)
name = dbclass + '.' + rand_part
return name


class ConnectionParser(object):

def __init__(self, datasource_xml, version):
self._dsxml = datasource_xml
self._dsversion = version
Expand Down Expand Up @@ -113,9 +145,23 @@ def __init__(self, dsxml, filename=None):
def from_file(cls, filename):
"""Initialize datasource from file (.tds)"""

dsxml = xml_open(filename).getroot()
dsxml = xml_open(filename, cls.__name__.lower()).getroot()
return cls(dsxml, filename)

@classmethod
def from_connections(cls, caption, connections):
root = ET.Element('datasource', caption=caption, version='10.0', inline='true')
outer_connection = ET.SubElement(root, 'connection')
outer_connection.set('class', 'federated')
named_conns = ET.SubElement(outer_connection, 'named-connections')
for conn in connections:
nc = ET.SubElement(named_conns,
'named-connection',
name=make_unique_name(conn.dbclass),
caption=conn.server)
nc.append(conn._connectionXML)
return cls(root)

def save(self):
"""
Call finalization code and save file.
Expand Down Expand Up @@ -143,6 +189,7 @@ def save_as(self, new_filename):
Nothing.

"""

xfile._save_file(self._filename, self._datasourceTree, new_filename)

###########
Expand Down
60 changes: 60 additions & 0 deletions tableaudocumentapi/dbclass.py
< 1241 th scope="col">Diff line change
Original file line number Diff line number
@@ -0,0 +1,60 @@


KNOWN_DB_CLASSES = ('msaccess',
'msolap',
'bigquery',
'asterncluster',
'bigsql',
'aurora',
'awshadoophive',
'dataengine',
'DataStax',
'db2',
'essbase',
'exasolution',
'excel',
'excel-direct',
'excel-reader',
'firebird',
'powerpivot',
'genericodbc',
'google-analytics',
'googlecloudsql',
'google-sheets',
'greenplum',
'saphana',
'hadoophive',
'hortonworkshadoophive',
'maprhadoophive',
'marklogic',
'memsql',
'mysql',
'netezza',
'oracle',
'paraccel',
'postgres',
'progressopenedge',
'redshift',
'snowflake',
'spark',
'splunk',
'kognitio',
'sqlserver',
'salesforce',
'sapbw',
'sybasease',
'sybaseiq',
'tbio',
'teradata',
'vectorwise',
'vertica',
'denormalized-cube',
'csv',
'textscan',
'webdata',
'webdata-direct',
'cubeextract')


def is_valid_dbclass(dbclass):
return dbclass in KNOWN_DB_CLASSES
22 changes: 20 additions & 2 deletions tableaudocumentapi/field.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import functools
import xml.etree.ElementTree as ET


_ATTRIBUTES = [
'id', # Name of the field as specified in the file, usually surrounded by [ ]
Expand All @@ -8,6 +10,7 @@
'type', # three possible values: quantitative, ordinal, or nominal
'alias', # Name of the field as displayed in Tableau if the default name isn't wanted
'calculation', # If this field is a calculated field, this will be the formula
'description', # If this field has a description, this will be the description (including formatting tags)
]

_METADATA_ATTRIBUTES = [
Expand Down Expand Up @@ -42,8 +45,10 @@ def __init__(self, column_xml=None, metadata_xml=None):

if column_xml is not None:
self._initialize_from_column_xml(column_xml)
if metadata_xml is not None:
self.apply_metadata(metadata_xml)
# This isn't currently never 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)

elif metadata_xml is not None:
self._initialize_from_metadata_xml(metadata_xml)
Expand Down Expand Up @@ -162,6 +167,11 @@ def default_aggregation(self):
""" The default type of aggregation on the field (e.g Sum, Avg)"""
return self._aggregation

@property
def description(self):
""" The contents of the <desc> tag on a field """
return self._description

@property
def worksheets(self):
return list(self._worksheets)
Expand All @@ -182,3 +192,11 @@ def _read_calculation(xmldata):
return None

return calc.attrib.get('formula', None)

@staticmethod
def _read_description(xmldata):
description = xmldata.find('.//desc')
if description is None:
return None

return u'{}'.format(ET.tostring(description, encoding='utf-8')) # This is necessary for py3 support
Loading
0