tableau/server-client-python

Workbooks,download fails for Tableau Server v.2023.1.7

ElenasemDA opened this issue ยท 22 comments

Describe the bug
Workbooks.download fails with an error in regard to 'filename'.
Versions: Tableau Server 2023.1.7 (API 3.19), tableauserverclient-0.14.1, python 3.7.9
(tested also: Tableau Server 2023.1.7 (API 3.19), tableauserverclient-0.28, python 3.9.13)

To Reproduce
server_int = TSC.Server("https://yousite",use_server_version = True)
tableau_auth_int = TSC.TableauAuth("username", "password", site_id='sitename')

with server_int.auth.sign_in(tableau_auth_int):

workbook= server_int.workbooks.get_by_id('fca33a9a-0139-41b9-b1e7-841a92bf5f92')
wkbk=workbook.name
print(workbook.name)        
print(workbook.id)
file_path = server_int.workbooks.download('fca33a9a-0139-41b9-b1e7-841a92bf5f92')
print("\nDownloaded the file to {0}.".format(file_path))

Results
WorkbookName
fca33a9a-0139-41b9-b1e7-841a92bf5f92

KeyError Traceback (most recent call last)
~\AppData\Local\Temp\1\ipykernel_7340\2448852531.py in
5 print(workbook.name)
6 print(workbook.id)
----> 7 file_path = server_int.workbooks.download('fca33a9a-0139-41b9-b1e7-841a92bf5f92')
8 print("\nDownloaded the file to {0}.".format(file_path))

E:\Anaconda3\envs\Tableau_analytics\lib\site-packages\tableauserverclient\server\endpoint\endpoint.py in wrapper(self, *args, **kwargs)
290 def wrapper(self, *args, **kwargs):
291 self.parent_srv.assert_at_least_version(version, self.class.name)
--> 292 return func(self, *args, **kwargs)
293
294 return wrapper

E:\Anaconda3\envs\Tableau_analytics\lib\site-packages\tableauserverclient\server\endpoint\endpoint.py in wrapper(self, *args, **kwargs)
332 error = "{!r} not available in {}, it will be ignored. Added in {}".format(p, server_ver, min_ver)
333 warnings.warn(error)
--> 334 return func(self, *args, **kwargs)
335
336 return wrapper

E:\Anaconda3\envs\Tableau_analytics\lib\site-packages\tableauserverclient\server\endpoint\endpoint.py in wrapper(self, *args, **kwargs)
332 error = "{!r} not available in {}, it will be ignored. Added in {}".format(p, server_ver, min_ver)
333 warnings.warn(error)
--> 334 return func(self, *args, **kwargs)
335
336 return wrapper

E:\Anaconda3\envs\Tableau_analytics\lib\site-packages\tableauserverclient\server\endpoint\workbooks_endpoint.py in download(self, workbook_id, filepath, include_extract, no_extract)
182 no_extract: Optional[bool] = None,
183 ) -> str:
--> 184 return self.download_revision(workbook_id, None, filepath, include_extract, no_extract)
185
186 # Get all views of workbook

E:\Anaconda3\envs\Tableau_analytics\lib\site-packages\tableauserverclient\server\endpoint\endpoint.py in wrapper(self, *args, **kwargs)
290 def wrapper(self, *args, **kwargs):
291 self.parent_srv.assert_at_least_version(version, self.class.name)
--> 292 return func(self, *args, **kwargs)
293
294 return wrapper

E:\Anaconda3\envs\Tableau_analytics\lib\site-packages\tableauserverclient\server\endpoint\workbooks_endpoint.py in download_revision(self, workbook_id, revision_number, filepath, include_extract, no_extract)
488 return_path = filepath
489 else:
--> 490 filename = to_filename(os.path.basename(params["filename"]))
491 download_path = make_download_path(filepath, filename)
492 with open(download_path, "wb") as f:

KeyError: 'filename'

NOTE: Be careful not to post user names, passwords, auth tokens or any other private or sensitive information.

It looks like the filename header in the following was changed to filename*, throwing the error:

with closing(self.get_request(url, parameters={"stream": True})) as server_response:
_, params = cgi.parse_header(server_response.headers["Content-Disposition"])
if isinstance(filepath, io_types_w):
for chunk in server_response.iter_content(1024): # 1KB
filepath.write(chunk)
return_path = filepath
else:
filename = to_filename(os.path.basename(params["filename"]))
download_path = make_download_path(filepath, filename)
with open(download_path, "wb") as f:
for chunk in server_response.iter_content(1024): # 1KB
f.write(chunk)
return_path = os.path.abspath(download_path)

@hersh-gupta @ElenasemDA we have run into this issue as well. Any rough idea of how long things like this usually take to get resolved?

Any updates on this? We are facing the same issue.

hahu66 commented

Same issue here, after upgrading from 2023.1.5 to 2023.1.7
Looks like a change in Rest API version 3.19?
There is no "filename" return in the 'params' set on line 484

File "C:\Program Files\Python38\lib\site-packages\tableauserverclient\server\endpoint\workbooks_endpoint.py", line 490, in download_revision
filename = to_filename(os.path.basename(params["filename"]))
KeyError: 'filename'

Hello. We faced the same problem. We are looking forward to the fix.

Update From our side, we did a test and upgraded our dev server to 2023.3.0 and it looks like the issue has been resolved. However, we've learnt from past experiences to never upgrade our production server to an 20XX.X.0 version as there are usually very buggy and cause more damage than they fix. We're going to wait till 2023.3.1 comes out.

Any update on this issue? we are facing the same.

Also getting this error. Awaiting a fix.

I'm also encountering this error. Looking forward to a resolution.

