tableau / server-client-python

A Python library for the Tableau Server REST API
https://tableau.github.io/server-client-python/
MIT License
655 stars 420 forks source link

0.29 Release: NotSignedInError - Intermittent issue pulling PDFs #1337

Closed rayanedlebi-jb closed 7 months ago

rayanedlebi-jb commented 7 months ago

Describe the bug Since the release of 0.29, pulling a PDF from a Tableau dashboard intermittently fails. Specific error is NotSignedInError (more details below). The issue having to do with concurrent runs using the same PAT has been ruled out, as part of my debugging involved generating a new token (which didn't resolve the issue).

Current workaround is to utilize 0.28 version instead of 0.29.

NotSignedInError: (b'<?xml version=\'1.0\' encoding=\'UTF-8\'?><tsResponse xmlns="http://tableau.com/api" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api https://help.tableau.com/samples/en-us/rest_api/ts-api_3_21.xsd"><error code="401002"><summary>Unauthorized Access</summary><detail>Invalid authentication credentials were provided.</detail></error></tsResponse>', 'https://prod-useast-b.online.tableau.com/api/3.21/auth/signout')

Versions

To Reproduce Issue is intermittent, but always happens with retrieving the pdf attribute of a ViewItem() object. Example code is below:

import tableauserverclient as tsc

# Tableau Creds
tableau_token_name = "TOKEN_NAME"
tableau_personal_access_token = "TOKEN_VALUE"
site_id = "SITE_ID"
server_url = "TABLEAU_URL"

# Workbook ID from Tableau
workbook_id = "WORKBOOK_ID"

# Authenticate to Tableau
tableau_authentication = tsc.PersonalAccessTokenAuth(token_name=tableau_token_name, personal_access_token=tableau_personal_access_token, site_id=site_id)
server = tsc.Server(server_address=server_url, use_server_version=True)

pdf_req_option = tsc.PDFRequestOptions(maxage=1)

# Login to Tableau
with server.auth.sign_in_with_personal_access_token(tableau_authentication):
    # Get workbook
    workbook = server.workbooks.get_by_id(workbook_id)

    # Get workbook URL
    workbook_web_url = workbook.webpage_url

    # Populate views of workbook
    server.workbooks.populate_views(workbook)

    # Get first view of workbook
    dashboard_view = workbook.views[0]

    # Populate image of the dashboard view
    server.views.populate_image(dashboard_view, image_req_option)

    # Get image of dashboard view
    dashboard_view_image = dashboard_view.image

    # Populate pdf of the dashboard view
    server.views.populate_pdf(dashboard_view, pdf_req_option)

    # Get pdf of the dashboard view 
    #### LINE THAT USUALLY CAUSES NotSignedInError ####
    dashboard_view_pdf = dashboard_view.pdf

# Log out of Tableau
server.auth.sign_out()

Results Specific error:

NotSignedInError                          Traceback (most recent call last)
<command-xyz>
     24 
     25     # Get pdf of the dashboard view
---> 26     dashboard_view_pdf = dashboard_view.pdf
     27 
     28 # Log out of Tableau

/lib/python3.9/site-packages/tableauserverclient/server/endpoint/auth_endpoint.py in __exit__(self, exc_type, exc_val, exc_tb)
     25 
     26         def __exit__(self, exc_type, exc_val, exc_tb):
---> 27             self._callback()
     28 
     29     @property

/lib/python3.9/site-packages/tableauserverclient/server/endpoint/endpoint.py in wrapper(self, *args, **kwargs)
    289         def wrapper(self, *args, **kwargs):
    290             self.parent_srv.assert_at_least_version(version, self.__class__.__name__)
--> 291             return func(self, *args, **kwargs)
    292 
    293         return wrapper

/lib/python3.9/site-packages/tableauserverclient/server/endpoint/auth_endpoint.py in sign_out(self)
     83         if not self.parent_srv.is_signed_in():
     84             return
---> 85         self.post_request(url, "")
     86         self.parent_srv._clear_auth()
     87         logger.info("Signed out")

/lib/python3.9/site-packages/tableauserverclient/server/endpoint/endpoint.py in post_request(self, url, xml_request, content_type, parameters)
    246 
    247     def post_request(self, url, xml_request, content_type=XML_CONTENT_TYPE, parameters=None):
--> 248         return self._make_request(
    249             self.parent_srv.session.post,
    250             url,

/lib/python3.9/site-packages/tableauserverclient/server/endpoint/endpoint.py in _make_request(self, method, url, content, auth_token, content_type, parameters)
    163         if isinstance(server_response, Exception):
    164             raise server_response
--> 165         self._check_status(server_response, url)
    166 
    167         loggable_response = self.log_response_safely(server_response)

/lib/python3.9/site-packages/tableauserverclient/server/endpoint/endpoint.py in _check_status(self, server_response, url)
    184                 if server_response.status_code == 401:
    185                     # TODO: catch this in server.py and attempt to sign in again, in case it's a session expiry
--> 186                     raise NotSignedInError(server_response.content, url)
    187 
    188                 raise ServerResponseError.from_response(server_response.content, self.parent_srv.namespace, url)

I'll add comments to this Issue if anything further to report on this. Please let me know if any questions, thanks.

Feromond commented 7 months ago

I have the exact same issue after the latest changes made in version 0.29. I follow similar use case. I see the same error as was shown here. If I pin my tableau server client version to 0.28 then I have no problems.

`

datasource_id = datasource_dict.get(data_source_selection)

with server.auth.sign_in(tableau_auth):
    datasource = server.datasources.get_by_id(datasource_id)
    refreshed_datasource_job = server.datasources.refresh(datasource)
    print(f'Extract refresh task for datasource {datasource_id} has been started.')`
bcantoni commented 7 months ago

I think there's a general bug in 0.29 that can impact multiple different types of endpoints. I've written that up in #1342

One workaround as both have noted above is to revert back to 0.28.

Another option that should work with 0.29 is to change your code from using the with statement to the older linear style.

For example, from:

with server.auth.sign_in(tableau_auth):
    datasource = server.datasources.get_by_id(datasource_id)
    refreshed_datasource_job = server.datasources.refresh(datasource)
    print(f'Extract refresh task for datasource {datasource_id} has been started.')`

to:

server.auth.sign_in(tableau_auth)

datasource = server.datasources.get_by_id(datasource_id)
refreshed_datasource_job = server.datasources.refresh(datasource)
print(f'Extract refresh task for datasource {datasource_id} has been started.')`
bcantoni commented 7 months ago

@rayanedlebi-jb @Feromond we have a new v0.30 release which we believe should fix this: https://github.com/tableau/server-client-python/releases/tag/v0.30

rayanedlebi-jb commented 7 months ago

Thank you @bcantoni - I'll test and let you know if any issues. I appreciate your prompt response to this!