10000 Merge pull request #894 from vogelsgesang/expose-fileuploads · cfmayden/server-client-python@ae56feb · GitHub
[go: up one dir, main page]

Skip to content

Commit ae56feb

Browse files
authored
Merge pull request tableau#894 from vogelsgesang/expose-fileuploads
Expose the `fileuploads` API endpoint and improve exception safety
2 parents 29a71b0 + 3abe4e9 commit ae56feb

File tree

7 files changed

+34
-47
lines changed

7 files changed

+34
-47
lines changed

tableauserverclient/server/endpoint/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .databases_endpoint import Databases
66
from .endpoint import Endpoint
77
from .favorites_endpoint import Favorites
8+
from .fileuploads_endpoint import Fileuploads
89
from .flows_endpoint import Flows
910
from .exceptions import (
1011
ServerResponseError,

tableauserverclient/server/endpoint/datasources_endpoint.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from .exceptions import InternalServerError, MissingRequiredFieldError
33
from .permissions_endpoint import _PermissionsEndpoint
44
from .dqw_endpoint import _DataQualityWarningEndpoint
5-
from .fileuploads_endpoint import Fileuploads
65
from .resource_tagger import _ResourceTagger
76
from .. import RequestFactory, DatasourceItem, PaginationItem, ConnectionItem
87
from ..query import QuerySet
@@ -244,7 +243,7 @@ def publish(
244243
# Determine if chunking is required (64MB is the limit for single upload method)
245244
if file_size >= FILESIZE_LIMIT:
246245
logger.info("Publishing {0} to server with chunking method (datasource over 64MB)".format(filename))
247-
upload_session_id = Fileuploads.upload_chunks(self.parent_srv, file)
246+
upload_session_id = self.parent_srv.fileuploads.upload(file)
248247
url = "{0}&uploadSessionId={1}".format(url, upload_session_id)
249248
xml_request, content_type = RequestFactory.Datasource.publish_req_chunked(
250249
datasource_item, connection_credentials, connections

tableauserverclient/server/endpoint/fileuploads_endpoint.py

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
class Fileuploads(Endpoint):
1515
def __init__(self, parent_srv):
1616
super(Fileuploads, self).__init__(parent_srv)
17-
self.upload_id = ""
1817

1918
@property
2019
def baseurl(self):
@@ -25,45 +24,40 @@ def initiate(self):
2524
url = self.baseurl
2625
server_response = self.post_request(url, "")
2726
fileupload_item = FileuploadItem.from_response(server_response.content, self.parent_srv.namespace)
28-
self.upload_id = fileupload_item.upload_session_id
29-
logger.info("Initiated file upload session (ID: {0})".format(self.upload_id))
30-
return self.upload_id
27+
upload_id = fileupload_item.upload_session_id
28+
logger.info("Initiated file upload session (ID: {0})".format(upload_id))
29+
return upload_id
3130

3231
@api(version="2.0")
33-
def append(self, xml_request, content_type):
34-
if not self.upload_id:
35-
error = "File upload session must be initiated first."
36-
raise MissingRequiredFieldError(error)
37-
url = "{0}/{1}".format(self.baseurl, self.upload_id)
38-
server_response = self.put_request(url, xml_request, content_type)
39-
logger.info("Uploading a chunk to session (ID: {0})".format(self.upload_id))
32+
def append(self, upload_id, data, content_type):
33+
url = "{0}/{1}".format(self.baseurl, upload_id)
34+
server_response = self.put_request(url, data, content_type)
35+
logger.info("Uploading a chunk to session (ID: {0})".format(upload_id))
4036
return FileuploadItem.from_response(server_response.content, self.parent_srv.namespace)
4137

42-
def read_chunks(self, file):
38+
def _read_chunks(self, file):
4339
file_opened = False
4440
try:
4541
file_content = open(file, "rb")
4642
file_opened = True
4743
except TypeError:
4844
file_content = file
4945

50-
while True:
51-
chunked_content = file_content.read(CHUNK_SIZE)
52-
if not chunked_content:
53-
if file_opened:
54-
file_content.close()
55-
break
56-
yield chunked_content
57-
58-
@classmethod
59-
def upload_chunks(cls, parent_srv, file):
60-
file_uploader = cls(parent_srv)
61-
upload_id = file_uploader.initiate()
62-
63-
chunks = file_uploader.read_chunks(file)
64-
for chunk in chunks:
65-
xml_request, content_type = RequestFactory.Fileupload.chunk_req(chunk)
66-
fileupload_item = file_uploader.append(xml_request, content_type)
46+
try:
47+
while True:
48+
chunked_content = file_content.read(CHUNK_SIZE)
49+
if not chunked_content:
50+
break
51+
yield chunked_content
52+
finally:
53+
if file_opened:
54+
file_content.close()
55+
56+
def upload(self, file):
57+
upload_id = self.initiate()
58+
for chunk in self._read_chunks(file):
59+
request, content_type = RequestFactory.Fileupload.chunk_req(chunk)
60+
fileupload_item = self.append(upload_id, request, content_type)
6761
logger.info("\tPublished {0}MB".format(fileupload_item.file_size))
68-
logger.info("\tCommitting file upload...")
62+
logger.info("File upload finished (ID: {0})".format(upload_id))
6963
return upload_id

tableauserverclient/server/endpoint/flows_endpoint.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from .exceptions import InternalServerError, MissingRequiredFieldError
33
from .permissions_endpoint import _PermissionsEndpoint
44
from .dqw_endpoint import _DataQualityWarningEndpoint
5-
from .fileuploads_endpoint import Fileuploads
65
from .resource_tagger import _ResourceTagger
76
from .. import RequestFactory, FlowItem, PaginationItem, ConnectionItem
87
from ...filesys_helpers import to_filename, make_download_path
@@ -169,7 +168,7 @@ def publish(self, flow_item, file_path, mode, connections=None):
169168
# Determine if chunking is required (64MB is the limit for single upload method)
170169
if os.path.getsize(file_path) >= FILESIZE_LIMIT:
171170
logger.info("Publishing {0} to server with chunking method (flow over 64MB)".format(filename))
172-
upload_session_id = Fileuploads.upload_chunks(self.parent_srv, file_path)
171+
upload_session_id = self.parent_srv.fileuploads.upload(file_path)
173172
url = "{0}&uploadSessionId={1}".format(url, upload_session_id)
174173
xml_request, content_type = RequestFactory.Flow.publish_req_chunked(flow_item, connections)
175174
else:

tableauserverclient/server/endpoint/workbooks_endpoint.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from .endpoint import QuerysetEndpoint, api, parameter_added_in
22
from .exceptions import InternalServerError, MissingRequiredFieldError
33
from .permissions_endpoint import _PermissionsEndpoint
4-
from .fileuploads_endpoint import Fileuploads
54
from .resource_tagger import _ResourceTagger
65
from .. import RequestFactory, WorkbookItem, ConnectionItem, ViewItem, PaginationItem
76
from ...models.job_item import JobItem
@@ -344,7 +343,7 @@ def publish(
344343
# Determine if chunking is required (64MB is the limit for single upload method)
345344
if file_size >= FILESIZE_LIMIT:
346345
logger.info("Publishing {0} to server with chunking method (workbook over 64MB)".format(workbook_item.name))
347-
upload_session_id = Fileuploads.upload_chunks(self.parent_srv, file)
346+
upload_session_id = self.parent_srv.fileuploads.upload(file)
348347
url = "{0}&uploadSessionId={1}".format(url, upload_session_id)
349348
conn_creds = connection_credentials
350349
xml_request, content_type = RequestFactory.Workbook.publish_req_chunked(

tableauserverclient/server/server.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
DataAccelerationReport,
2525
Favorites,
2626
DataAlerts,
27+
Fileuploads,
2728
)
2829
from .endpoint.exceptions import (
2930
EndpointUnavailableError,
@@ -82,6 +83,7 @@ def __init__(self, server_address, use_server_version=False):
8283
self.webhooks = Webhooks(self)
8384
self.data_acceleration_report = DataAccelerationReport(self)
8485
self.data_alerts = DataAlerts(self)
86+
self.fileuploads = Fileuploads(self)
8587
self._namespace = Namespace()
8688

8789
if use_server_version:

test/test_fileuploads.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
from ._utils import asset
66
from tableauserverclient.server import Server
7-
from tableauserverclient.server.endpoint.fileuploads_endpoint import Fileuploads
87

98
TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets')
109
FILEUPLOAD_INITIALIZE = os.path.join(TEST_ASSET_DIR, 'fileupload_initialize.xml')
@@ -22,23 +21,18 @@ def setUp(self):
2221
self.baseurl = '{}/sites/{}/fileUploads'.format(self.server.baseurl, self.server.site_id)
2322

2423
def test_read_chunks_file_path(self):
25-
fileuploads = Fileuploads(self.server)
26-
2724
file_path = asset('SampleWB.twbx')
28-
chunks = fileuploads.read_chunks(file_path)
25+
chunks = self.server.fileuploads._read_chunks(file_path)
2926
for chunk in chunks:
3027
self.assertIsNotNone(chunk)
3128

3229
def test_read_chunks_file_object(self):
33-
fileuploads = Fileuploads(self.server)
34-
3530
with open(asset('SampleWB.twbx'), 'rb') as f:
36-
chunks = fileuploads.read_chunks(f)
31+
chunks = self.server.fileuploads._read_chunks(f)
3732
for chunk in chunks:
3833
self.assertIsNotNone(chunk)
3934

4035
def test_upload_chunks_file_path(self):
41-
fileuploads = Fileuploads(self.server)
4236
file_path = asset('SampleWB.twbx')
4337
upload_id = '7720:170fe6b1c1c7422dadff20f944d58a52-1:0'
4438

@@ -49,12 +43,11 @@ def test_upload_chunks_file_path(self):
4943
with requests_mock.mock() as m:
5044
m.post(self.baseurl, text=initialize_response_xml)
5145
m.put(self.baseurl + '/' + upload_id, text=append_response_xml)
52-
actual = fileuploads.upload_chunks(self.server, file_path)
46+
actual = self.server.fileuploads.upload(file_path)
5347

5448
self.assertEqual(upload_id, actual)
5549

5650
def test_upload_chunks_file_object(self):
57-
fileuploads = Fileuploads(self.server)
5851
upload_id = '7720:170fe6b1c1c7422dadff20f944d58a52-1:0'
5952

6053
with open(asset('SampleWB.twbx'), 'rb') as file_content:
@@ -65,6 +58,6 @@ def test_upload_chunks_file_object(self):
6558
with requests_mock.mock() as m:
6659
m.post(self.baseurl, text=initialize_response_xml)
6760
m.put(self.baseurl + '/' + upload_id, text=append_response_xml)
68-
actual = fileuploads.upload_chunks(self.server, file_content)
61+
actual = self.server.fileuploads.upload(file_content)
6962

7063
self.assertEqual(upload_id, actual)

0 commit comments

Comments
 (0)
0