bryanthowell-tableau / tableau_tools Goto Github PK
View Code? Open in Web Editor NEWPackage containing Tableau REST API, XML modification, tabcmd and repository tools
License: Other
Package containing Tableau REST API, XML modification, tabcmd and repository tools
License: Other
Hi,
The TableauParameters init() code chokes on parameters with the text "Parameter" in the name.
The code assumes they have the format: [Parameter #]
and tries to cast the second "word" in the parameter name to an int.
This fails if you have a parameter called (for example) "Country Name Parameter" (the xml name attribute is [Country Name Parameter]
).
One method to fix the error might be to use a regular expression matcher rather than string.find().
e.g.:
...
import re
...
else:
self.log(u'Parameter XML passed in, finding essential characteristics')
self.ds_xml = datasource_xml
params_xml = self.ds_xml.findall(u'./column')
numberedParameterRegex = re.compile('\[Parameter (\d+)\]')
for column in params_xml:
alias = column.get(u'caption')
internal_name = column.get(u'name') # type: unicode
# Parameters are all given internal name [Parameter #], unless they are copies where they
# end with (copy) h/t Jeff James for discovering
regexMatch = numberedParameterRegex.match(internal_name)
if regexMatch and regexMatch.group(1):
param_num = int(regexMatch.group(1))
# Move up the highest_param_num counter for when you add new ones
if param_num > self._highest_param_num:
self._highest_param_num = param_num
...
Regards,
Andrew
Is it compatible with python 3? I'm trying to use the TableauRestApiConnection to signin to the server. I get an error -
Traceback (most recent call last):
File "program.py", line 32, in
from tableau_tools.tableau_rest_api import *
File "C:\Python34\lib\site-packages\tableau_tools__init__.py", line 2, in
from tableau_base import TableauBase
File "C:\Python34\Lib\site-packages\tableau_tools\tableau_base.py", line 3, in
from logger import Logger
File "C:\Python34\Lib\site-packages\tableau_tools\logger.py", line 11
print u"Error: File '{}' cannot be opened to write for logging".format(filen
ame)
^
SyntaxError: invalid syntax
When installing via pipenv, tableau_tools gets installed from pypi because it is listed in the requirements.txt. Additionally, when trying to install package with pipenv install -e .
, the following error is produced
Installing -e ....
Obtaining file:///path_here/tableau_tools
Complete output from command python setup.py egg_info:
running egg_info
creating tableau_tools.egg-info
writing requirements to tableau_tools.egg-info/requires.txt
writing tableau_tools.egg-info/PKG-INFO
writing top-level names to tableau_tools.egg-info/top_level.txt
writing dependency_links to tableau_tools.egg-info/dependency_links.txt
writing manifest file 'tableau_tools.egg-info/SOURCES.txt'
error: package directory 'tableau_tools' does not exist
----------------------------------------
Error: An error occurred while installing -e .!
Command "python setup.py egg_info" failed with error code 1 in /path_here/tableau_tools/
This is likely caused by a bug in -e .. Report this to its maintainers.
The same issues occurs in Python 3.
tableau_tools Version = 4.5.3
Python Version = 2.7.10
OS Version = macOS High Sierra 10.13.3
Tableau Server Version = 10.5
Hi,
I'm getting an error refreshing an extract using the API 2.8 call for update_datasource_now. I get the following:
HTTPError: 400 Client Error: Bad Request for url: https://server/api/2.8/sites/{site_id}/datasources/{ds_id}/refresh
I am able to manually refresh data sources directly on Tableau Server. Do I need specific permissions to do the same via code? Currently, my site role is Publisher.
Thanks
can we create a tableau extract without using tableau but by using tableau_tools?
In tableau_rest_api_connection, starting on line 121 you have:
if new_server_address is not None:
c.set(u'serverAddress="{}" ', new_server_address)
if new_server_port is not None:
c.set(u'serverPort="{}" ', new_server_port)
if new_connection_username is not None:
c.set(u'userName="{}" ', new_connection_username)
if new_connection_username is not None:
c.set(u'password="{}"', new_connection_password)
You need to remove '="{}"' from each unicode string. It misformats the connection xml payload and causes an error. Look at the function above for an example of how you are correctly using it.
Change it to this:
if new_server_address is not None:
c.set(u'serverAddress', new_server_address)
if new_server_port is not None:
c.set(u'serverPort', new_server_port)
if new_connection_username is not None:
c.set(u'userName', new_connection_username)
if new_connection_username is not None:
c.set(u'password', new_connection_password)
We've lately upgraded Tableau Server to:
Tableau Server Version: 10.3.2 (10300.17.0728.2252) 64-bit Windows
We've started seeing issues with querying list of workbooks by username - only first 99 users are taken in the querying pool, if the user is latter on the user list, the app will throw NoMatchFoundException, since it cannot find the user it's querying the workbooks for.
We're guessing this might be some kind of pagination issue - is there anything on API level that can be set to resolve this? Or is it some kind of server-side settings we need to change?
I believe the Tabcmd.create_export command's User Impersonation does not work on Python 3.4+. Tracing the error led me to tableau_http.py which calls request.add_data(). The add_data function was removed from urllib.
Publish call fails for extracts larger than 20mb.
The error occurs when tableau_rest_api_connection.py runs append_to_file_upload, specifically the publish += content. The error is TypeError: must be str, not bytes.
I dug in and can change the single_upload_limit to 64, but that doesn't solve the problem for extracts larger than this.
I've seen similar issues reported for workbooks, but I guess they both use the same functions.
delete_tags_from_datasource builds a url to delete tags from views not datasources.
Regards,
Andrew
Tableau connection currently assumes "schema" to be an alias for "dbname", whereas Athena uses both these fields in the xml, and in fact expects them to be different.
This is problematic when replicating publishing connections, as doing a conn.schema = u'whatever' actually modifies the "dbname" field in the xml, rather than the "schema" field, as would be proper.
I'm using the latest version of the tableau_tools library to try and create new data sources from scratch.
I've been successful in creating a new file and adding a connection to my database, but the file is not valid without adding at least one relation to the TDS file. I saw in the code that you have a function set_first_table()
which sets up the main relation, but while the main_table_relation
object is set in the TableauDatasource object, it is never actually added to the data source xml.
I have a Parameters data source which has copied parameters. Some of them have been copied more than once. In that case, Tableau appends '(copy 2)' or '(copy #)' depending on how many times it's been copied. The if internal_name.find condition should account for that possibility. Thank you!
Starting at line 43:
# Parameters are all given internal name [Parameter #], unless they are copies where they
# end with (copy) h/t Jeff James for discovering
if internal_name.find(u'(copy)') == -1:
If you call query_datasource with a name AND a project id, the method returns the first datasource in that project, no matter what the name.
This appears to be due to the method getting all datasources, then doing a findall by name, ignoring that result and doing a findall by project id on the full list and returning the first datasource!
PS: If your server has a lot of datasources, this method takes a very long time to return due to the REST call returning all datasources first. Can you update it to use name:eq: filter and then filter the result by project id (if required)?
Thanks,
Andrew
Hi,
I've been experiencing one particular issue for a couple of days now, and I'm hoping you might be able to help. I have a TWBX file saved locally (by nature of TWBX it includes an extract) for which I am attempting to make a copy with certain extract filters added. However despite seeing the successful addition of these filters within python (datasource extract_filters are present in TableauDatasource object), I am finding that upon saving the TWBX file - the new extract filters are not being included in the saved file. Can you please advise on resolution to this issue?
relevant excerpt from my script:
`
##myTWBXfileVariable is filepath to TWBX file
tempWorkBook = myTWBXfileVariable
tempTWBXfile = TableauFile(filename=tempWorkBook, logger_obj=None, create_new=False, ds_version=u'10')
tempDoc = tempTWBXfile.tableau_document
tempDSes = tempDoc.datasources
##product is a variable containing string of desired filter value
for tempDS in tempDSes:
tempDS.add_dimension_extract_filter(column_name=u"Product_Name", values=[product, ],
custom_value_list=False)
tempDS.add_continuous_extract_filter(column_name="DeliveryDate", min_value=startDate,
max_value=None, date=True)
newFile = tempTWBXfile.save_new_file(new_filename_no_extension=product)
`
I appreciate any insight that you might be able to provide.
Thanks!
Hi team,
are we supporting Python 3.0 for this package.
Thanks,
Vick
I am testing out the functionality of the tableau_tools.tableau_documents package, looking into creating tableau datasources in code. Unfortunately, before I can even start creating datasources, I want to make sure I can create and save a new file, which I currently cannot do.
My function:
def test_tableau_tools_library():
LOGGER = Logger("log.log")
new_tableau_file = TableauFile("test.tds", logger_obj=LOGGER, create_new=True, ds_version=u'10')
print(new_tableau_file.file_type)
new_tableau_document = new_tableau_file.tableau_document
print(new_tableau_document.document_type)
new_tableau_document.save_file(u'test')
While attempting to run this function, I receive the following error:
Traceback (most recent call last):
File "tableau_datasource_creator/tableau_datasource_creator.py", line 17, in <module>
test_tableau_tools_library()
File "tableau_datasource_creator/tableau_datasource_creator.py", line 14, in test_tableau_tools_library
new_tableau_document.save_file(u'test', save_to_directory="testing")
File "C:\Users\COBY~1.BEN\Desktop\env\lib\site-packages\tableau_tools\tableau_documents\tableau_datasource.py", line 315, in save_file
lh.write(self.get_datasource_xml())
File "C:\Users\COBY~1.BEN\Desktop\env\lib\codecs.py", line 718, in write
return self.writer.write(data)
File "C:\Users\COBY~1.BEN\Desktop\env\lib\codecs.py", line 376, in write
data, consumed = self.encode(object, self.errors)
TypeError: utf_8_encode() argument 1 must be str, not bytes
Hello Bryan,
is it possible to edit the XML structure of a published workbook, for example by published_workbook_object?
I need to find a way to edit parameters in an wb after publishing.
Best regards,
Michael
I am able to change owner on Tableau Server directly, but when running update_workbook(workbook_name_or_luid, workbook_project_name_or_luid, new_owner_luid)
I get the error below. I get a similar error when trying to use the query_users
function. From the error, it appears to want to query users, so that might be where it's failing.
EDIT: I also notice it's using tableau_rest_api_connection_25.pyc
, but I connected using the 2.8 API.
EDIT2: I tried the same code with someone who has Site Administrator access and it seemed to work for them. I have Project Lead access (or elevated Publisher, don't know the exact terminology). I'd still be curious knowing how to equivalently change object owners via code as I can already do it manually on Tableau Server.
<ipython-input-9-da6428a90963> in <module>()
14 workbook_name_or_luid=f['tableau_workbook_name'],
15 workbook_project_name_or_luid=proj_luid,
---> 16 new_owner_luid=f['tableau_owner_luid']
17 )
18 # sleep(.5)
/Users/user/virtualenvs/ishbooks/lib/python2.7/site-packages/tableau_tools/tableau_rest_api/tableau_rest_api_connection.pyc in update_workbook(self, workbook_name_or_luid, workbook_project_name_or_luid, new_project_luid, new_owner_luid, show_tabs)
1871 else:
1872 workbook_luid = self.query_workbook_luid(workbook_name_or_luid, workbook_project_name_or_luid,
-> 1873 self.username)
1874 tsr = etree.Element(u"tsRequest")
1875 w = etree.Element(u"workbook")
/Users/user/virtualenvs/ishbooks/lib/python2.7/site-packages/tableau_tools/tableau_rest_api/tableau_rest_api_connection.pyc in query_workbook_luid(self, wb_name, proj_name_or_luid, username_or_luid)
916 if username_or_luid is None:
917 username_or_luid = self.user_luid
--> 918 workbooks = self.query_workbooks(username_or_luid)
919 workbooks_with_name = workbooks.findall(u'.//t:workbook[@name="{}"]'.format(wb_name), self.ns_map)
920 if len(workbooks_with_name) == 0:
/Users/user/virtualenvs/ishbooks/lib/python2.7/site-packages/tableau_tools/tableau_rest_api/tableau_rest_api_connection_25.pyc in query_workbooks(self, username_or_luid, project_name_or_luid, all_fields, created_at_filter, updated_at_filter, owner_name_filter, tags_filter, sorts, fields)
315 user_luid = username_or_luid
316 else:
--> 317 user_luid = self.query_user_luid(username_or_luid)
318
319 filter_checks = {u'updatedAt': updated_at_filter, u'createdAt': created_at_filter, u'tags': tags_filter,
/Users/user/virtualenvs/ishbooks/lib/python2.7/site-packages/tableau_tools/tableau_rest_api/tableau_rest_api_connection_25.pyc in query_user_luid(self, username)
400 else:
401 user_luid = self.query_single_element_luid_from_endpoint_with_filter(u"user", username,
--> 402 optimize_with_field=True)
403 self.username_luid_cache[username] = user_luid
404 self.end_log_block()
/Users/user/virtualenvs/ishbooks/lib/python2.7/site-packages/tableau_tools/tableau_rest_api/tableau_rest_api_connection_25.pyc in query_single_element_luid_from_endpoint_with_filter(self, element_name, name, optimize_with_field)
242 self.start_log_block()
243 if optimize_with_field is True:
--> 244 elements = self.query_resource(u"{}s?filter=name:eq:{}&fields=id".format(element_name, name))
245 else:
246 elements = self.query_resource(u"{}s?filter=name:eq:{}".format(element_name, name))
/Users/user/virtualenvs/ishbooks/lib/python2.7/site-packages/tableau_tools/tableau_rest_api/tableau_rest_api_connection_25.pyc in query_resource(self, url_ending, server_level, filters, sorts, additional_url_ending, fields)
180 self._request_obj.url = api_call
181 self._request_obj.http_verb = u'get'
--> 182 self._request_obj.request_from_api()
183 xml = self._request_obj.get_response() # return Element rather than ElementTree
184 self._request_obj.url = None
/Users/user/virtualenvs/ishbooks/lib/python2.7/site-packages/tableau_tools/tableau_rest_api/rest_xml_request.pyc in request_from_api(self, page_number)
259 def request_from_api(self, page_number=1):
260 try:
--> 261 self.__make_request(page_number)
262 except:
263 raise
/Users/user/virtualenvs/ishbooks/lib/python2.7/site-packages/tableau_tools/tableau_rest_api/rest_xml_request.pyc in __make_request(self, page_number)
196 # Error detection
197 except requests.exceptions.HTTPError as e:
--> 198 self._handle_http_error(response, e)
199
200 def _handle_http_error(self, response, e):
/Users/user/virtualenvs/ishbooks/lib/python2.7/site-packages/tableau_tools/tableau_rest_api/rest_xml_request.pyc in _handle_http_error(self, response, e)
238 if status_code == 409:
239 self.log(u'HTTP 409 error, most likely an already exists')
--> 240 raise RecoverableHTTPException(status_code, error_code, detail_luid)
241 else:
242 raise e
RecoverableHTTPException: ```
It appears this package doesn't work without psycopg2
from tableau_tools import Tabcmd
Traceback (most recent call last):
File "", line 1, in
ImportError: cannot import name 'Tabcmd'
from tableau_tools import tabcmd
Traceback (most recent call last):
File "", line 1, in
File "/env/lib/python3.6/site-packages/tableau_tools/tabcmd.py", line 6, in/env/lib/python3.6/site-packages/tableau_tools/tableau_repository.py", line 5, in
from tableau_tools.tableau_repository import *
File "
import psycopg2
ModuleNotFoundError: No module named 'psycopg2'
Hi
I am a beginner tableau developer. I have a question how to publish a workbook with separately data source using Tableau REST API. I'm using source control so the tbwx and tde files are on the disk. Do you have a step-by-step example of how to do this?
Best regards,
Michael
Hi!
After replicating the workbooks (replicate_workbooks_with_published_dses
), the embedded password for the connection to pgSQL in the workbooks is lost. I need to manually go to Workbook > Data Sources > Edit connection, switch the "Password" option to "Embedded password in connection" and paste the password again.
How could I prevent that?
the file tableau_rest_api_connection_33.py declares a class named TableauRestApiConnection32.
Should this class actually be named TableauRestApiConnection33?
Hi,
I'm getting below error:
Tabcmd(tabcmd_dir, server, username, password, site=site_content_url, tabcmd_config_location=tabcmd_config_location)
NameError: name 'Tabcmd' is not defined.
Attached my python script.
testtabcmd.txt
If you publish content, any subsequent (non-publish) update fails.
The send_publish_request and send_append_request methods call set_publish_content(content, boundary_string) and that sets__publish = True
However, in their "cleanup" code, when they call self._request_obj.set_publish_content(None, None) ,
sets__publish remains True.
Subsequent calls to update have the wrong ContentType set.
see: https://github.com/bryantbhowell/tableau_tools/blob/f64a04a8b5d79807bd12a03459508b377462c92b/tableau_rest_api/rest_xml_request.py#L138
Thanks,
Andrew
I'm getting the well known from previous versions - UnicodeDecodeError. This happens exactly the same as was in: https://github.com/bryantbhowell/tableau_rest_api/issues/9
all my files are UTF-8 encoded, I'm using the latest version of Tableau Tools.
Traceback (most recent call last): File "D:\Apache\Apache_2.2_32\Apache22\htdocs\CoMeT\python\getWorkbookList.py" , line 30, in <module> tab_srv.signin() File "C:\Python27\lib\tableau_tools\tableau_rest_api\tableau_rest_api_connecti on.py", line 215, in signin api.request_from_api(0) File "C:\Python27\lib\tableau_tools\tableau_rest_api\rest_xml_request.py", lin e 195, in request_from_api self.__make_request(page_number) File "C:\Python27\lib\tableau_tools\tableau_rest_api\rest_xml_request.py", lin e 140, in __make_request unicode_raw_response = parser.unescape(initial_response) File "C:\Python27\lib\HTMLParser.py", line 475, in unescape return re.sub(r"&(#?[xX]?(?:[0-9a-fA-F]+|\w{1,8}));", replaceEntities, s) File "C:\Python27\lib\re.py", line 155, in sub return _compile(pattern, flags).sub(repl, string, count) UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 674: ordina l not in range(128)
Any ideas?
EDIT: Adding log file:
---------- signin started at 2017-07-03 03:41:34 ---------- 2017-07-03 03:41:34 : Trying version 10.0 2017-07-03 03:41:34 : Logging in via: https://tab.cert.sabre.com/api/2.3/auth/signin 2017-07-03 03:41:34 : Login payload is <tsRequest><credentials name="username" password="password" ><site /></credentials></tsRequest> 2017-07-03 03:41:34 : HTTP verb is post 2017-07-03 03:41:34 : Making REST request to Tableau Server using post 2017-07-03 03:41:34 : Request URI: https://tab.cert.sabre.com/api/2.3/auth/signin 2017-07-03 03:41:34 : Request XML: <tsRequest><credentials name="username" password="password" ><site /></credentials></tsRequest> 2017-07-03 03:41:35 : Content type from headers: text/html;charset=UTF-8
Hi,
Querying workbooks in a project by luid does not work, always returns an empty array. Querying them by name works as expected.
Hi.
It appears that I am not able to connected to Tableau Online when using Tabcmd(). Have attached the screenshot of the error message. Running the same configuration directly on tabcmd.exe
Script details:
`
from tableau_tools import *
from tableau_tools.tabcmd import *
from tableau_tools.tableau_rest_api import *
from tableau_tools.tableau_documents import *
logger = Logger(u'abc.log')
tabcmd_dir = u"C:\Program Files\Tableau\Tableau Server\10.5\extras\Command Line Utility\"
tabcmd_config_location = u'C:\Users\DataSpark Analytics\AppData\Local\Tableau\Tabcmd\'
server = u'https://online.tableau.com'
site_content_url = u'abc'
username = u'{[email protected]}'
password = u'{abc}'
tabcmd = Tabcmd(tabcmd_dir, server, username, password, site=site_content_url, tabcmd_config_location=tabcmd_config_location)
tabcmd.enable_logging(logger)
`
I have recently started to use tableau_tools
to upload dynamically created Tableau workbooks to Tableau server. I can get a code snippet to work when uploading workbooks smaller than 20MB, but I receive an error when I try to upload larger notebooks. I think the problem may be coming from the way in which this package handles the sending of chunks of data to the Tableau API, but I'm not sure.
Below is the error that I receive in my code snippet:
TypeError Traceback (most recent call last)
<ipython-input-315-8c1a4ee06859> in <module>()
1 t.publish_workbook(
2 LOCAL_FILE_NAME, SERVER_WORKBOOK_NAME, project, check_published_ds=False,
----> 3 connection_username='xxxxxxxx', connection_password='xxxxxxxx')
~/personal_python_libraries/tableau_tools/tableau_rest_api/tableau_rest_api_connection.py in publish_workbook(self, workbook_filename, workbook_name, project_obj, overwrite, connection_username, connection_password, save_credentials, show_tabs, check_published_ds, oauth_flag)
2124 {"overwrite": overwrite}, connection_username, connection_password,
2125 save_credentials, show_tabs=show_tabs, check_published_ds=check_published_ds,
-> 2126 oauth_flag=oauth_flag)
2127 workbook = xml.findall('.//t:workbook', self.ns_map)
2128 return workbook[0].get('id')
~/personal_python_libraries/tableau_tools/tableau_rest_api/tableau_rest_api_connection.py in publish_content(self, content_type, content_filename, content_name, project_luid, url_params, connection_username, connection_password, save_credentials, show_tabs, check_published_ds, oauth_flag)
2272 for piece in self.read_file_in_chunks(content_file):
2273 self.log("Appending chunk to upload session {}".format(upload_session_id))
-> 2274 self.append_to_file_upload(upload_session_id, piece, final_filename)
2275
2276 # Finalize the publish
~/personal_python_libraries/tableau_tools/tableau_rest_api/tableau_rest_api_connection.py in append_to_file_upload(self, upload_session_id, content, filename)
2325 publish_request += 'Content-Type: application/octet-stream\r\n\r\n'
2326
-> 2327 publish_request += content
2328
2329 publish_request += "\r\n--{}--".format(boundary_string)
TypeError: Can't convert 'bytes' object to str implicitly
I am using Python 3.5.2 and the most recent version of tableau_tools
. Please let me know if I can provide more information.
The datasource class is missing a couple features I need for creating a tds file from scratch:
Both additions would be incredibly useful to have so I can fully automate the editing/creation of datasources!
tableau_tools Version = 4.6.0
Python Version = 2.7.10
OS Version = macOS High Sierra 10.13.3
Tableau Server Version = 10.5
I followed the docs to create a data source from scratch, but keep running into errors. When trying to create one with a Custom SQL Query, I get an Element error. Below is my code:
new_file = TableauFile(filename='test.tds',create_new=True,ds_version='10.5')
new_doc = new_file.tableau_document
new_doc.add_new_connection(ds_type='postgres'
,server='localhost'
,db_or_schema_name='db_name'
,authentication='username-password'
)
ds = new_doc.datasources[0]
conn = ds.connections[0]
conn.port = '5432'
conn.username = 'postgres'
ds.set_first_custom_sql('select * from schema.table limit 5','my_query',conn.connection_name)
new_doc.save_file(u'New TDS')
TypeError Traceback (most recent call last)
<ipython-input-33-c2c2382e0ae5> in <module>()
----> 1 new_doc.save_file(u'New TDS')
/Users/tableaukun/virtualenvs/venv/lib/python2.7/site-packages/tableau_tools/tableau_documents/tableau_datasource.py in save_file(self, filename_no_extension, save_to_directory)
386 lh.write(u"<?xml version='1.0' encoding='utf-8' ?>\n\n")
387 # Write the datasource XML itself
--> 388 lh.write(self.get_datasource_xml())
389 lh.close()
390
/Users/tableaukun/virtualenvs/venv/lib/python2.7/site-packages/tableau_tools/tableau_documents/tableau_datasource.py in get_datasource_xml(self)
319 new_rel_xml = self.generate_relation_section()
320 connection_root = new_xml.find(u'.//connection', self.ns_map)
--> 321 connection_root.append(new_rel_xml)
322 cas = self.generate_aliases_column_section()
323 # If there is no existing aliases tag, gotta add one. Unlikely but safety first
TypeError: must be Element, not None
There seems to be an issue with generate_relation_section()
. I manually ran the code for the function and received the expected value:
import xml.etree.ElementTree as ET
rel_xml_obj = ET.Element(u"relation")
# There's only a single main relation with only one table
if len(new_doc.join_relations) == 0:
for item in new_doc.main_table_relation.items():
rel_xml_obj.set(item[0], item[1])
if new_doc.main_table_relation.text is not None:
rel_xml_obj.text = new_doc.main_table_relation.text
I recently used the tableau_tools library to do automated translations of field labels and ran into this issue.
When running the translate_columns operation, I would encounter an error. The error would occur at the self.read_existing_relations() call. In this operation, the object self.relation_xml_obj is used. However, within the operation there is no check to see if this object actually exists. Apparently, after reading my workbook, this object would have value None.
Proposed solution: add a check to see if self.read_existing_relations() is None before running the self.read_existing_relations() operation or perform this check within the operation.
tableau_rest_api_connection.py
Line 17 update to:
def init(self, server, username, password, site_content_url="", version=""):
after line 24 insert another line to set the api_version (right now it defaults to "2.8" no matter what):
TableauBase.set_tableau_server_version(self, version)
This now lets you determine which API to use when making call:
TableauRestApiConnection(server, username, password, site_content_url=site, version=ver)
I recently used the tableau_tools library to do automated translations of field labels and ran into this issue.
When running the translate_captions operation from the tableau_document.py file, the getroot() call would throw an error. I am new to Python but if I understood it correctly, this call is not needed here since it would return the root of the XML branch but we want to select columns in this operation, which are nested children.
After removing the getroot() calls, the operation seems to work correctly.
For reference, my new version of this operation:
def translate_captions(self):
self.start_log_block()
for column in self.columns_list:
if column.get('caption') is None:
trans = self.__find_translation(column.get('name'))
else:
# Try to match caption first, if not move to name
self.log('Existing caption')
trans = self.__find_translation(column.get('caption'))
if trans is None:
trans = self.__find_translation(column.get('name'))
if trans is not None:
column.set('caption', trans)
self.end_log_block()
If you call query_workbook() with a p_name_or_luid set to a luid, then you get the python error:
NameError: name 'wbp_name_or_luid' is not defined
This is caused by a typo in tableau_rest_api_connection.py, line 838:
wb_in_proj = workbooks.findall('.//t:workbook[@name="{}"]/:project[@id="{}"]/..'.format(wbp_name_or_luid), self.ns_map)
For the new version 4.6.3 (and 4.6.2), there now appears to be a Type error on line 390 in the tableau_datasource.py file:
if type(ds_string, u'bytes'):
throws an error: TypeError: type() takes 1 or 3 arguments
This appears to be a new addition. Will ds_string ever be a byte?
there are references to tableau_datasource_generator
but this module is no longer part of tableau_tools
Hi,
Previous project tableau_rest_api did not require the TableauSDK dependency, which is not on PyPI and can be a pain to install inside docker images.
Is there a way of using tableautools.tableau_rest_api without requiring this extra dependency?
Thanks,
Tableau On-premise server to Tableau Online migration requires username translation to email ID
Code given in replicate_site_structure_sample.py is not working. How do we translate the replace the username to email?
Tableau_tools version: 4.4.1.
OS: MacOs High Sierra 10.13.4
Python: 2.7.10
I'm using an example in this repo. Original file uses TableauRestApiConnection25 which results in the same error as when it is modified to using TableauRestApiConnection28.
Publishing a small <10MB workbook works just fine. Uploading a 100MB+ workbook results in an error like so:
$ python copy-extract-large.py
Error: File 'temp_wb.twbx' cannot be opened to upload
Traceback (most recent call last):
File "copy-extract-large.py", line 31, in <module>
d.publish_workbook(u'{}.twbx'.format(downloaded_filename), wb_name_on_server, proj, save_credentials=False, overwrite=True)
File "/Users/svaranasi/WorkDir/py27venv/scheduler/lib/python2.7/site-packages/tableau_tools/tableau_rest_api/tableau_rest_api_connection.py", line 1946, in publish_workbook
check_published_ds=check_published_ds)
File "/Users/svaranasi/WorkDir/py27venv/scheduler/lib/python2.7/site-packages/tableau_tools/tableau_rest_api/tableau_rest_api_connection.py", line 2077, in publish_content
return self.send_publish_request(url, publish_request, boundary_string)
File "/Users/svaranasi/WorkDir/py27venv/scheduler/lib/python2.7/site-packages/tableau_tools/tableau_rest_api/tableau_rest_api_connection.py", line 367, in send_publish_request
api.request_from_api(0)
File "/Users/svaranasi/WorkDir/py27venv/scheduler/lib/python2.7/site-packages/tableau_tools/tableau_rest_api/rest_xml_request.py", line 285, in request_from_api
self.__make_request(page_number)
File "/Users/svaranasi/WorkDir/py27venv/scheduler/lib/python2.7/site-packages/tableau_tools/tableau_rest_api/rest_xml_request.py", line 200, in __make_request
response.raise_for_status()
File "/Users/svaranasi/WorkDir/py27venv/scheduler/lib/python2.7/site-packages/requests/models.py", line 935, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 400 Client Error: Bad Request for url: https://<snipped>/api/2.8/sites/8fui90b6-61e7-40a4-8b27-3a1971caef0e/workbooks?uploadSessionId=228:3A71C112761E4547BG872YZ562FP5D54-2:0&workbookType=twbx&overwrite=true
The error Error: File 'temp_wb.twbx' cannot be opened to upload is strange because I can surely see the script creating this file and deleting it as part of the copy.
I've also checked Tableau's REST API docs for Publish Workbook and the URL and params seems to be to spec.
The logs show the part files uploading successfully (PUT operations). It is the final "send_publish_request" that is failing.
The log file has this:
2018-04-10 15:07:32 : Received a 400 error, here was response:
2018-04-10 15:07:32 : <?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 http://tableau.com/api/ts-api-2.8.xsd"><error code="400011"><summary>Bad Request</summary><detail>There was a problem publishing the file '29187:9B137D4FF84945D4BE55B9E1F21772C8-1:0'.</detail></error></tsResponse>
2018-04-10 15:07:32 : Tableau REST API error code is: 400011
I added additional logging in the rest_xml_request.py file and here's line 156, 157 in the file after my change:
self.log(u"HERE#3 !"+url+"DATA"+str(self.__publish_content)+"HEADERS"+str(request_headers))
response = requests.post(url, data=self.__publish_content, headers=request_headers)
I see that the request looks OK to my eyes. Here's the logs:
DATA
--0d6a88bdf4b5c30b09ca46e16958c7
Content-Disposition: name="request_payload"
Content-Type: text/xml
<tsRequest>
<workbook name="Workbook Example" showTabs="true">
<project id="ea62d37c-11m6-11e8-8b28-bf4e547648ce" />
</workbook></tsRequest>
--0d6a88bdf4b5c30b09ca46e16958c7
--HEADERS{'X-tableau-auth': 'op42_abcQ--JSvrKm7yvZw|iV9pvJeGt8q5I2b7Ntg1l4pEq6JZlXYZ', 'Content-Type': 'multipart/mixed; boundary=0d6a88bdf4b5c30b09ca46e16958c7'}
(^ Tokens and IDs have been slightly modified when pasting here for security reasons)
Please help!
Would it be possible to push changes into PyPi repo? The latest version on that is 4.3.16 from 2/22. Thanks!
Tableau has released version 10 of Tableau Server and version 2.3 of the REST API. Although there doesn't seem to be any evident backward incompabilities, the new version adds a whole lot of useful features.
Hi Bryant!
The init.py is out of date - it doesn't import TableauRestApiConnection32
Thanks!
I know this has been asked before in different ways but the previous issues don't really solve my issue.
I have a python project that does my ETL process and sets up postgres tables that Tableau can use. After we create each table we have to manually go into Tableau Desktop and create a new data source that points to the new table set the data source to be an extract and the publish it to Tableau Server where we schedule the refreshes. I'm trying to automate the process of manually creating the data sources on Tableau desktop and then publish them to the server.
Following the docs and looking at the code I was able to do this:
new_tableau_file = TableauFile("test.tds", logger_obj=None, create_new=True, ds_version=u'10.5')
new_tableau_document = new_tableau_file.tableau_document
dses = new_tableau_document.datasources
ds = dses[0]
ds.add_new_connection(ds_type=u'postgres', server=hostname, db_or_schema_name=u'db_name')
conn = ds.connections[0]
conn.port = port
conn.username = username
ds.set_first_table(db_table_name=table_name, table_alias=table_alias, connection=conn.connection_name)
ds.add_column_alias('person_id', caption='Person ID', dimension_or_measure='dimension', discrete_or_continuous='discrete', datatype='integer', calculation=None)
ds.add_column_alias('name', caption='Name', dimension_or_measure='dimension', discrete_or_continuous='discrete', datatype='string', calculation=None)
new_tableau_document.save_file(datasource_name)
And it successfully creates a live connection to my table on my database. Now I want to be able to create a datasource that is an extract and that can be refreshed on a schedule on the server. I tried adding this create_extract(extract_name)
just before saving the file, but it fails on save file with the following error:
Traceback (most recent call last):
File "datasources.py", line 30, in create_tds
new_tableau_document.save_file(datasource_name)
File "/.virtualenv/lib/python3.7/site-packages/tableau_tools/tableau_documents/tableau_datasource.py", line 398, in save_file
ds_string = self.get_datasource_xml()
File "/.virtualenv/lib/python3.7/site-packages/tableau_tools/tableau_documents/tableau_datasource.py", line 356, in get_datasource_xml
new_xml.remove(l)
TypeError: remove() argument must be xml.etree.ElementTree.Element, not None
I'm not sure if I'm doing something wrong or if what I want to do is not supported by the library. Could you help me out with this?
Right now it seems as though the TableauHTTP class is agnostic as to whether it receives a URL that uses HTTPS as a protocol vs. unencrypted HTTP. HTTPS is clearly preferred for everything, but we've had issues getting Python running on Windows to properly verify internal certs that we've generated. And some certs may throw verification errors, which we don't care about immediately, but still want to encrypt our traffic.
So, it'd be great if we could support verify / don't verify and cafile / capath options in this class, all as optional parameters.
Thanks!
Getting error when I am trying to update 'Server Administrator' user's SiteRole to 'Unlicensed'. I am using Tableau 2019.1 with api version 3.3 and easily change role but not with this Tableau_Tools.
any user with SiteRole = Server Administrator throwing below error for method : t.update_user(username_or_luid=user, site_role=u'Unlicensed')
400 Client Error: Bad Request for url: https://{server.com}/api/3.3/sites/366dsdsd600cb-56fe-4ssabc-88-e99c566369f/users/dsdhtfn7-f5ad-4874-97ce-6bb68d
Is their any reason I can't set Server Administrator user role to Unlicensed? Apart from that role I can change to any role for any user with same method.
It seems this script will fail is the case sensitivity of the source username changes over time. Minor issue but noted. Has to do with the xpath check (this is case sensitive) as such, things do get convoluted when considering unicode user names, xml formatting, api access via url, etc, etc.
When downloading a datasource/workbook, Tableau provides a flag option to include or exclude the underlying extract.
Is it possible to add this as a filter or additional function? Thanks!
Thanks for the library. This is insanely full featured relative to the tableau document api. I'm creating a bunch of workbooks, but would like the resulting columns to correctly be dimensions and/or measure, and also ensure they have the correct datatypes. Is this possible with Oracle datasources? When I try the following code, I only get the one column:
from tableau_tools.tableau_documents import *
tf = TableauFile(filename='template.twb')
ds = tf.tableau_document.datasources[0]
for col in ds.columns.columns_list:
print(col)
> <Element 'column' at 0x053B9870>
What am I missing?
Great job on this project! The only concern I have is the very brief mention of rights in the README:
This is free and available for anyone to use and modify.
If you are comfortable with doing so, would you mind adding a standard license to the project? (MIT, GPL, etc..) I'm not a lawyer, so this is just something I noticed looking at the project overview.
In the past, I've found the following two sites helpful in understanding the general differences between the various licenses:
[https://tldrlegal.com/](tldr legal)
[http://choosealicense.com/](tldr legal)
Cheers -
Stephen
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.