10000 Merge multicredential and as job (#313) · jsonhuff/server-client-python@59bf892 · GitHub
[go: up one dir, main page]

Skip to content

Commit 59bf892

Browse files
author
Russell Hay
authored
Merge multicredential and as job (tableau#313)
Merging things since as_job was added in master by accident
1 parent 3a00122 commit 59bf892

File tree

10 files changed

+130
-27
lines changed

10 files changed

+130
-27
lines changed

docs/docs/api-ref.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1077,7 +1077,7 @@ The project resources for Tableau are defined in the `ProjectItem` class. The cl
10771077

10781078
```py
10791079

1080-
ProjectItem(name, description=None, content_permissions=None)
1080+
ProjectItem(name, description=None, content_permissions=None, parent_id=None)
10811081

10821082
```
10831083
The project resources for Tableau are defined in the `ProjectItem` class. The class corresponds to the project resources you can access using the Tableau Server REST API.
@@ -1090,6 +1090,7 @@ Name | Description
10901090
`name` | Name of the project.
10911091
`description` | The description of the project.
10921092
`id` | The project id.
1093+
`parent_id` | The parent project id.
10931094

10941095

10951096

samples/publish_workbook.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def main():
3030
parser.add_argument('--filepath', '-f', required=True, help='filepath to the workbook to publish')
3131
parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error',
3232
help='desired logging level (set to error by default)')
33+
parser.add_argument('--as-job', '-a', help='Publishing asynchronously', action='store_true')
3334

3435
args = parser.parse_args()
3536

@@ -67,11 +68,14 @@ def main():
6768
# Step 3: If default project is found, form a new workbook item and publish.
6869
if default_project is not None:
6970
new_workbook = TSC.WorkbookItem(default_project.id)
70-
new_workbook = server.workbooks.publish(new_workbook,
71-
args.filepath,
72-
overwrite_true,
73-
connections=all_connections)
74-
print("Workbook published. ID: {0}".format(new_workbook.id))
71+
if args.as_job:
72+
new_job = server.workbooks.publish(new_workbook, args.filepath, overwrite_true,
73+
connections=all_connections, as_job=args.as_job)
74+
print("Workbook published. JOB ID: {0}".format(new_job.id))
75+
else:
76+
new_workbook = server.workbooks.publish(new_workbook, args.filepath, overwrite_true,
77+
connections=all_connections, as_job=args.as_job)
78+
print("Workbook published. ID: {0}".format(new_workbook.id))
7579
else:
7680
error = "The default project could not be found."
7781
raise LookupError(error)

tableauserverclient/models/job_item.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import xml.etree.ElementTree as ET
22
from ..datetime_helpers import parse_datetime
33
from .target import Target
4+
from ..datetime_helpers import parse_datetime
45

56

67
class JobItem(object):
7-
def __init__(self, id_, job_type, created_at, started_at=None, completed_at=None, finish_code=0):
8+
def __init__(self, id_, job_type, progress, created_at, started_at=None, completed_at=None, finish_code=0):
89
self._id = id_
910
self._type = job_type
11+
self._progress = progress
1012
self._created_at = created_at
1113
self._started_at = started_at
1214
self._completed_at = completed_at
@@ -20,6 +22,10 @@ def id(self):
2022
def type(self):
2123
return self._type
2224

25+
@property
26+
def progress(self):
27+
return self._progress
28+
2329
@property
2430
def created_at(self):
2531
return self._created_at
@@ -38,7 +44,7 @@ def finish_code(self):
3844

3945
def __repr__(self):
4046
return "<Job#{_id} {_type} created_at({_created_at}) started_at({_started_at}) completed_at({_completed_at})" \
41-
" finish_code({_finish_code})>".format(**self.__dict__)
47+
" progress ({_progress}) finish_code({_finish_code})>".format(**self.__dict__)
4248

4349
@classmethod
4450
def from_response(cls, xml, ns):
@@ -54,11 +60,12 @@ def from_response(cls, xml, ns):
5460
def _parse_element(cls, element, ns):
5561
id_ = element.get('id', None)
5662
type_ = element.get('type', None)
57-
created_at = element.get('createdAt', None)
58-
started_at = element.get('startedAt', None)
59-
completed_at = element.get('completedAt', None)
63+
progress = element.get('progress', None)
64+
created_at = parse_datetime(element.get('createdAt', None))
65+
started_at = parse_datetime(element.get('startedAt', None))
66+
completed_at = parse_datetime(element.get('completedAt', None))
6067
finish_code = element.get('finishCode', -1)
61-
return cls(id_, type_, created_at, started_at, completed_at, finish_code)
68+
return cls(id_, type_, progress, created_at, started_at, completed_at, finish_code)
6269

6370

6471
class BackgroundJobItem(object):

tableauserverclient/models/user_item.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ class Roles:
1616
ViewerWithPublish = 'ViewerWithPublish'
1717
Guest = 'Guest'
1818

19+
Creator = 'Creator'
20+
Explorer = 'Explorer'
21+
ExplorerCanPublish = 'ExplorerCanPublish'
22+
ReadOnly = 'ReadOnly'
23+
SiteAdministratorCreator = 'SiteAdministratorCreator'
24+
SiteAdministratorExplorer = 'SiteAdministratorExplorer'
25+
UnlicensedWithPublish = 'UnlicensedWithPublish'
26+
1927
class Auth:
2028
SAML = 'SAML'
2129
ServerDefault = 'ServerDefault'
@@ -147,3 +155,6 @@ def _parse_element(user_xml, ns):
147155
domain_name = domain_elem.get('name', None)
148156

149157
return id, name, site_role, last_login, external_auth_user_id, fullname, email, auth_setting, domain_name
158+
159+
def __repr__(self):
160+
return "<User {} name={} role={}>".format(self.id, self.name, self.site_role)

tableauserverclient/server/endpoint/datasources_endpoint.py

Lines changed: 15 additions & 5 deletions
< F987 /tr>
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,9 @@ def refresh(self, datasource_item):
151151

152152
# Publish datasource
153153
@api(version="2.0")
154-
@parameter_added_in(connections="99.99")
155-
def publish(self, datasource_item, file_path, mode, connection_credentials=None, connections=None):
154+
@parameter_added_in(connections="2.8")
155+
@parameter_added_in(as_job='3.0')
156+
def publish(self, datasource_item, file_path, mode, connection_credentials=None, connections=None, as_job=False):
156157
if not os.path.isfile(file_path):
157158
error = "File path does not lead to an existing file."
158159
raise IOError(error)
@@ -175,6 +176,9 @@ def publish(self, datasource_item, file_path, mode, connection_credentials=None,
175176
if mode == self.parent_srv.PublishMode.Overwrite or mode == self.parent_srv.PublishMode.Append:
176177
url += '&{0}=true'.format(mode.lower())
177178

179+
if as_job:
180+
url += '&{0}=true'.format('asJob')
181+
178182
# Determine if chunking is required (64MB is the limit for single upload method)
179183
if os.path.getsize(file_path) >= FILESIZE_LIMIT:
180184
logger.info('Publishing {0} to server with chunking method (datasource over 64MB)'.format(filename))
@@ -193,6 +197,12 @@ def publish(self, datasource_item, file_path, mode, connection_credentials=None,
193197
connection_credentials,
194198
connections)
195199
server_response = self.post_request(url, xml_request, content_type)
196-
new_datasource = DatasourceItem.from_response(server_response.content, self.parent_srv.namespace)[0]
197-
logger.info('Published {0} (ID: {1})'.format(filename, new_datasource.id))
198-
return new_datasource
200+
201+
if as_job:
202+
new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
203+
logger.info('Published {0} (JOB_ID: {1}'.format(filename, new_job.id))
204+
return new_job
205+
else:
206+
new_datasource = DatasourceItem.from_response(server_response.content, self.parent_srv.namespace)[0]
207+
logger.info('Published {0} (ID: {1})'.format(filename, new_datasource.id))
208+
return new_datasource

tableauserverclient/server/endpoint/workbooks_endpoint.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,9 @@ def _get_wb_preview_image(self, workbook_item):
199199

200200
# Publishes workbook. Chunking method if file over 64MB
201201
@api(version="2.0")
202+
@parameter_added_in(as_job='3.0')
202203
@parameter_added_in(connections='2.8')
203-
def publish(self, workbook_item, file_path, mode, connection_credentials=None, connections=None):
204+
def publish(self, workbook_item, file_path, mode, connection_credentials=None, connections=None, as_job=False):
204205

205206
if connection_credentials is not None:
206207
import warnings
@@ -232,6 +233,9 @@ def publish(self, workbook_item, file_path, mode, connection_credentials=None, c
232233
error = 'Workbooks cannot be appended.'
233234
raise ValueError(error)
234235

236+
if as_job:
237+
url += '&{0}=true'.format('asJob')
238+
235239
# Determine if chunking is required (64MB is the limit for single upload method)
236240
if os.path.getsize(file_path) >= FILESIZE_LIMIT:
237241
logger.info('Publishing {0} to server with chunking method (workbook over 64MB)'.format(filename))
@@ -253,6 +257,11 @@ def publish(self, workbook_item, file_path, mode, connection_credentials=None, c
253257
connections=connections)
254258
logger.debug('Request xml: {0} '.format(xml_request[:1000]))
255259
server_response = self.post_request(url, xml_request, content_type)
256-
new_workbook = WorkbookItem.from_response(server_response.content, self.parent_srv.namespace)[0]
257-
logger.info('Published {0} (ID: {1})'.format(filename, new_workbook.id))
258-
return new_workbook
260+
if as_job:
261+
new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0]
262+
logger.info('Published {0} (JOB_ID: {1}'.format(filename, new_job.id))
263+
return new_job
264+
else:
265+
new_workbook = WorkbookItem.from_response(server_response.content, self.parent_srv.namespace)[0]
266+
logger.info('Published {0} (ID: {1})'.format(filename, new_workbook.id))
267+
return new_workbook
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
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-3.0.xsd">
3+
<job id="9a373058-af5f-4f83-8662-98b3e0228a73" mode="Asynchronous" type="PublishDatasource" progress="0" createdAt="2018-06-30T00:54:54Z" finishCode="1"/>
4+
</tsResponse>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
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-3.0.xsd">
3+
<job id="7c3d599e-949f-44c3-94a1-f30ba85757e4" mode="Asynchronous" type="PublishWorkbook" progress="0" createdAt="2018-06-29T23:22:32Z" finishCode="1"/>
4+
</tsResponse>

test/test_datasource.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
GET_BY_ID_XML = 'datasource_get_by_id.xml'
1414
POPULATE_CONNECTIONS_XML = 'datasource_populate_connections.xml'
1515
PUBLISH_XML = 'datasource_publish.xml'
16+
PUBLISH_XML_ASYNC = 'datasource_publish_async.xml'
1617
UPDATE_XML = 'datasource_update.xml'
1718
UPDATE_CONNECTION_XML = 'datasource_connection_update.xml'
1819

@@ -178,9 +179,11 @@ def test_publish(self):
178179
with requests_mock.mock() as m:
179180
m.post(self.baseurl, text=response_xml)
180181
new_datasource = TSC.DatasourceItem('SampleDS', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760')
182+
publish_mode = self.server.PublishMode.CreateNew
183+
181184
new_datasource = self.server.datasources.publish(new_datasource,
182185
asset('SampleDS.tds'),
183-
mode=self.server.PublishMode.CreateNew)
186+
mode=publish_mode)
184187

185188
self.assertEqual('e76a1461-3b1d-4588-bf1b-17551a879ad9', new_datasource.id)
186189
self.assertEqual('SampleDS', new_datasource.name)
@@ -192,6 +195,24 @@ def test_publish(self):
192195
self.assertEqual('default', new_datasource.project_name)
193196
self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', new_datasource.owner_id)
194197

198+
def test_publish_async(self):
199+
response_xml = read_xml_asset(PUBLISH_XML_ASYNC)
200+
with requests_mock.mock() as m:
201+
m.post(self.baseurl, text=response_xml)
202+
new_datasource = TSC.DatasourceItem('SampleDS', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760')
203+
publish_mode = self.server.PublishMode.CreateNew
204+
205+
new_job = self.server.datasources.publish(new_datasource,
206+
asset('SampleDS.tds'),
207+
mode=publish_mode,
208+
as_job=True)
209+
210+
self.assertEqual('9a373058-af5f-4f83-8662-98b3e0228a73', new_job.id)
211+
self.assertEqual('PublishDatasource', new_job.type)
212+
self.assertEqual('0', new_job.progress)
213+
self.assertEqual('2018-06-30T00:54:54Z', format_datetime(new_job.created_at))
214+
self.assertEqual('1', new_job.finish_code)
215+
195216
def test_delete(self):
196217
with requests_mock.mock() as m:
197218
m.delete(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb', status_code=204)

test/test_workbook.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
POPULATE_VIEWS_XML = os.path.join(TEST_ASSET_DIR, 'workbook_populate_views.xml')
1919
POPULATE_VIEWS_USAGE_XML = os.path.join(TEST_ASSET_DIR, 'workbook_populate_views_usage.xml')
2020
PUBLISH_XML = os.path.join(TEST_ASSET_DIR, 'workbook_publish.xml')
21+
PUBLISH_ASYNC_XML = os.path.join(TEST_ASSET_DIR, 'workbook_publish_async.xml')
2122
UPDATE_XML = os.path.join(TEST_ASSET_DIR, 'workbook_update.xml')
2223

2324

@@ -290,10 +291,17 @@ def test_publish(self):
290291
response_xml = f.read().decode('utf-8')
291292
with requests_mock.mock() as m:
292293
m.post(self.baseurl, text=response_xml)
293-
new_workbook = TSC.WorkbookItem(name='Sample', show_tabs=False,
294+
295+
new_workbook = TSC.WorkbookItem(name='Sample',
296+
show_tabs=False,
294297
project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760')
295-
new_workbook = self.server.workbooks.publish(new_workbook, os.path.join(TEST_ASSET_DIR, 'SampleWB.twbx'),
296-
self.server.PublishMode.CreateNew)
298+
299+
sample_workbok = os.path.join(TEST_ASSET_DIR, 'SampleWB.twbx')
300+
publish_mode = self.server.PublishMode.CreateNew
301+
302+
new_workbook = self.server.workbooks.publish(new_workbook,
303+
sample_workbok,
304+
publish_mode)
297305

298306
self.assertEqual('a8076ca1-e9d8-495e-bae6-c684dbb55836', new_workbook.id)
299307
self.assertEqual('RESTAPISample', new_workbook.name)
@@ -309,10 +317,34 @@ def test_publish(self):
309317
self.assertEqual('GDP per capita', new_workbook.views[0].name)
310318
self.assertEqual('RESTAPISample_0/sheets/GDPpercapita', new_workbook.views[0].content_url)
311319

320+
def test_publish_async(self):
321+
with open(PUBLISH_ASYNC_XML, 'rb') as f:
322+
response_xml = f.read().decode('utf-8')
323+
with requests_mock.mock() as m:
324+
m.post(self.baseurl, text=response_xml)
325+
326+
new_workbook = TSC.WorkbookItem(name='Sample',
327+
show_tabs=False,
328+
project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760')
329+
330+
sample_workbok = os.path.join(TEST_ASSET_DIR, 'SampleWB.twbx')
331+
publish_mode = self.server.PublishMode.CreateNew
332+
333+
new_job = self.server.workbooks.publish(new_workbook,
334+
sample_workbok,
335+
publish_mode,
336+
as_job=True)
337+
338+
self.assertEqual('7c3d599e-949f-44c3-94a1-f30ba85757e4', new_job.id)
339+
self.assertEqual('PublishWorkbook', new_job.type)
340+
self.assertEqual('0', new_job.progress)
341+
self.assertEqual('2018-06-29T23:22:32Z', format_datetime(new_job.created_at))
342+
self.assertEqual('1', new_job.finish_code)
343+
312344
def test_publish_invalid_file(self):
313345
new_workbook = TSC.WorkbookItem('test', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760')
314-
self.assertRaises(IOError, self.server.workbooks.publish, new_workbook,
315-
'.', self.server.PublishMode.CreateNew)
346+
self.assertRaises(IOError, self.server.workbooks.publish, new_workbook, '.',
347+
self.server.PublishMode.CreateNew)
316348

317349
def test_publish_invalid_file_type(self):
318350
new_workbook = TSC.WorkbookItem('test', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760')

0 commit comments

Comments
 (0)
0