This is going to be a rambling post which hopefully helps anyone actually implementing the Tableau REST API. For experienced web developers this stuff may be 101, but I never claimed any experience. The Tableau Server REST API is finicky to say the least, and when you do something it doesn’t like, it tends to express it as a 400 series HTTP code error. This tends to bubble up as an exception in your HTTP library, and there may be a whole different pathway of handling things than a 200 series response.
When I initially implemented the tableau_rest_api library, I tried to avoid errors as much as possible by doing ‘pre-checks’; if you were going to add a user, the library would request all the existing users and see if the user already exists. Recently when performing some scale testing, I recognized this was causing a huge performance penalty between the extra request and processing each time. I had originally just assumed that any 40X HTTP error code was an exception worth ending the script for; but in reality everything above 400 is possibly sending back information which can be used to recover and do a different logical action. For example, attempting to add a username that already exists causes a 409 error code; but most scripts don’t need to terminate with exceptions if this exists. You should be able to simply keep moving, logging what happened and why.
Here’s an example of a 409 when trying to add a group when a group with that name already exists
<tsResponse xmlns="http://tableausoftware.com/api" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableausoftware.com/api http://tableausoftware.com/api/ts-api-2.0.xsd"> <error code="409006"> <summary>Resource Conflict</summary> <detail>A project already exists with the name 'Existing Project'</detail> </error> </tsResponse>
Now the __make_request method of the RestXmlRequest class returns a RecoverableHTTPException exception with the HTTP error code, the Tableau Error Code, and an attempt to pull any LUID that exists in the detail. The LUID match was implemented in the hope that the LUID of the existing resource would be returned. In practice, it seems that only the Site LUID comes back in some cases, making it not as useful. The add methods now simply do the query for the existing LUID only when there is an Exception thrown. This gives the exact same functionality as the original method of pre-checks, but only has the extra calls in the case of the Exception instead of each time.
In fact, the send_delete_request method as a whole treats all 404 errors as recoverable and doesn’t raise the exception any further (other than logging it). What does it matter if we actually deleted or it didn’t exist already?
For other methods, like add_user_by_username, there is now an ‘update_if_exists’ flag that, when set to True, makes the command work like an ‘upsert’ — rather than throwing an exception, it just updates the existing username with the details. Otherwise it returns an AlreadyExistsException that can be handled by your script. create_group and create_project now always return the existing group or project’s LUID; I can’t think of a reason to raise an exception (what would you do with the knowledge?) For the other methods, catching the RecoverableHTTPException is the best way to go.