Skip to content

Commit 266bef4

Browse files
committed
Implement call to move to highest supported REST API version
1 parent 39f4b22 commit 266bef4

File tree

10 files changed

+103
-8
lines changed

10 files changed

+103
-8
lines changed

.travis.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,6 @@ script:
1414
# Tests
1515
- python setup.py test
1616
# pep8 - disabled for now until we can scrub the files to make sure we pass before turning it on
17-
- pycodestyle .
17+
- pycodestyle tableauserverclient
18+
- pycodestyle test
19+
- pycodestyle samples

samples/initialize_server.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import logging
1111
import glob
1212

13+
1314
def main():
1415
parser = argparse.ArgumentParser(description='Initialize a server with content.')
1516
parser.add_argument('--server', '-s', required=True, help='server address')
@@ -47,12 +48,12 @@ def main():
4748
# Create the site if it doesn't exist
4849
if existing_site is None:
4950
print("Site not found: {0} Creating it...").format(args.site)
50-
new_site = TSC.SiteItem(name=args.site, content_url=args.site.replace(" ", ""), admin_mode=TSC.SiteItem.AdminMode.ContentAndUsers)
51+
new_site = TSC.SiteItem(name=args.site, content_url=args.site.replace(" ", ""),
52+
admin_mode=TSC.SiteItem.AdminMode.ContentAndUsers)
5153
server.sites.create(new_site)
5254
else:
5355
print("Site {0} exists. Moving on...").format(args.site)
5456

55-
5657
################################################################################
5758
# Step 3: Sign-in to our target site
5859
################################################################################
@@ -82,6 +83,7 @@ def main():
8283
publish_datasources_to_site(server_upload, project, args.datasources_folder)
8384
publish_workbooks_to_site(server_upload, project, args.workbooks_folder)
8485

86+
8587
def publish_datasources_to_site(server_object, project, folder):
8688
path = folder + '/*.tds*'
8789

samples/pagination_sample.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,6 @@ def main():
6666
# >>> request_options = TSC.RequestOptions(pagesize=1000)
6767
# >>> all_workbooks = list(TSC.Pager(server.workbooks, request_options))
6868

69+
6970
if __name__ == '__main__':
7071
main()

tableauserverclient/server/endpoint/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from .auth_endpoint import Auth
22
from .datasources_endpoint import Datasources
33
from .endpoint import Endpoint
4-
from .exceptions import ServerResponseError, MissingRequiredFieldError
4+
from .exceptions import ServerResponseError, MissingRequiredFieldError, ServerInfoEndpointNotFoundError
55
from .groups_endpoint import Groups
66
from .projects_endpoint import Projects
77
from .schedules_endpoint import Schedules

tableauserverclient/server/endpoint/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ def from_response(cls, resp):
2424

2525
class MissingRequiredFieldError(Exception):
2626
pass
27+
28+
29+
class ServerInfoEndpointNotFoundError(Exception):
30+
pass

tableauserverclient/server/endpoint/server_info_endpoint.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .endpoint import Endpoint
2+
from .exceptions import ServerResponseError, ServerInfoEndpointNotFoundError
23
from ...models import ServerInfoItem
34
import logging
45

@@ -12,6 +13,11 @@ def baseurl(self):
1213

1314
def get(self):
1415
""" Retrieve the server info for the server. This is an unauthenticated call """
15-
server_response = self.get_unauthenticated_request(self.baseurl)
16+
try:
17+
server_response = self.get_unauthenticated_request(self.baseurl)
18+
except ServerResponseError as e:
19+
if e.code == "404003":
20+
raise ServerInfoEndpointNotFoundError
21+
1622
server_info = ServerInfoItem.from_response(server_response.content)
1723
return server_info

tableauserverclient/server/server.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
1+
import xml.etree.ElementTree as ET
2+
13
from .exceptions import NotSignedInError
2-
from .endpoint import Sites, Views, Users, Groups, Workbooks, Datasources, Projects, Auth, Schedules, ServerInfo
4+
from .endpoint import Sites, Views, Users, Groups, Workbooks, Datasources, Projects, Auth, \
5+
Schedules, ServerInfo, ServerInfoEndpointNotFoundError
36

47
import requests
58

9+
_PRODUCT_TO_REST_VERSION = {
10+
'10.0': '2.3',
11+
'9.3': '2.2',
12+
'9.2': '2.1',
13+
'9.1': '2.0',
14+
'9.0': '2.0'
15+
}
16+
617

718
class Server(object):
819
class PublishMode:
@@ -47,6 +58,29 @@ def _set_auth(self, site_id, user_id, auth_token):
4758
self._user_id = user_id
4859
self._auth_token = auth_token
4960

