1 Reply Latest reply on Aug 23, 2018 11:38 AM by Patrick A Van Der Hyde

    Tableau 2018.1 server does not accept data source through REST API

    Richard Kooijman

      Hi,

       

      Like others here, I am having trouble getting a data source published through the REST API.

      The resulting error is 400011: Bad Request.

       

      If I check the request against the 3.0 REST API XSD, it is valid:

       

      <tsRequest><datasource name="Test RKO"><connectionCredentials embed="true" name="HANSANDERS_WDR" password="********" /><project id="31ca5587-0b05-4abe-87fe-77ae31f153d7" /></datasource></tsRequest>

       

      According to XML Spy at least.

       

      I changed the request_factory.py to do this:

       

      class DatasourceRequest(object):
         def _generate_xml(self, datasource_item, connection_credentials=None, connections=None):
        xml_request = ET.Element('tsRequest')
        datasource_element = ET.SubElement(xml_request, 'datasource')
        datasource_element.attrib['name'] = datasource_item.name
      
         if connection_credentials is not None and connections is not None:
         raise RuntimeError('You cannot set both `connections` and `connection_credentials`')
      
         if connection_credentials:
        credentials_element = ET.SubElement(datasource_element, 'connectionCredentials')
        credentials_element.attrib['name'] = connection_credentials.name
        credentials_element.attrib['password'] = connection_credentials.password
        credentials_element.attrib['embed'] = 'true' if connection_credentials.embed else 'false'
      
         if connection_credentials.oauth:
        credentials_element.attrib['oAuth'] = 'true'
      
         project_element = ET.SubElement(datasource_element, 'project')
        project_element.attrib['id'] = datasource_item.project_id
      

       

       

      The fix is to have the project definition come after the credentials. Before this change, the request was not valid according to the XSD.

       

      Overall, I am trying to achieve this:

      - change database environment in the data source

      - publish the data source with changed credentials

      - I am using the document and service Python SDK's to do this

       

      import os
      import argparse
      import logging
      
      import tableauserverclient as TSC
      from tableauserverclient import ConnectionCredentials
      from tableaudocumentapi import Datasource
      
      
      def main():
        parser = argparse.ArgumentParser(description='Publish datasource to server')
        parser.add_argument('--host', '-H', required=True, help='database host')
        parser.add_argument('--port', required=True, help='database port')
        parser.add_argument('--database', '-d', required=True, help='database name')
        parser.add_argument('--login', '-L', required=True, help='login to sign into database')
        parser.add_argument('-P', required=True, help='password to sign into database')
        parser.add_argument('--server', '-s', help='server to publish to')
        parser.add_argument('--site', '-S', default=None)
        parser.add_argument('--project', default=None)
        parser.add_argument('--username', '-u', help='username to sign into server')
        parser.add_argument('-p', '--password', default=None)
      
        parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error',
         help='desired logging level (set to error by default)')
      
        parser.add_argument('datasource', help='one or more datasources to publish', nargs='+')
      
        args = parser.parse_args()
      
         # Set logging level based on user input, or error by default
         logging_level = getattr(logging, args.logging_level.upper())
        logging.basicConfig(level=logging_level)
      
        tableau_auth = TSC.TableauAuth(args.username, args.password)
        server = TSC.Server(args.server)
      
        overwrite_true = TSC.Server.PublishMode.Overwrite
      
         with server.auth.sign_in(tableau_auth):
        server.use_server_version()
      
        all_projects, _ = server.projects.get()
        project = next((project for project in all_projects if project.name == args.project), None)
      
         if project is None:
        error = "project {0} can not be found".format(args.project)
         raise LookupError(error)
      
         for ds in args.datasource:
        tds = Datasource.from_file(ds)
         if len(tds.connections) > 1:
        error = "only single connection data sources are supported at this time"
         raise ValueError(error)
        tds.connections[0].dbname = args.database
        tds.connections[0].server = args.host
        tds.connections[0].port = args.port
        tds.connections[0].username = args.login
        filename = os.path.basename(ds)
        filename_short = os.path.splitext(filename)[0]
        file_extension = os.path.splitext(filename)[1][1:]
        new_ds_name = "{0}_{1}.{2}".format(filename_short, args.database, file_extension)
        tds.save_as(new_ds_name)
        creds = ConnectionCredentials(args.login, args.P, embed=True)
        new_ds = TSC.DatasourceItem(project.id)
        new_ds.name = filename_short
         new_ds = server.datasources.publish(new_ds, new_ds_name, mode = overwrite_true, connection_credentials=creds)
      
      
      if __name__ == '__main__':
        main()
      

       

       

      I hope the code comes through alright, it seems messed up in the editor when I look at it.

       

      Any hints?

       

       

      Regards, Richard.

       

      PS I'll also attach the source