8000 Extract operations to match tabcmd by jacalata · Pull Request #672 · tableau/server-client-python · GitHub
[go: up one dir, main page]

Skip to content

Extract operations to match tabcmd #672

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 6 commits into from
Aug 21, 2020
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 0.13 (19 Aug 2020)
* Add support for basic Extract operations - Create, Delete, en/re/decrypt for site

## 0.12.1 (22 July 2020)

* Fixed login.py sample to properly handle sitename (#652)
Expand Down
16 changes: 16 additions & 0 deletions tableauserverclient/server/endpoint/datasources_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,22 @@ def refresh(self, datasource_item):
new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
return new_job

@api(version='3.5')
def create_extract(self, datasource_item, encrypt=False):
id_ = getattr(datasource_item, 'id', datasource_item)
url = "{0}/{1}/createExtract?encrypt={2}".format(self.baseurl, id_, encrypt)
empty_req = RequestFactory.Empty.empty_req()
server_response = self.post_request(url, empty_req)
new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
return new_job

@api(version='3.5')
def delete_extract(self, datasource_item):
id_ = getattr(datasource_item, 'id', datasource_item)
url = "{0}/{1}/deleteExtract".format(self.baseurl, id_)
empty_req = RequestFactory.Empty.empty_req()
self.post_request(url, empty_req)

# Publish datasource
@api(version="2.0")
@parameter_added_in(connections="2.8")
Expand Down
28 changes: 28 additions & 0 deletions tableauserverclient/server/endpoint/sites_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,31 @@ def create(self, site_item):
new_site = SiteItem.from_response(server_response.content, self.parent_srv.namespace)[0]
logger.info('Created new site (ID: {0})'.format(new_site.id))
return new_site

@api(version="3.5")
def encrypt_extracts(self, site_id):
if not site_id:
error = "Site ID undefined."
raise ValueError(error)
url = "{0}/{1}/encrypt-extracts".format(self.baseurl, site_id)
empty_req = RequestFactory.Empty.empty_req()
self.post_request(url, empty_req)

@api(version="3.5")
def decrypt_extracts(self, site_id):
if not site_id:
error = "Site ID undefined."
raise ValueError(error)
url = "{0}/{1}/decrypt-extracts".format(self.baseurl, site_id)
empty_req = RequestFactory.Empty.empty_req()
self.post_request(url, empty_req)

@api(version="3.5")
def re_encrypt_extracts(self, site_id):
if not site_id:
error = "Site ID undefined."
raise ValueError(error)
url = "{0}/{1}/reencrypt-extracts".format(self.baseurl, site_id)

empty_req = RequestFactory.Empty.empty_req()
self.post_request(url, empty_req)
19 changes: 19 additions & 0 deletions tableauserverclient/server/endpoint/workbooks_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,25 @@ def refresh(self, workbook_id):
new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
return new_job

# create one or more extracts on 1 workbook, optionally encrypted
@api(version='3.5')
def create_extract(self, workbook_item, encrypt=False, includeAll=True, datasources=None):
id_ = getattr(workbook_item, 'id', workbook_item)
url = "{0}/{1}/createExtract?encrypt={2}".format(self.baseurl, id_, encrypt)

datasource_req = RequestFactory.Workbook.embedded_extract_req(includeAll, datasources)
server_response = self.post_request(url, datasource_req)
new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
return new_job

# delete all the extracts on 1 workbook
@api(version='3.5')
def delete_extract(self, workbook_item):
id_ = getattr(workbook_item, 'id', workbook_item)
url = "{0}/{1}/deleteExtract".format(self.baseurl, id_)
empty_req = RequestFactory.Empty.empty_req()
server_response = self.post_request(url, empty_req)

# Delete 1 workbook by id
@api(version="2.0")
def delete(self, workbook_id):
Expand Down
12 changes: 11 additions & 1 deletion tableauserverclient/server/request_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,16 @@ def publish_req_chunked(
parts = {'request_payload': ('', xml_request, 'text/xml')}
return _add_multipart(parts)

@_tsrequest_wrapped
def embedded_extract_req(self, xml_request, include_all=True, datasources=None):
list_element = ET.SubElement(xml_request, 'datasources')
if include_all:
list_element.attrib['includeAll'] = "true"
else:
for datasource_item in datasources:
datasource_element = list_element.SubElement(xml_request, 'datasource')
datasource_element.attrib['id'] = datasource_item.id


class Connection(object):
@_tsrequest_wrapped
Expand Down Expand Up @@ -623,7 +633,7 @@ def create_req(self, xml_request, webhook_item):
webhook.attrib['name'] = webhook_item.name

source = ET.SubElement(webhook, 'webhook-source')
event = ET.SubElement(source, webhook_item._event)
ET.SubElement(source, webhook_item._event)

destination = ET.SubElement(webhook, 'webhook-destination')
post = ET.SubElement(destination, 'webhook-destination-http')
Expand Down
27 changes: 27 additions & 0 deletions test/test_datasource.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,3 +381,30 @@ def test_synchronous_publish_timeout_error(self):
self.assertRaisesRegex(InternalServerError, 'Please use asynchronous publishing to avoid timeouts.',
self.server.datasources.publish, new_datasource,
asset('SampleDS.tds'), publish_mode)

def test_delete_extracts(self):
self.server.version = "3.10"
self.baseurl = self.server.datasources.baseurl
with requests_mock.mock() as m:
m.post(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42/deleteExtract', status_code=200)
self.server.datasources.delete_extract('3cc6cd06-89ce-4fdc-b935-5294135d6d42')

def test_create_extracts(self):
self.server.version = "3.10"
self.baseurl = self.server.datasources.baseurl

response_xml = read_xml_asset(PUBLISH_XML_ASYNC)
with requests_mock.mock() as m:
m.post(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42/createExtract',
status_code=200, text=response_xml)
self.server.datasources.create_extract('3cc6cd06-89ce-4fdc-b935-5294135d6d42')

def test_create_extracts_encrypted(self):
self.server.version = "3.10"
self.baseurl = self.server.datasources.baseurl

response_xml = read_xml_asset(PUBLISH_XML_ASYNC)
with requests_mock.mock() as m:
m.post(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42/createExtract',
status_code=200, text=response_xml)
self.server.datasources.create_extract('3cc6cd06-89ce-4fdc-b935-5294135d6d42', True)
16 changes: 16 additions & 0 deletions test/test_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
class SiteTests(unittest.TestCase):
def setUp(self):
self.server = TSC.Server('http://test')
self.server.version = "3.10"

# Fake signin
self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM'
Expand Down Expand Up @@ -140,3 +141,18 @@ def test_delete(self):

def test_delete_missing_id(self):
self.assertRaises(ValueError, self.server.sites.delete, '')

def test_encrypt(self):
with requests_mock.mock() as m:
m.post(self.baseurl + '/0626857c-1def-4503-a7d8-7907c3ff9d9f/encrypt-extracts', status_code=200)
self.server.sites.encrypt_extracts('0626857c-1def-4503-a7d8-7907c3ff9d9f')

def test_recrypt(self):
with requests_mock.mock() as m:
m.post(self.baseurl + '/0626857c-1def-4503-a7d8-7907c3ff9d9f/reencrypt-extracts', status_code=200)
self.server.sites.re_encrypt_extracts('0626857c-1def-4503-a7d8-7907c3ff9d9f')

def test_decrypt(self):
with requests_mock.mock() as m:
m.post(self.baseurl + '/0626857c-1def-4503-a7d8-7907c3ff9d9f/decrypt-extracts', status_code=200)
self.server.sites.decrypt_extracts('0626857c-1def-4503-a7d8-7907c3ff9d9f')
33 changes: 33 additions & 0 deletions test/test_workbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ def test_publish_with_hidden_view(self):
hidden_views=['GDP per capita'])

request_body = m._adapter.request_history[0]._request.body
# order of attributes in xml is unspecified
self.assertTrue(re.search(rb'<views><view.*?hidden=\"true\".*?\/><\/views>', request_body))
self.assertTrue(re.search(rb'<views><view.*?name=\"GDP per capita\".*?\/><\/views>', request_body))

Expand Down Expand Up @@ -566,3 +567,35 @@ def test_synchronous_publish_timeout_error(self):

self.assertRaisesRegex(InternalServerError, 'Please use asynchronous publishing to avoid timeouts',
self.server.workbooks.publish, new_workbook, asset('SampleWB.twbx'), publish_mode)

def test_delete_extracts_all(self):
self.server.version = "3.10"
self.baseurl = self.server.workbooks.baseurl
with requests_mock.mock() as m:
m.post(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42/deleteExtract', status_code=200)
self.server.workbooks.delete_extract('3cc6cd06-89ce-4fdc-b935-5294135d6d42')

def test_create_extracts_all(self):
self.server.version = "3.10"
self.baseurl = self.server.workbooks.baseurl

with open(PUBLISH_ASYNC_XML, 'rb') as f:
response_xml = f.read().decode('utf-8')
with requests_mock.mock() as m:
m.post(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42/createExtract',
status_code=200, text=response_xml)
self.server.workbooks.create_extract('3cc6cd06-89ce-4fdc-b935-5294135d6d42')

def test_create_extracts_one(self):
self.server.version = "3.10"
self.baseurl = self.server.workbooks.baseurl

datasource = TSC.DatasourceItem('test')
datasource._id = '1f951daf-4061-451a-9df1-69a8062664f2'

with open(PUBLISH_ASYNC_XML, 'rb') as f:
response_xml = f.read().decode('utf-8')
with requests_mock.mock() as m:
m.post(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42/createExtract',
status_code=200, text=response_xml)
self.server.workbooks.create_extract('3cc6cd06-89ce-4fdc-b935-5294135d6d42', False, datasource)
0