From 58239dc868909b214e4d32b9383a11183a6d6c52 Mon Sep 17 00:00:00 2001 From: T8y8 Date: Thu, 11 May 2017 15:21:00 -0700 Subject: [PATCH 1/5] Support for Certified Data Sources in the REST API --- tableauserverclient/models/datasource_item.py | 57 +++++++++++++++---- .../server/endpoint/datasources_endpoint.py | 2 +- tableauserverclient/server/request_factory.py | 7 +++ test/assets/datasource_update.xml | 2 +- test/test_datasource.py | 3 + 5 files changed, 58 insertions(+), 13 deletions(-) diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index 498f4e277..8070f2869 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -1,6 +1,6 @@ import xml.etree.ElementTree as ET from .exceptions import UnpopulatedPropertyError -from .property_decorators import property_not_nullable +from .property_decorators import property_not_nullable, property_is_boolean from .tag_item import TagItem from .. import NAMESPACE from ..datetime_helpers import parse_datetime @@ -17,6 +17,8 @@ def __init__(self, project_id, name=None): self._initial_tags = set() self._project_name = None self._updated_at = None + self._certified = None + self._certification_note = None self.name = name self.owner_id = None self.project_id = project_id @@ -37,6 +39,24 @@ def content_url(self): def created_at(self): return self._created_at + @property + def certified(self): + return self._certified + + @certified.setter + @property_not_nullable + @property_is_boolean + def certified(self, value): + self._certified = value + + @property + def certification_note(self): + return self._certification_note + + @certification_note.setter + def certification_note(self, value): + self._certification_note = value + @property def id(self): return self._id @@ -65,16 +85,18 @@ def updated_at(self): def _set_connections(self, connections): self._connections = connections - def _parse_common_tags(self, datasource_xml): + def _parse_common_elements(self, datasource_xml): if not isinstance(datasource_xml, ET.Element): datasource_xml = ET.fromstring(datasource_xml).find('.//t:datasource', namespaces=NAMESPACE) if datasource_xml is not None: - (_, _, _, _, _, updated_at, _, project_id, project_name, owner_id) = self._parse_element(datasource_xml) - self._set_values(None, None, None, None, None, updated_at, None, project_id, project_name, owner_id) + (_, _, _, _, _, updated_at, _, project_id, project_name, owner_id, + certified, certification_note) = self._parse_element(datasource_xml) + self._set_values(None, None, None, None, None, updated_at, None, project_id, + project_name, owner_id, certified, certification_note) return self def _set_values(self, id, name, datasource_type, content_url, created_at, - updated_at, tags, project_id, project_name, owner_id): + updated_at, tags, project_id, project_name, owner_id, certified, certification_note): if id is not None: self._id = id if name: @@ -96,6 +118,10 @@ def _set_values(self, id, name, datasource_type, content_url, created_at, self._project_name = project_name if owner_id: self.owner_id = owner_id + if certified: + self.certified = certified + if certification_note: + self.certification_note = certification_note @classmethod def from_response(cls, resp): @@ -104,22 +130,30 @@ def from_response(cls, resp): all_datasource_xml = parsed_response.findall('.//t:datasource', namespaces=NAMESPACE) for datasource_xml in all_datasource_xml: - (id, name, datasource_type, content_url, created_at, updated_at, - tags, project_id, project_name, owner_id) = cls._parse_element(datasource_xml) + (id_, name, datasource_type, content_url, created_at, updated_at, + tags, project_id, project_name, owner_id, + certified, certification_note) = cls._parse_element(datasource_xml) datasource_item = cls(project_id) - datasource_item._set_values(id, name, datasource_type, content_url, created_at, updated_at, - tags, None, project_name, owner_id) + datasource_item._set_values(id_, name, datasource_type, content_url, created_at, updated_at, + tags, None, project_name, owner_id, certified, certification_note) all_datasource_items.append(datasource_item) return all_datasource_items @staticmethod def _parse_element(datasource_xml): - id = datasource_xml.get('id', None) + blorp = datasource_xml + id_ = datasource_xml.get('id', None) name = datasource_xml.get('name', None) datasource_type = datasource_xml.get('type', None) content_url = datasource_xml.get('contentUrl', None) created_at = parse_datetime(datasource_xml.get('createdAt', None)) updated_at = parse_datetime(datasource_xml.get('updatedAt', None)) + certification_note = datasource_xml.get('certificationNote', None) + + certified = datasource_xml.get('isCertified', None) + + if certified: + certified = str(certified).lower() == 'true' tags = None tags_elem = datasource_xml.find('.//t:tags', namespaces=NAMESPACE) @@ -138,4 +172,5 @@ def _parse_element(datasource_xml): if owner_elem is not None: owner_id = owner_elem.get('id', None) - return id, name, datasource_type, content_url, created_at, updated_at, tags, project_id, project_name, owner_id + return (id_, name, datasource_type, content_url, created_at, updated_at, tags, project_id, + project_name, owner_id, certified, certification_note) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 3d4c070fb..c18639b62 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -112,7 +112,7 @@ def update(self, datasource_item): server_response = self.put_request(url, update_req) logger.info('Updated datasource item (ID: {0})'.format(datasource_item.id)) updated_datasource = copy.copy(datasource_item) - return updated_datasource._parse_common_tags(server_response.content) + return updated_datasource._parse_common_elements(server_response.content) # Publish datasource @api(version="2.0") diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 1762cd3bd..282275ce8 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -65,6 +65,13 @@ def update_req(self, datasource_item): if datasource_item.owner_id: owner_element = ET.SubElement(datasource_element, 'owner') owner_element.attrib['id'] = datasource_item.owner_id + + if datasource_item.certified: + datasource_element.attrib['isCertified'] = str(datasource_item.certified).lower() + + if datasource_item.certification_note: + datasource_element.attrib['certificationNote'] = str(datasource_item.certification_note) + return ET.tostring(xml_request) def publish_req(self, datasource_item, filename, file_contents, connection_credentials=None): diff --git a/test/assets/datasource_update.xml b/test/assets/datasource_update.xml index 637bc0187..3b4c36e58 100644 --- a/test/assets/datasource_update.xml +++ b/test/assets/datasource_update.xml @@ -1,6 +1,6 @@ - + diff --git a/test/test_datasource.py b/test/test_datasource.py index d75a29d85..5954e967b 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -93,11 +93,14 @@ def test_update(self): single_datasource = TSC.DatasourceItem('test', '1d0304cd-3796-429f-b815-7258370b9b74') single_datasource.owner_id = 'dd2239f6-ddf1-4107-981a-4cf94e415794' single_datasource._id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' + single_datasource.certified = True + single_datasource.certification_note = "Warning, here be dragons." single_datasource = self.server.datasources.update(single_datasource) self.assertEqual('9dbd2263-16b5-46e1-9c43-a76bb8ab65fb', single_datasource.id) self.assertEqual('1d0304cd-3796-429f-b815-7258370b9b74', single_datasource.project_id) self.assertEqual('dd2239f6-ddf1-4107-981a-4cf94e415794', single_datasource.owner_id) + self.assertEqual(True, single_datasource.certified) def test_update_copy_fields(self): with open(UPDATE_XML, 'rb') as f: From 67f0a4fa9a907f19a818a5d46a9d11eb79601084 Mon Sep 17 00:00:00 2001 From: T8y8 Date: Fri, 12 May 2017 11:11:19 -0700 Subject: [PATCH 2/5] Remove debug lines --- tableauserverclient/models/datasource_item.py | 5 +---- test/test_datasource.py | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index 8070f2869..d0f3dbfa2 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -141,7 +141,6 @@ def from_response(cls, resp): @staticmethod def _parse_element(datasource_xml): - blorp = datasource_xml id_ = datasource_xml.get('id', None) name = datasource_xml.get('name', None) datasource_type = datasource_xml.get('type', None) @@ -151,9 +150,7 @@ def _parse_element(datasource_xml): certification_note = datasource_xml.get('certificationNote', None) certified = datasource_xml.get('isCertified', None) - - if certified: - certified = str(certified).lower() == 'true' + certified = str(certified).lower() == 'true' tags = None tags_elem = datasource_xml.find('.//t:tags', namespaces=NAMESPACE) diff --git a/test/test_datasource.py b/test/test_datasource.py index 5954e967b..80e61159e 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -101,6 +101,7 @@ def test_update(self): self.assertEqual('1d0304cd-3796-429f-b815-7258370b9b74', single_datasource.project_id) self.assertEqual('dd2239f6-ddf1-4107-981a-4cf94e415794', single_datasource.owner_id) self.assertEqual(True, single_datasource.certified) + self.assertEqual("Warning, here be dragons.", single_datasource.certification_note) def test_update_copy_fields(self): with open(UPDATE_XML, 'rb') as f: From 219c01f37944f2dc190cb76d5d1a5cf808d10059 Mon Sep 17 00:00:00 2001 From: T8y8 Date: Fri, 12 May 2017 11:27:00 -0700 Subject: [PATCH 3/5] Fix a bug where we said None instead of False --- tableauserverclient/models/datasource_item.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index d0f3dbfa2..7bbb78b2a 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -118,10 +118,9 @@ def _set_values(self, id, name, datasource_type, content_url, created_at, self._project_name = project_name if owner_id: self.owner_id = owner_id - if certified: - self.certified = certified if certification_note: self.certification_note = certification_note + self.certified = certified # Always True/False, not conditional @classmethod def from_response(cls, resp): @@ -148,9 +147,7 @@ def _parse_element(datasource_xml): created_at = parse_datetime(datasource_xml.get('createdAt', None)) updated_at = parse_datetime(datasource_xml.get('updatedAt', None)) certification_note = datasource_xml.get('certificationNote', None) - - certified = datasource_xml.get('isCertified', None) - certified = str(certified).lower() == 'true' + certified = str(datasource_xml.get('isCertified', None)).lower() == 'true' tags = None tags_elem = datasource_xml.find('.//t:tags', namespaces=NAMESPACE) From 87027e2c80e2777d491fd0067fc7c88b5c7545ed Mon Sep 17 00:00:00 2001 From: T8y8 Date: Fri, 12 May 2017 11:29:38 -0700 Subject: [PATCH 4/5] pep8 --- tableauserverclient/models/datasource_item.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index 7bbb78b2a..f481e3b10 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -147,7 +147,7 @@ def _parse_element(datasource_xml): created_at = parse_datetime(datasource_xml.get('createdAt', None)) updated_at = parse_datetime(datasource_xml.get('updatedAt', None)) certification_note = datasource_xml.get('certificationNote', None) - certified = str(datasource_xml.get('isCertified', None)).lower() == 'true' + certified = str(datasource_xml.get('isCertified', None)).lower() == 'true' tags = None tags_elem = datasource_xml.find('.//t:tags', namespaces=NAMESPACE) From 3916c6b99abceed80e1de4e1f13e9481fae81377 Mon Sep 17 00:00:00 2001 From: T8y8 Date: Fri, 12 May 2017 11:44:58 -0700 Subject: [PATCH 5/5] We should always serialize the bool --- tableauserverclient/server/request_factory.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 282275ce8..9dbaaacba 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -66,8 +66,7 @@ def update_req(self, datasource_item): owner_element = ET.SubElement(datasource_element, 'owner') owner_element.attrib['id'] = datasource_item.owner_id - if datasource_item.certified: - datasource_element.attrib['isCertified'] = str(datasource_item.certified).lower() + datasource_element.attrib['isCertified'] = str(datasource_item.certified).lower() if datasource_item.certification_note: datasource_element.attrib['certificationNote'] = str(datasource_item.certification_note)