Skip to content

Commit 96d8967

Browse files
author
Russell Hay
authored
Merge pull request #108 from tableau/development
Release 0.5
2 parents a3b7322 + a426a42 commit 96d8967

13 files changed

+141
-62
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 05 (01 November 2016)
2+
3+
* Added ability to set the port for connections (#97)
4+
* Added ability to read and write caption for datasources (#99)
5+
* Added documentation
6+
17
## 0.4 (07 October 2016)
28

39
* Add ability to remove repository location (#86)

contributing.md

Lines changed: 0 additions & 33 deletions
This file was deleted.

contributing.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
docs/docs/contributing.md

docs/_config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Site settings
22
title: Tableau Document API
3-
email: jdominguez@tableau.com
3+
email: github@tableau.com
44
description: Programmatically update your Tableau workbooks and data sources.
55
baseurl: "/document-api-python"
66
permalinks: pretty

docs/_layouts/docs.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<div class="content .col-xs-12 .col-sm-8 .col-md-9">
1818
<h1>{{ page.title }}</h1>
1919
<div class="edit-container">
20-
<a href="https://github.com/tableau/document-api-python/edit/master/{{ page.path }}" class="edit-links"><span class="glyphicon glyphicon-pencil"></span> Edit this page</a>
20+
<a href="https://github.com/tableau/document-api-python/edit/master/docs/{{ page.path }}" class="edit-links"><span class="glyphicon glyphicon-pencil"></span> Edit this page</a>
2121
&nbsp;
2222
<a href="https://github.com/tableau/document-api-python/issues" class="edit-links"><span class="glyphicon glyphicon-flag"></span> Submit an issue</a>
2323
</div>

docs/docs/contributing.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
title: Contributing
33
layout: docs
44
---
5+
# Contributing
56

67
We welcome contributions to this project!
78

docs/docs/dev-guide.md

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,51 +3,67 @@ title: Developer Guide
33
layout: docs
44
---
55

6-
### Making your first patch
6+
<<<<<<< 6ee666bf06d5ab59931100b3955779c35693e33f
7+
## Submitting your first patch
78

8-
1. Make sure you've signed the CLA
9+
1. Make sure you have [signed the CLA](http://tableau.github.io/#contributor-license-agreement-cla)
910

10-
1. Clone the repo
11+
1. Fork the repository.
12+
13+
We follow the "Fork and Pull" model as described [here](https://help.github.com/articles/about-collaborative-development-models/).
14+
15+
1. Clone your fork:
1116

1217
```shell
13-
git clone http://github.com/tableau/document-api-python
18+
git clone http://github.com/<your_username>/document-api-python
1419
```
1520

16-
1. Run the tests to make sure everything is peachy
21+
1. Run the tests to make sure everything is peachy:
1722

1823
```shell
1924
python setup.py test
2025
```
2126

2227
1. Set up the feature, fix, or documentation branch.
2328

24-
It is recommended to use the format [issue#]-[type]-[description] (e.g. 13-fix-connection-bug)
29+
It is recommended to use the format issue#-type-description (e.g. 13-fix-connection-bug) like so:
2530

2631
```shell
2732
git checkout -b 13-feature-new-stuff
2833
```
2934

30-
1. Code and Commit!
35+
1. Code and commit!
36+
37+
Here's a quick checklist for ensuring a good pull request:
38+
39+
- Only touch the minimal amount of files possible while still accomplishing the goal.
40+
- Ensure all indentation is done as 4-spaces and your editor is set to unix line endings.
41+
- The code matches PEP8 style guides. If you cloned the repo you can run `pycodestyle .`
42+
- Keep commit messages clean and descriptive.
43+
If the PR is accepted it will get 'Squashed' into a single commit before merging, the commit messages will be used to generate the Merge commit message.
3144

32-
Here's a quick checklist for ensuring a good diff:
45+
1. Add tests.
3346

34-
- The diff touches the minimal amount of files possible while still fufilling the purpose of the diff
35-
- The diff uses Unix line endings
36-
- The diff adheres to our PEP8 style guides. If you've cloned the repo you can run `pycodestyle .`
47+
All of our tests live under the `test/` folder in the repository.
48+
We use `unittest` and the built-in test runner `python setup.py test`.
49+
If a test needs a static file, like a twb/twbx, it should live under `test/assets/`
3750

38-
1. Add Tests
51+
1. Update the documentation.
3952

40-
1. Update Documentation
53+
Our documentation is written in markdown and built with Jekyll on Github Pages. All of the documentation source files can be found in `docs/docs`.
4154

42-
Our documentation is written in markdown and built with [Mkdocs](http://www.mkdocs.org). More information on how to update and build the docs can be found [here](#updating-documentation)
55+
When adding a new feature or improving existing functionality we may ask that you update the documentation along with your code.
56+
57+
If you are just making a PR for documentation updates (adding new docs, fixing typos, improving wording) the easiest method is to use the built in `Edit this file` in the Github UI
4358

44-
1. Run the tests again and make sure they pass!
59+
1. Submit to your fork.
4560

46-
1. Submit to your fork
61+
1. Make a PR as described [here](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) against the 'development' branch.
4762

48-
1. Submit a PR
63+
1. Wait for a review and address any feedback.
64+
While we try and stay on top of all issues and PRs it might take a few days for someone to respond. Politely pinging the PR after a few days with no response is OK, we'll try and respond with a timeline as soon as we are able.
4965

50-
1. Wait for a review, and address any feedback.
66+
1. That's it! When the PR has received :rocket:'s from members of the core team they will merge the PR
5167

5268
<!--
5369
### Updating Documentation

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setup(
77
name='tableaudocumentapi',
8-
version='0.4',
8+
version='0.5',
99
author='Tableau',
1010
author_email='github@tableau.com',
1111
url='https://github.com/tableau/document-api-python',

tableaudocumentapi/connection.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,20 @@ def __init__(self, connxml):
3030
self._username = connxml.get('username')
3131
self._authentication = connxml.get('authentication')
3232
self._class = connxml.get('class')
33+
self._port = connxml.get('port', None)
3334

3435
def __repr__(self):
3536
return "'<Connection server='{}' dbname='{}' @ {}>'".format(self._server, self._dbname, hex(id(self)))
3637

3738
@classmethod
38-
def from_attributes(cls, server, dbname, username, dbclass, authentication=''):
39+
def from_attributes(cls, server, dbname, username, dbclass, port=None, authentication=''):
3940
root = ET.Element('connection', authentication=authentication)
4041
xml = cls(root)
4142
xml.server = server
4243
xml.dbname = dbname
4344
xml.username = username
4445
xml.dbclass = dbclass
46+
xml.port = port
4547

4648
return xml
4749

@@ -133,3 +135,22 @@ def dbclass(self, value):
133135

134136
self._class = value
135137
self._connectionXML.set('class', value)
138+
139+
###########
140+
# port
141+
###########
142+
@property
143+
def port(self):
144+
return self._port
145+
146+
@port.setter
147+
def port(self, value):
148+
self._port = value
149+
# If port is None we remove the element and don't write it to XML
150+
if value is None:
151+
try:
152+
del self._connectionXML.attrib['port']
153+
except KeyError:
154+
pass
155+
else:
156+
self._connectionXML.set('port', value)

tableaudocumentapi/datasource.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ def __init__(self, dsxml, filename=None):
137137
self._name = self._datasourceXML.get('name') or self._datasourceXML.get(
138138
'formatted-name') # TDS files don't have a name attribute
139139
self._version = self._datasourceXML.get('version')
140+
self._caption = self._datasourceXML.get('caption', '')
140141
self._connection_parser = ConnectionParser(
141142
self._datasourceXML, version=self._version)
142143
self._connections = self._connection_parser.get_connections()
@@ -207,6 +208,20 @@ def name(self):
207208
def version(self):
208209
return self._version
209210

211+
@property
212+
def caption(self):
213+
return self._caption
214+
215+
@caption.setter
216+
def caption(self, value):
217+
self._datasourceXML.set('caption', value)
218+
self._caption = value
219+
220+
@caption.deleter
221+
def caption(self):
222+
del self._datasourceXML.attrib['caption']
223+
self._caption = ''
224+
210225
###########
211226
# connections
212227
###########

test/assets/CONNECTION.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<connection authentication='sspi' class='sqlserver' dbname='TestV1' odbc-native-protocol='yes' one-time-sql='' server='mssql2012' username=''></connection>
1+
<connection authentication='sspi' class='sqlserver' dbname='TestV1' odbc-native-protocol='yes' one-time-sql='' server='mssql2012' username='' port='1433'></connection>

test/assets/datasource_test.tds

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?xml version='1.0' encoding='utf-8' ?>
2-
<datasource formatted-name='postgres.1of3kl00aoax5d1a1ejma1397430' inline='true' source-platform='mac' version='9.3' xmlns:user='http://www.tableausoftware.com/xml/user'>
2+
<datasource caption='foo' formatted-name='postgres.1of3kl00aoax5d1a1ejma1397430' inline='true' source-platform='mac'
3+
version='9.3' xmlns:user='http://www.tableausoftware.com/xml/user'>
34
<repository-location />
45
<connection authentication='username-password' class='postgres' dbname='TestV1' odbc-native-protocol='yes' port='5432' server='postgres91.test.tsi.lan' username='test'>
56
<relation name='xy' table='[public].[xy]' type='table' />

test/bvt.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818

1919
TABLEAU_10_TWB = os.path.join(TEST_DIR, 'assets', 'TABLEAU_10_TWB.twb')
2020

21-
TABLEAU_CONNECTION_XML = ET.parse(os.path.join(
22-
TEST_DIR, 'assets', 'CONNECTION.xml')).getroot()
21+
TABLEAU_CONNECTION_XML = os.path.join(TEST_DIR, 'assets', 'CONNECTION.xml')
2322

2423
TABLEAU_10_TWBX = os.path.join(TEST_DIR, 'assets', 'TABLEAU_10_TWBX.twbx')
2524

@@ -51,7 +50,7 @@ def test_can_extract_federated_connections(self):
5150
class ConnectionModelTests(unittest.TestCase):
5251

5352
def setUp(self):
54-
self.connection = TABLEAU_CONNECTION_XML
53+
self.connection = ET.parse(TABLEAU_CONNECTION_XML).getroot()
5554

5655
def test_can_read_attributes_from_connection(self):
5756
conn = Connection(self.connection)
@@ -60,15 +59,24 @@ def test_can_read_attributes_from_connection(self):
6059
self.assertEqual(conn.server, 'mssql2012')
6160
self.assertEqual(conn.dbclass, 'sqlserver')
6261
self.assertEqual(conn.authentication, 'sspi')
62+
self.assertEqual(conn.port, '1433')
6363

6464
def test_can_write_attributes_to_connection(self):
6565
conn = Connection(self.connection)
6666
conn.dbname = 'BubblesInMyDrink'
6767
conn.server = 'mssql2014'
6868
conn.username = 'bob'
69+
conn.port = '1337'
6970
self.assertEqual(conn.dbname, 'BubblesInMyDrink')
7071
self.assertEqual(conn.username, 'bob')
7172
self.assertEqual(conn.server, 'mssql2014')
73+
self.assertEqual(conn.port, '1337')
74+
75+
def test_can_delete_port_from_connection(self):
76+
conn = Connection(self.connection)
77+
conn.port = None
78+
self.assertEqual(conn.port, None)
79+
self.assertIsNone(conn._connectionXML.get('port'))
7280

7381
def test_bad_dbclass_rasies_attribute_error(self):
7482
conn = Connection(self.connection)
@@ -90,11 +98,13 @@ def test_can_create_datasource_from_connections(self):
9098
conn1 = Connection.from_attributes(
9199
server='a', dbname='b', username='c', dbclass='mysql', authentication='d')
92100
conn2 = Connection.from_attributes(
93-
server='1', dbname='2', username='3', dbclass='mysql', authentication='7')
101+
server='1', dbname='2', username='3', dbclass='mysql', port='1337', authentication='7')
94102
ds = Datasource.from_connections('test', connections=[conn1, conn2])
95103

96104
self.assertEqual(ds.connections[0].server, 'a')
105+
self.assertEqual(ds.connections[0].port, None)
97106
self.assertEqual(ds.connections[1].server, '1')
107+
self.assertEqual(ds.connections[1].port, '1337')
98108

99109

100110
class ConnectionParserInComplicatedWorkbooks(unittest.TestCase):

test/test_datasource.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
import unittest
1+
import os
22
import os.path
3+
import shutil
4+
import tempfile
5+
import unittest
6+
37

48
from tableaudocumentapi import Datasource, Workbook
59

@@ -22,6 +26,19 @@ class DataSourceFieldsTDS(unittest.TestCase):
2226

2327
def setUp(self):
2428
self.ds = Datasource.from_file(TEST_TDS_FILE)
29+
self.to_delete = set()
30+
31+
def cleanUp(self):
32+
for path in self.to_delete:
33+
if os.path.isdir(path):
34+
shutil.rmtree(path, ignore_errors=True)
35+
elif os.path.isfile(path):
36+
os.unlink(path)
37+
38+
def get_temp_file(self, filename):
39+
tempdir = tempfile.mkdtemp('tda-datasource')
40+
self.to_delete.add(tempdir)
41+
return os.path.join(tempdir, filename)
2542

2643
def test_datasource_returns_correct_fields(self):
2744
self.assertIsNotNone(self.ds.fields)
@@ -63,6 +80,30 @@ def test_datasource_field_description(self):
6380
self.assertIsNotNone(actual)
6481
self.assertTrue(u'muted gray' in actual)
6582

83+
def test_datasource_caption(self):
84+
actual = self.ds.caption
85+
self.assertIsNotNone(actual)
86+
self.assertEqual(actual, 'foo')
87+
88+
def test_datasource_can_set_caption(self):
89+
filename = self.get_temp_file('test_datasource_can_set_caption')
90+
self.ds.caption = 'bar'
91+
self.ds.save_as(filename)
92+
93+
actual = Datasource.from_file(filename)
94+
self.assertIsNotNone(actual)
95+
self.assertIsNotNone(actual.caption)
96+
self.assertEqual(actual.caption, 'bar')
97+
98+
def test_datasource_can_remove_caption(self):
99+
filename = self.get_temp_file('test_datasource_can_remove_caption')
100+
del self.ds.caption
101+
self.ds.save_as(filename)
102+
103+
actual = Datasource.from_file(filename)
104+
self.assertIsNotNone(actual)
105+
self.assertEqual(actual.caption, '')
106+
66107
def test_datasource_clear_repository_location(self):
67108
filename = os.path.join(TEST_ASSET_DIR, 'clear-repository-test.tds')
68109

0 commit comments

Comments
 (0)