8000 Implement call to move to highest supported REST API version (#100) · SnarkyPapi/server-client-python@bfff665 · GitHub
[go: up one dir, main page]

Skip to content

Commit bfff665

Browse files
authored
Implement call to move to highest supported REST API version (tableau#100)
Yaay hackathon! This PR adds the ability to detect the highest supported version a given Tableau Server supports. In 10.1 and later this makes use of the `ServerInfo` endpoint, and in others it falls back to `auth.xml` which is guaranteed to be present on all versions of Server that we would care about. If we can't determine the version for some reason, we default to 2.1, which is the last 'major' release of the API (with permissions semantics changes). This currently doesn't have an auto-upgrade flag, that can come in another PR after more discussion
1 parent fc2b9bf commit bfff665

File tree

7 files changed

+95
-5
lines changed

7 files changed

+95
-5
lines changed

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>

test/assets/server_info_auth_info.xml

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)
0