61+
def _get_legacy_version(self):
62+
response = self._session.get(self.server_address + "/auth?format=xml")
63+
info_xml = ET.fromstring(response.content)
64+
prod_version = info_xml.find('.//product_version').text
65+
version = _PRODUCT_TO_REST_VERSION.get(prod_version, '2.1') # 2.1
66+
return version
67+
68+
def _determine_highest_version(self):
69+
try:
70+
old_version = self.version
71+
self.version = "2.4"
72+
version = self.server_info.get().rest_api_version
73+
except ServerInfoEndpointNotFoundError:
74+
version = self._get_legacy_version()
75+
76+
finally:
77+
self.version = old_version
78+
79+
return version
80+
81+
def use_highest_version(self):
82+
self.version = self._determine_highest_version()
83+
5084
@property
5185
def baseurl(self):
5286
return "{0}/api/{1}".format(self._server_address, str(self.version))

test/assets/server_info_404.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<tsResponse xmlns="http://tableau.com/api" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-2.3.xsd">
3+
<error code="404003">
4+
<summary>Resource Not Found</summary>
5+
<detail>Unknown resource '/2.4/serverInfo' specified in URI.</detail>
6+
</error>
7+
</tsResponse>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<authinfo>
2+
<version version="0.31">
3+
<api_version>0.31</api_version>
4+
<server_api_version>0.31</server_api_version>
5+
<document_version>9.2</document_version>
6+
<product_version>9.3</product_version>
7+
<external_version>9.3.4</external_version>
8+
<build>hello.16.1106.2025</build>
9+
<publishing_method>unrestricted</publishing_method>
10+
<mobile_api_version>2.6</mobile_api_version>
11+
</version>
12+
</authinfo>

test/test_server_info.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,48 @@
66
TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets')
77

88
SERVER_INFO_GET_XML = os.path.join(TEST_ASSET_DIR, 'server_info_get.xml')
9+
SERVER_INFO_404 = os.path.join(TEST_ASSET_DIR, 'server_info_404.xml')
10+
SERVER_INFO_AUTH_INFO_XML = os.path.join(TEST_ASSET_DIR, 'server_info_auth_info.xml')
911

1012

1113
class ServerInfoTests(unittest.TestCase):
1214
def setUp(self):
1315
self.server = TSC.Server('http://test')
14-
self.server.version = '2.4'
1516
self.baseurl = self.server.server_info.baseurl
1617

1718
def test_server_info_get(self):
1819
with open(SERVER_INFO_GET_XML, 'rb') as f:
1920
response_xml = f.read().decode('utf-8')
2021
with requests_mock.mock() as m:
21-
m.get(self.baseurl, text=response_xml)
22+
self.server.version = '2.4'
23+
m.get(self.server.server_info.baseurl, text=response_xml)
2224
actual = self.server.server_info.get()
2325

2426
self.assertEqual('10.1.0', actual.product_version)
2527
self.assertEqual('10100.16.1024.2100', actual.build_number)
2628
self.assertEqual('2.4', actual.rest_api_version)
29+
30+
def test_server_info_use_highest_version_downgrades(self):
31+
with open(SERVER_INFO_AUTH_INFO_XML, 'rb') as f:
32+
# This is the auth.xml endpoint present back to 9.0 Servers
33+
auth_response_xml = f.read().decode('utf-8')
34+
with open(SERVER_INFO_404, 'rb') as f:
35+
# 10.1 serverInfo response
36+
si_response_xml = f.read().decode('utf-8')
37+
with requests_mock.mock() as m:
38+
# Return a 404 for serverInfo so we can pretend this is an old Server
39+
m.get(self.server.server_address + "/api/2.4/serverInfo", text=si_response_xml, status_code=404)
40+
m.get(self.server.server_address + "/auth?format=xml", text=auth_response_xml)
41+
self.server.use_highest_version()
42+
self.assertEqual(self.server.version, '2.2')
43+
44+
def test_server_info_use_highest_version_upgrades(self):
45+
with open(SERVER_INFO_GET_XML, 'rb') as f:
46+
si_response_xml = f.read().decode('utf-8')
47+
with requests_mock.mock() as m:
48+
m.get(self.server.server_address + "/api/2.4/serverInfo", text=si_response_xml)
49+
# Pretend we're old
50+
self.server.version = '2.0'
51+
self.server.use_highest_version()
52+
# Did we upgrade to 2.4?
53+
self.assertEqual(self.server.version, '2.4')

0 commit comments

Comments
 (0)