facing this same issue after our server upgrade. Please push a fix to the python client.

We use the snippet below instead of calling the server.workbooks.download (v.2023.1.7). Check if you can incorporate it to your processes.

with closing(server_int.workbooks.get_request(url, parameters={"stream": True})) as server_response:
_, params = cgi.parse_header(server_response.headers["Content-Disposition"])
if isinstance(ind_path, io_types_w):
for chunk in server_response.iter_content(1024): # 1KB
ind_path.write(chunk)
return_path = ind_path
else:
filename = to_filename(os.path.basename(params["filename*"]))
filename = filename.replace("UTF-8","").replace("+"," ")
filename = filename.replace("28","(").replace("29",")")
download_path = make_download_path(ind_path, filename)
with open(download_path, "wb") as f:
for chunk in server_response.iter_content(1024): # 1KB
f.write(chunk)
return_path = os.path.abspath(download_path)

We use the snippet below instead of calling the server.workbooks.download (v.2023.1.7). Check if you can incorporate it to your processes.

with closing(server_int.workbooks.get_request(url, parameters={"stream": True})) as server_response: _, params = cgi.parse_header(server_response.headers["Content-Disposition"]) if isinstance(ind_path, io_types_w): for chunk in server_response.iter_content(1024): # 1KB ind_path.write(chunk) return_path = ind_path else: filename = to_filename(os.path.basename(params["filename*"])) filename = filename.replace("UTF-8","").replace("+"," ") filename = filename.replace("28","(").replace("29",")") download_path = make_download_path(ind_path, filename) with open(download_path, "wb") as f: for chunk in server_response.iter_content(1024): # 1KB f.write(chunk) return_path = os.path.abspath(download_path)

This is my workaround snippet. maybe you'll find it useful as well until the fix:

def _decode_filename(self, params):
    if 'filename*' in params:
        encoded_filename = params['filename*']
        if encoded_filename.startswith("UTF-8''"):
            encoded_filename = encoded_filename[
                               7:]  # strip off the "UTF-8''" prefix
        filename = urllib.parse.unquote_plus(
            encoded_filename)  # decode percent-encoded octets
    elif 'filename' in params:
        filename = params['filename']
    else:
        filename = None

    return filename

# Download 1 workbook revision by revision number
@api(version="2.3")
def download_revision(
    self,
    workbook_id: str,
    revision_number: str,
    filepath: Optional[PathOrFileW] = None,
    include_extract: bool = True,
    no_extract: Optional[bool] = None,
) -> PathOrFileW:
    if not workbook_id:
        error = "Workbook ID undefined."
        raise ValueError(error)
    if revision_number is None:
        url = "{0}/{1}/content".format(self.baseurl, workbook_id)
    else:
        url = "{0}/{1}/revisions/{2}/content".format(self.baseurl, workbook_id, revision_number)

    if no_extract is False or no_extract is True:
        import warnings

        warnings.warn(
            "no_extract is deprecated, use include_extract instead.",
            DeprecationWarning,
        )
        include_extract = not no_extract

    if not include_extract:
        url += "?includeExtract=False"

    with closing(self.get_request(url, parameters={"stream": True})) as server_response:
        _, params = cgi.parse_header(server_response.headers["Content-Disposition"])
        if isinstance(filepath, io_types_w):
            for chunk in server_response.iter_content(1024):  # 1KB
                filepath.write(chunk)
            return_path = filepath
        else:
            filename = self._decode_filename(params)
            filename = to_filename(os.path.basename(filename))
            download_path = make_download_path(filepath, filename)
            with open(download_path, "wb") as f:
                for chunk in server_response.iter_content(1024):  # 1KB
                    f.write(chunk)
            return_path = os.path.abspath(download_path)

    logger.info(
        "Downloaded workbook revision {0} to {1} (ID: {2})".format(revision_number, return_path, workbook_id)
    )
    return return_path

Same issue for us since upgrading to 2023.1.7 - looking forward to a fix.

facing the same issue in 2023.1.7. Looking forward to a resolution.

Faced the same issue. You can fix it by opening /{path/to/your/python-libraries}/site-packages/tableauserverclient/server/endpoint/workbooks_endpoint.py and on line 490 update:
"...(params["filename"]))"
to
"...(params["filename*"]))"
Workbooks should now download but they have "UTF-8" appended to beginning of their name so you can write some code to take care of that until they patch this issue.
Will probably need to edit the datasources file as well to handle downloading datasources.

Our code uses both workbook endpoint and datasource endpoint, and is a mixture of upgraded as well as old version servers, so these are the changes I did to get it working for our usecase.

/venv/lib/python3.8/site-packages/tableauserverclient/server/endpoint/workbooks_endpoint.py

205             try:
206                 filename = to_filename(os.path.basename(params["filename*"]))
207                 filename = filename.replace("UTF-8","").replace("+"," ")
208             except KeyError:
209                 filename = to_filename(os.path.basename(params["filename"]))

venv/lib/python3.8/site-packages/tableauserverclient/server/endpoint/datasources_endpoint.py

161             try:
162                 filename = to_filename(os.path.basename(params["filename*"]))
163                 filename = filename.replace("UTF-8","").replace("+"," ")
164             except KeyError:
165                 filename = to_filename(os.path.basename(params["filename"]))

We also are affected by this issue.

For anyone facing this issue, please try downloaded the new v0.29 release. It includes #1330 which should fix this.

unfortunately, i get an issue with 0.29 to uploading datasources : 400011: Bad Request
There was a problem publishing the file '17851:753240939c9444f4a364f54c39c26e3e-1:0'

@SebMontreal06 That is different than the problem being discussed in this issue. Please open a new issue.