def download(self, authentication, export_type, path, filename, filters=""): filter_string = "" if export_type == "image": filename += ".png" if export_type == "data": filename += ".csv" if export_type == "PDF": # export_type = "image" filename += ".pdf" if not filters == "": filter_string = "?vf_" + filters url = authentication.server + "/api/{0}/sites/{1}/views/{2}/{3}{4}".format( Tools.VERSION, authentication.site_id, self.view_id, export_type, filter_string) server_response = authentication.session.get( url, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 200) # Header format: Content-Disposition: name="tableau_workbook"; filename="workbook-filename" # filename = re.findall(r'filename="(.*)"', server_response.headers['Content-Disposition'])[0] output = path + filename.replace('/', '-') with open(output, 'wb') as f: f.write(server_response.content)
def create(authentication, name, description, log): """ Creates the Project on the server. However to store this project into a project obj you will need to query the server. :param authentication: authentication: <authentication obj> :class Auth: :param name: Project Name :param description: Project Description :param log: log: <logging obj> """ logger = log url = authentication.server + "/api/{0}/sites/{1}/projects".format( Tools.VERSION, authentication.site_id) # xml_response = ET.fromstring(Tools.encode_for_display(server_response.text)) xml_request = ET.Element('tsRequest') project_element = ET.SubElement(xml_request, 'project', parentProjectId="", name=name, description=description, contentPermissions="LockedToProject") xml_request = ET.tostring(xml_request) server_response = authentication.session.post( url, data=xml_request, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 201)
def add_datasource(authentication, capability, datasource_id, group_user, group_id): ''' Function to add default permissions at a datasources level :param authentication: The authentication object holding all information for the signed in session :param capability: Key value pair, consisting of below permissions followed by "Allow"/"Deny" AddComment, ChangeHierarchy, ChangePermissions, Delete, ExportData, ExportImage, ExportXml, Filter, Read, ShareView, ViewComments, ViewUnderlyingData, WebAuthoring, and Write. :param project_id: id for the project that you are assigning the permissions :param group_user: String to specify group or user :param group_id: id of the group receiving the permissions, interchangable with user id :return: ''' url = authentication.server + "/api/{0}/sites/{1}/datasources/{2}/permissions".format( Tools.VERSION, authentication.site_id, datasource_id) xml_request = ET.Element('tsRequest') project_element = ET.SubElement(xml_request, 'permissions') ET.SubElement(xml_request, "datasource", id=datasource_id) capability_element = ET.SubElement(project_element, "granteeCapabilities") group_element = ET.SubElement(capability_element, group_user, id=group_id) capabilities = ET.SubElement(capability_element, "capabilities") for key, value in capability.items(): ET.SubElement(capabilities, "capability", name=key, mode=value) xml_request = ET.tostring(xml_request) server_response = authentication.session.put( url, data=xml_request, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 200)
def delete(self, authentication): url = authentication.server + "/api/{0}/sites/{1}/projects/{2}".format( Tools.VERSION, authentication.site_id, self.project_id) server_response = authentication.session.delete( url, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 204)
def delete(self): url = self.authentication.server + "/api/{0}/schedules/{1}".format( Tools.VERSION, self.schedule_id) server_response = requests.delete( url, headers={'x-tableau-auth': self.authentication.get_token()}) Tools.check_status(server_response, 204)
def get_workgroup_id(self, session, repos_url=""): if repos_url: url = repos_url + "/vizportal/api/web/v1/getSchedules" else: url = self.authentication.server + "/vizportal/api/web/v1/getSchedules" payload = "{\"method\":\"getSchedules\",\"params\":{\"filter\":{\"operator\":\"and\",\"clauses\":[{\"operator\":\"eq\", \"field\":\"siteId\", \"value\":\"4\"}]},\"order\":[{\"field\":\"name\",\"ascending\":true}],\"page\":{\"startIndex\":0,\"maxItems\":1000}}}" xsrf_token = self.authentication.get_viz_token() # Make the request to server server_response = session.post(url, headers={ 'content-type': "application/json;charset=UTF-8", 'accept': "application/json, text/plain, */*", 'cache-control': "no-cache", 'X-XSRF-TOKEN': xsrf_token }, data=payload) response_json = json.loads(server_response.text) returned_schedules = response_json["result"]["schedules"] for s in returned_schedules: if s['name'] == self.name: self.set_workgroup_id(s['id']) Tools.check_status(server_response, 200) # ASCII encode server response to enable displaying to console server_response = Tools.encode_for_display(server_response.text)
def get_workgroup_id(self, session, repos_url=""): """ for use with the vizportal API the repository(workgroup) id is required, this function gets said id. :param session: :param repos_url: :return: """ if repos_url: url = repos_url + "/vizportal/api/web/v1/setExtractTasksSchedule" else: url = self.authentication.server + "/vizportal/api/web/v1/setExtractTasksSchedule" payload = "{\"method\":\"getExtractTasks\",\"params\":{\"filter\":{\"operator\":\"and\",\"clauses\":[{\"operator\":\"eq\",\"field\":\"siteId\",\"value\":\"4\"}]},\"order\":[{\"field\":\"targetName\",\"ascending\":true}],\"page\":{\"startIndex\":0,\"maxItems\":1000}}}" xsrf_token = self.authentication.get_viz_token() # Make the request to server server_response = session.post(url, headers={ 'content-type': "application/json;charset=UTF-8", 'accept': "application/json, text/plain, */*", 'cache-control': "no-cache", 'X-XSRF-TOKEN': xsrf_token }, data=payload) response_json = json.loads(server_response.text) returned_task = response_json["result"]["tasks"] for s in returned_tasks: if s['name'] == self.name: # Todo not sure any frame of validation here, task id is the only consistent which this is identifying. self.workgroup_id = (s['id']) print(server_response) Tools.check_status(server_response, 200) # ASCII encode server response to enable displaying to console server_response = Tools.encode_for_display(server_response.text)
def set_task_schedule(self, task_id, schedule_id, session, repos_url=""): """ Again built using the vizportal API which is not recommended. :param task_id: :param schedule_id: :param session: :param repos_url: :return: """ if repos_url: url = repos_url + "/vizportal/api/web/v1/setExtractTasksSchedule" else: url = self.authentication.server + "/vizportal/api/web/v1/setExtractTasksSchedule" payload = "{\"method\":\"setExtractTasksSchedule\",\"params\":{\"ids\":[\"%s\"], \"scheduleId\":\"%s\"}}" % ( task_id, schedule_id) xsrf_token = self.authentication.get_viz_token() # Make the request to server server_response = session.post(url, headers={ 'content-type': "application/json;charset=UTF-8", 'accept': "application/json, text/plain, */*", 'cache-control': "no-cache", 'X-XSRF-TOKEN': xsrf_token }, data=payload) Tools.check_status(server_response, 200) # ASCII encode server response to enable displaying to console server_response = Tools.encode_for_display(server_response.text)
def query_view(authentication, name, workbook_id): """ Gets the workbook info and stores it :param authentication: authentication object that grants user access to API calls and holds any signin info :param name: the name of the workbook :return: """ url = authentication.server + "/api/{0}/sites/{1}/workbooks/{2}/views?includeUsageStatistics=true".format( Tools.VERSION, authentication.site_id, workbook_id) server_response = authentication.session.get( url, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 200) xml_response = ET.fromstring(Tools.encode_for_display( server_response.text)) views = xml_response.findall('.//t:view', namespaces=Tools.XMLNS) for v in views: if v.get('name') == name: # source_workbook_id = v.find('.//t:workbook', namespaces=Tools.XMLNS).get('id') # source_owner_id = v.find('.//t:owner', namespaces=Tools.XMLNS).get('id') # source_view_count = v.find('.//t:usage', namespaces=Tools.XMLNS).get('totalViewCount') view = View(v.get('name'), v.get('id'), v.get('contentUrl'), workbook_id) # source_owner_id, source_view_count) return view error = "View named '{0}' not found.".format(name) raise LookupError(error)
def remove_from_site(authentication, user_id, logger): url = authentication.server + "/api/{0}/sites/{1}/users/{2}".format( Tools.VERSION, authentication.site_id, user_id) server_response = authentication.session.delete( url, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 204) pass
def query(authentication, log, name=""): """ Returns Project(s) from Tableau Server. :param authentication: <authentication obj> :class Auth: :param log: <logging obj> :param name: if not stated will return all projects on specified server. :return: """ logger = log done = False page_size = 100 page_number = 1 total_returned = 0 output_projects = [] if name == "": logger.debug("No name specified, pulling all projects") while not (done): url = authentication.server + "/api/{0}/sites/{1}/projects".format( Tools.VERSION, authentication.site_id) url += "?pageSize={0}&pageNumber={1}".format(page_size, page_number) server_response = authentication.session.get( url, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 200) xml_response = ET.fromstring( Tools.encode_for_display(server_response.text)) # Get total number of records from the <pagination> element total_available = xml_response.find('.//t:pagination', namespaces={ 't': "http://tableau.com/api" }).attrib['totalAvailable'] # Note! Need to convert "total_available" to integer total_available = int(total_available) page_number += 1 total_returned += page_size projects = xml_response.findall('.//t:project', namespaces=Tools.XMLNS) for p in projects: if name == "": project = Project(p.get('id'), p.get('name'), p.get('description'), p.get('contentPermission')) output_projects.append(project) elif p.get('name') == name: project = Project(p.get('id'), p.get('name'), p.get('description'), p.get('contentPermission')) return project if total_returned >= total_available: done = True if name == "": return output_projects error = "Project named '{0}' not found.".format(name) raise LookupError(error)
def sign_out(self): """Sign out of tableau Server, destroying the Authentication Token""" url = self.server + "/api/{0}/auth/signout".format(Tools.VERSION) server_response = self.session.post( url, headers={'x-tableau-auth': self.token}) Tools.check_status(server_response, 204) self.set_token("") del self return
def query_group(authentication, log, name=''): """ Returns group(s) from Tableau Server. :param authentication: <authentication obj> :class Auth: :param log: <logging obj> :param name: if not stated will return all groups on specified server. :return: <Group Obj> or list<Group Obj> """ logger = log done = False page_size = 100 page_number = 1 total_returned = 0 returned_groups = [] while not (done): url = authentication.server + "/api/{0}/sites/{1}/groups".format( Tools.VERSION, authentication.site_id) url += "?pageSize={0}&pageNumber={1}".format(page_size, page_number) server_response = authentication.session.get( url, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 200) xml_response = ET.fromstring( Tools.encode_for_display(server_response.text)) # Get total number of records from the <pagination> element total_available = xml_response.find('.//t:pagination', namespaces={ 't': "http://tableau.com/api" }).attrib['totalAvailable'] # Note! Need to convert "total_available" to integer total_available = int(total_available) page_number += 1 total_returned += page_size groups = xml_response.findall('.//t:group', namespaces=Tools.XMLNS) for g in groups: if name == '': domain_name = g.find('.//t:domain', namespaces=Tools.XMLNS).get('name') group = Group(g.get('id'), g.get('name'), domain_name, logger) returned_groups.append(group) elif g.get('name') == name: domain_name = g.find('.//t:domain', namespaces=Tools.XMLNS).get('name') group = Group(g.get('id'), g.get('name'), domain_name, logger) return group if total_returned >= total_available: done = True if name == "": return returned_groups error = "Group named '{0}' not found.".format(name) raise LookupError(error)
def query_user(authentication, log, name=""): """ Returns users(s) from Tableau Server. :param authentication: <authentication obj> :class Auth: :param log: <logging obj> :param name: if not stated will return all users on specified server. :return: <User Obj> or list<User Obj> """ logger = log done = False page_size = 100 page_number = 1 total_returned = 0 return_users = [] while not (done): url = authentication.server + "/api/{0}/sites/{1}/users".format( Tools.VERSION, authentication.site_id) url += "?pageSize={0}&pageNumber={1}".format(page_size, page_number) server_response = authentication.session.get( url, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 200) xml_response = ET.fromstring( Tools.encode_for_display(server_response.text)) # Get total number of records from the <pagination> element total_available = xml_response.find('.//t:pagination', namespaces={ 't': "http://tableau.com/api" }).attrib['totalAvailable'] # Note! Need to convert "total_available" to integer total_available = int(total_available) page_number += 1 total_returned += page_size users = xml_response.findall('.//t:user', namespaces=Tools.XMLNS) for u in users: if name != "": if u.get('name').upper() == name.upper(): user = User(u.get('id'), u.get('name'), u.get('siteRole'), u.get('lastLogin')) return user else: user = User(u.get('id'), u.get('name'), u.get('siteRole'), u.get('lastLogin')) return_users.append(user) if total_returned >= total_available: done = True if name == "": return return_users error = "User named '{0}' not found.".format(name) raise LookupError(error)
def get_preview_image(self, authentication, path, filename): url = authentication.server + "/api/{0}/sites/{1}/workbooks/{2}/views/{3}/previewImage".format( Tools.VERSION, authentication.site_id, self.workbook_id, self.view_id) server_response = authentication.session.get( url, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 200) # Header format: Content-Disposition: name="tableau_workbook"; filename="workbook-filename" # filename = re.findall(r'filename="(.*)"', server_response.headers['Content-Disposition'])[0] output = path + filename.replace('/', '-') with open(output, 'wb') as f: f.write(server_response.content)
def create(self, type, start_time, frequency, interval="", end_time="", execution_order="Parallel", priority="50"): url = self.authentication.server + "/api/{0}/schedules".format( Tools.VERSION) # Builds the request xml_payload = ET.Element('tsRequest') schedule_element = ET.SubElement(xml_payload, 'schedule', name=self.name, priority=priority, type=type, frequency=frequency, executionOrder=execution_order) fd = ET.SubElement(schedule_element, 'frequencyDetails', start=start_time, end=end_time) if frequency != "Daily": intervals_element = ET.SubElement(fd, 'intervals') if frequency == "Hourly": ET.SubElement(intervals_element, 'interval', hours=interval, minutes=interval) elif frequency == "Weekly": ET.SubElement(intervals_element, 'interval', weekDay=interval) elif frequency == "Monthly": ET.SubElement(intervals_element, 'interval', monthDay=interval) xml_payload = ET.tostring(xml_payload) # Make the request to server server_response = requests.post( url, headers={'x-tableau-auth': self.authentication.get_token()}, data=xml_payload) Tools.check_status(server_response, 201) # ASCII encode server response to enable displaying to console server_response = Tools.encode_for_display(server_response.text) # Reads and parses the response parsed_response = ET.fromstring(server_response)
def add_to_site(authentication, name, site_role, log): logger = log url = authentication.server + "/api/{0}/sites/{1}/users".format( Tools.VERSION, authentication.site_id) xml_request = ET.Element('tsRequest') user_element = ET.SubElement(xml_request, 'user', name=name, siteRole=site_role) xml_request = ET.tostring(xml_request) server_response = authentication.session.post( url, data=xml_request, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 201)
def get_project_id(server, project_name, auth_token, site_id): """ Returns the project ID for the project on the Tableau server. 'server' specified server address 'auth_token' authentication token that grants user access to API calls 'site_id' ID of the site that the user is signed into """ page_num, page_size = 1, 1000 # Default paginating values # Builds the request url = server + "/api/{0}/sites/{1}/projects".format(Tools.VERSION, site_id) paged_url = url + "?pageSize={0}&pageNumber={1}".format( page_size, page_num) server_response = requests.get(paged_url, headers={'x-tableau-auth': auth_token}) Tools.check_status(server_response, 200) xml_response = ET.fromstring(Tools.encode_for_display( server_response.text)) # Used to determine if more requests are required to find all projects on server total_projects = int( xml_response.find('t:pagination', namespaces=Tools.XMLNS).get('totalAvailable')) max_page = int(math.ceil(total_projects / page_size)) projects = xml_response.findall('.//t:project', namespaces=Tools.XMLNS) # Continue querying if more projects exist on the server for page in range(2, max_page + 1): paged_url = url + "?pageSize={0}&pageNumber={1}".format( page_size, page) server_response = requests.get(paged_url, headers={'x-tableau-auth': auth_token}) Tools.check_status(server_response, 200) xml_response = ET.fromstring( Tools.encode_for_display(server_response.text)) projects.extend( xml_response.findall('.//t:project', namespaces=Tools.XMLNS)) # Look through all projects to find the 'default' one for project in projects: if project.get('name') == project_name: # .replace(" ", ""): return project.get('id') raise LookupError("Project was not found on server")
def start_upload_session(server, auth_token, site_id): """ Creates a POST request that initiates a file upload session. 'server' specified server address 'auth_token' authentication token that grants user access to API calls 'site_id' ID of the site that the user is signed into Returns a session ID that is used by subsequent functions to identify the upload session. """ url = server + "/api/{0}/sites/{1}/fileUploads".format( Tools.VERSION, site_id) server_response = requests.post(url, headers={'x-tableau-auth': auth_token}) Tools.check_status(server_response, 201) xml_response = ET.fromstring(Tools.encode_for_display( server_response.text)) return xml_response.find('t:fileUpload', namespaces=Tools.XMLNS).get('uploadSessionId')
def download(packaged, authentication, workbook_id, path): if packaged: url = authentication.server + "/api/{0}/sites/{1}/workbooks/{2}/content?includeExtract=True".format( Tools.VERSION, authentication.site_id, workbook_id) else: url = authentication.server + "/api/{0}/sites/{1}/workbooks/{2}/content?includeExtract=False".format( Tools.VERSION, authentication.site_id, workbook_id) server_response = authentication.session.get( url, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 200) # Header format: Content-Disposition: name="tableau_workbook"; filename="workbook-filename" # filename = re.findall(r'filename="(.*)"', server_response.headers['Content-Disposition'])[0] extension = re.findall(r'filename="(.*)"', server_response.headers['Content-Disposition'])[0] extension = extension.split('.')[1] with open("{}.{}".format(path, extension), 'wb') as f: f.write(server_response.content) return "{}.{}".format(path, extension)
def swap_site(self, site): url = self.server + "/api/{0}/auth/switchSite".format(Tools.VERSION) if site == "": site_name = "Default Site" else: site_name = site # Builds the request xml_payload = ET.Element('tsRequest') credentials_element = ET.SubElement(xml_payload, 'site', contentUrl=site.replace(" ", "")) xml_payload = ET.tostring(xml_payload) print("\nSwapping site to", site_name, "...") # Make the request to server server_response = self.session.post( url, headers={'x-tableau-auth': self.get_token()}, data=xml_payload) Tools.check_status(server_response, 200) # ASCII encode server response to enable displaying to console server_response = Tools.encode_for_display(server_response.text) # Reads and parses the response parsed_response = ET.fromstring(server_response) # Gets the auth token, site ID and user ID self.set_token( parsed_response.find('t:credentials', namespaces=Tools.XMLNS).get('token')) self.set_site_id( parsed_response.find('.//t:site', namespaces=Tools.XMLNS).get('id')) self.set_user_id( parsed_response.find('.//t:user', namespaces=Tools.XMLNS).get('id')) print("\nNow Signed into", site_name) return self.get_token
def get_details(self, authentication): done = False page_size = 100 page_number = 1 total_returned = 0 while not (done): url = authentication.server + "/api/{0}/sites/{1}/groups".format( Tools.VERSION, authentication.site_id) url += "?pageSize={0}&pageNumber={1}".format( page_size, page_number) server_response = authentication.session.get( url, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 200) xml_response = ET.fromstring( Tools.encode_for_display(server_response.text)) # Get total number of records from the <pagination> element total_available = xml_response.find('.//t:pagination', namespaces={ 't': "http://tableau.com/api" }).attrib['totalAvailable'] # Note! Need to convert "total_available" to integer total_available = int(total_available) page_number += 1 total_returned += page_size groups = xml_response.findall('.//t:group', namespaces=Tools.XMLNS) for g in groups: if g.get('name') == self.name: self.group_id = g.get('id') if self.domain_name == "": self.domain_name = g.find( './/t:domain', namespaces=Tools.XMLNS).get('name') return self if total_returned >= total_available: done = True error = "Group named '{0}' not found.".format(self.name) raise LookupError(error)
def add(self, authentication, user_id): """ Adds the given user_id to the current group. :param authentication: :param user_id: :return: """ url = authentication.server + "/api/{0}/sites/{1}/groups/{2}/users".format( Tools.VERSION, authentication.site_id, self.group_id) xml_request = ET.Element('tsRequest') # for user_id in user_ids: group_element = ET.SubElement(xml_request, 'user', id=user_id) xml_request = ET.tostring(xml_request) server_response = authentication.session.post( url, data=xml_request, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 200)
def sign_in_as(self, user_id): """Sign into tableau Server as a user. If user is not unique it will require domain. Creating the Authentication Token""" url = self.server + "/api/{0}/auth/signin".format(Tools.VERSION) # Builds the request xml_payload = ET.Element('tsRequest') credentials_element = ET.SubElement(xml_payload, 'credentials', name=self.username, password=self.password) ET.SubElement(credentials_element, 'site', contentUrl=self.site) ET.SubElement(credentials_element, 'user', id=user_id) xml_payload = ET.tostring(xml_payload) logger.info("\nSigning in as {}, to {} on the {} site...".format( self.username, self.server, self.site)) # Make the request to server server_response = self.session.post(url, data=xml_payload) Tools.check_status(server_response, 200) # ASCII encode server response to enable displaying to console server_response = Tools.encode_for_display(server_response.text) # Reads and parses the response parsed_response = ET.fromstring(server_response) logger.debug("Returned a response of {}".format(parsed_response)) # Gets the auth token, site ID and user ID of the signed in user. self.set_token( parsed_response.find('t:credentials', namespaces=Tools.XMLNS).get('token')) self.set_site_id( parsed_response.find('.//t:site', namespaces=Tools.XMLNS).get('id')) self.set_user_id( parsed_response.find('.//t:user', namespaces=Tools.XMLNS).get('id')) logger.info("\nSigned in as {}, to {} on the {} site...".format( self.username, self.server, self.site)) return self.get_token
def create_group(authentication, name, log): """ Creates the Group on the server. However to store this group into a group obj you will need to query the server. :param authentication: authentication: <authentication obj> :class Auth: :param name: Group Name :param log: <logging obj> """ logger = log url = authentication.server + "/api/{0}/sites/{1}/groups".format( Tools.VERSION, authentication.site_id) xml_request = ET.Element('tsRequest') group_element = ET.SubElement(xml_request, 'group', name=name) xml_request = ET.tostring(xml_request) server_response = authentication.session.post( url, data=xml_request, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 201) return Group("", name, "", logger)
def update_user(authentication, user, log): """ Use to change details of the given User Object on the Server. Will take current values if new value not specified :param authentication: authentication: <authentication obj> :class Auth: :param user: Updated user object. :param log: <logging obj> :return: """ # PUT /api/api-version/sites/site-id/users/user-id logger = log url = authentication.server + "/api/{0}/sites/{1}/users/{2}".format( Tools.VERSION, authentication.site_id, user.user_id) xml_request = ET.Element('tsRequest') # Currently we can only change site role, as everything else is drawn through AD user_element = ET.SubElement(xml_request, 'user', siteRole=user.site_role) xml_request = ET.tostring(xml_request) server_response = authentication.session.put( url, data=xml_request, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 200)
def get_users(self, authentication): done = False page_size = 100 page_number = 1 total_returned = 0 users_in_group = [] while not (done): url = authentication.server + "/api/{0}/sites/{1}/groups/{2}/users".format( Tools.VERSION, authentication.site_id, self.group_id) url += "?pageSize={0}&pageNumber={1}".format( page_size, page_number) server_response = authentication.session.get( url, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 200) xml_response = ET.fromstring( Tools.encode_for_display(server_response.text)) # Get total number of records from the <pagination> element total_available = xml_response.find('.//t:pagination', namespaces={ 't': "http://tableau.com/api" }).attrib['totalAvailable'] # Note! Need to convert "total_available" to integer total_available = int(total_available) page_number += 1 total_returned += page_size users = xml_response.findall('.//t:user', namespaces=Tools.XMLNS) for u in users: user = User(u.get('id'), u.get('name'), u.get('siteRole'), u.get('lastLogin')) users_in_group.append(user) if total_returned >= total_available: done = True return users_in_group
def query_tasks(authentication, task_id="", schedule_id=""): """ Gets the task info and stores it :param schedule_id: :param authentication: authentication object that grants user access to API calls and holds any signin info :param task_id: task ID for searching for specific task :return: """ if task_id == "": url = authentication.server + "/api/{0}/sites/{1}/tasks/extractRefreshes".format( Tools.VERSION, authentication.site_id) # url += "?pageSize={0}&pageNumber={1}".format(page_size, page_number) server_response = requests.get( url, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 200) xml_response = ET.fromstring( Tools.encode_for_display(server_response.text)) # Get total number of records from the <pagination> element # total_available = xml_response.find('.//t:pagination', # namespaces={'t': "http://tableau.com/api"}).attrib['totalAvailable'] # Note! Need to convert "total_available" to integer # total_available = int(total_available) # page_number += 1 # total_returned += page_size tasks = xml_response.findall('.//t:tasks', namespaces=Tools.XMLNS) output_tasks = [] if not schedule_id == "": for t in tasks: get_schedule = t.find('.//t:schedule', namespaces=Tools.XMLNS) if schedule_id == get_schedule.get('id'): extract_refresh = t.find('.//t:extractRefresh', namespaces=Tools.XMLNS) extract_id = extract_refresh.get('id') extract_priority = extract_refresh.get('priority') extract_fails = extract_refresh.get( 'consecutiveFailedCount') extract_type = extract_refresh.get('type') try: content_id = { "datasource": t.find('.//t:datasource', namespaces=Tools.XMLNS).get('id') } except AttributeError: content_id = { "workbook": t.find('.//t:workbook', namespaces=Tools.XMLNS).get('id') } schedule = Schedule(authentication, get_schedule.get('name'), get_schedule.get('id'), get_schedule.get('state'), get_schedule.get('priority'), get_schedule.get('createdAt'), get_schedule.get('updatedAt'), get_schedule.get('type'), get_schedule.get('frequency'), get_schedule.get('nextRunAt')) task = Task(authentication, extract_id, extract_priority, extract_fails, extract_type, schedule, content_id) output_tasks.append(task) elif schedule_id == "": for t in tasks: extract_refresh = t.find('.//t:extractRefresh', namespaces=Tools.XMLNS) extract_id = extract_refresh.get('id') extract_priority = extract_refresh.get('priority') extract_fails = extract_refresh.get('consecutiveFailedCount') extract_type = extract_refresh.get('type') get_schedule = t.find('.//t:schedule', namespaces=Tools.XMLNS) schedule = Schedule(authentication, get_schedule.get('name'), get_schedule.get('id'), get_schedule.get('state'), get_schedule.get('priority'), get_schedule.get('createdAt'), get_schedule.get('updatedAt'), get_schedule.get('type'), get_schedule.get('frequency'), get_schedule.get('nextRunAt')) try: content_id = { "datasource": t.find('.//t:datasource', namespaces=Tools.XMLNS).get('id') } except AttributeError: content_id = { "workbook": t.find('.//t:workbook', namespaces=Tools.XMLNS).get('id') } task = Task(authentication, extract_id, extract_priority, extract_fails, extract_type, schedule, content_id) output_tasks.append(task) # if total_returned >= total_available: # done = True else: url = authentication.server + "/api/{0}/sites/{1}/tasks/extractRefreshes".format( Tools.VERSION, authentication.site_id) # url += "?pageSize={0}&pageNumber={1}".format(page_size, page_number) server_response = requests.get( url, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 200) xml_response = ET.fromstring( Tools.encode_for_display(server_response.text)) # Get total number of records from the <pagination> element # total_available = xml_response.find('.//t:pagination', # namespaces={'t': "http://tableau.com/api"}).attrib['totalAvailable'] # Note! Need to convert "total_available" to integer # total_available = int(total_available) # page_number += 1 # total_returned += page_size tasks = xml_response.findall('.//t:tasks', namespaces=Tools.XMLNS) output_tasks = [] if not schedule_id == "": for t in tasks: get_schedule = t.find('.//t:schedule', namespaces=Tools.XMLNS) if schedule_id == get_schedule.get('id'): extract_refresh = t.find('.//t:extractRefresh', namespaces=Tools.XMLNS) extract_id = extract_refresh.get('id') extract_priority = extract_refresh.get('priority') extract_fails = extract_refresh.get( 'consecutiveFailedCount') extract_type = extract_refresh.get('type') try: content_id = { "datasource": t.find('.//t:datasource', namespaces=Tools.XMLNS).get('id') } except AttributeError: content_id = { "workbook": t.find('.//t:workbook', namespaces=Tools.XMLNS).get('id') } schedule = Schedule(authentication, get_schedule.get('name'), get_schedule.get('id'), get_schedule.get('state'), get_schedule.get('priority'), get_schedule.get('createdAt'), get_schedule.get('updatedAt'), get_schedule.get('type'), get_schedule.get('frequency'), get_schedule.get('nextRunAt')) task = Task(authentication, extract_id, extract_priority, extract_fails, extract_type, schedule, content_id) output_tasks.append(task) elif schedule_id == "": for t in tasks: if task_id == t.find('.//t:extractRefresh', namespaces=Tools.XMLNS).get('id'): extract_refresh = t.find('.//t:extractRefresh', namespaces=Tools.XMLNS) extract_id = extract_refresh.get('id') extract_priority = extract_refresh.get('priority') extract_fails = extract_refresh.get( 'consecutiveFailedCount') extract_type = extract_refresh.get('type') get_schedule = t.find('.//t:schedule', namespaces=Tools.XMLNS) schedule = Schedule(authentication, get_schedule.get('name'), get_schedule.get('id'), get_schedule.get('state'), get_schedule.get('priority'), get_schedule.get('createdAt'), get_schedule.get('updatedAt'), get_schedule.get('type'), get_schedule.get('frequency'), get_schedule.get('nextRunAt')) try: content_id = { "datasource": t.find('.//t:datasource', namespaces=Tools.XMLNS).get('id') } except AttributeError: content_id = { "workbook": t.find('.//t:workbook', namespaces=Tools.XMLNS).get('id') } task = Task(authentication, extract_id, extract_priority, extract_fails, extract_type, schedule, content_id) output_tasks.append(task) if len(output_tasks) > 0: return output_tasks error = "Tasks associated with '{0}{1}' were not found.".format( task_id, schedule_id) raise LookupError(error)
def update(self, authentication, name="", description="", owners=[], limit="", lifecycle=""): """ Use to change details of the current Project Object on the Server. Will take current values if new value not specified. :param authentication: :param name: :param description: :param owners: :param limit: :param lifecycle: :return: """ approvers = "" count = 0 if name == "": name = self.name if not len(owners) < 1: for owner in owners: if not count > 3: if count == 0: approvers += "<approver>" + owner + "</approver>" count += 1 else: approvers += ", " + "<approver" + str( count) + ">" + owner + "</approver" + str( count) + ">" count += 1 if description == "": description = self.description # "\n\nThe project owner(s) and approver(s) for this project is " + approvers \ description += ".\n\nThis project will have a user limit of " + " <userlimit>" + str( limit) + "</userlimit> users." # users across it's lifecycle of <projectlife>" + lifecycle + "</projectlife>." url = authentication.server + "/api/{0}/sites/{1}/projects/{2}".format( Tools.VERSION, authentication.site_id, self.project_id) # xml_response = ET.fromstring(Tools.encode_for_display(server_response.text)) xml_request = ET.Element('tsRequest') project_element = ET.SubElement(xml_request, 'project', parentProjectId="", name=name, description=description, contentPermissions="LockedToProject") xml_request = ET.tostring(xml_request) server_response = authentication.session.put( url, data=xml_request, headers={'x-tableau-auth': authentication.get_token()}) Tools.check_status(server_response, 200)
def publish(project, authentication, upload_file_path, parameter, ds_user="", ds_pass="", embed=""): """ Handles the publishing of Tableau Workbooks and Tableau Datasources based on the file extension. :param project: string of project name. :param authentication: authentication object :param upload_file_path: File path including extension :param parameter: overwrite or (append if TDE)true or false :return: """ ##### STEP 0: INITIALIZATION ##### server = authentication.server username = authentication.username auth_token = authentication.get_token() site_id = authentication.get_site_id() # workbook_file_path = raw_input("\nWorkbook file to publish (include file extension): ") upload_file_path = os.path.abspath(upload_file_path) # Workbook file with extension, without full path upload_file = os.path.basename(upload_file_path) print("\n*Publishing '{0}' to the {1} project as {2}*".format( upload_file, project, username)) if not os.path.isfile(upload_file_path): error = "{0}: file not found".format(upload_file_path) raise IOError(error) # Break workbook file by name and extension filename, file_extension = upload_file.split('.', 1) # Get workbook size to check if chunking is necessary size = os.path.getsize(upload_file_path) chunked = size >= FILESIZE_LIMIT ##### STEP 2: OBTAIN DEFAULT PROJECT ID ##### print("\n2. Finding the '{0}' project to publish to)".format(project)) project_id = get_project_id(server, project, auth_token, site_id) upload_content, upload_content_type, xml_request = file_extension_check( file_extension, filename, project_id, ds_user, ds_pass, embed) if chunked: print("\n3. Publishing '{0}' in {1}MB chunks (workbook over 64MB)". format(upload_file, CHUNK_SIZE / 1024000)) # Initiates an upload session uploadID = start_upload_session(server, auth_token, site_id) # URL for PUT request to append chunks for publishing put_url = server + "/api/{0}/sites/{1}/fileUploads/{2}".format( Tools.VERSION, site_id, uploadID) # Read the contents of the file in chunks of 100KB with open(upload_file_path, 'rb') as f: while True: data = f.read(CHUNK_SIZE) if not data: break payload, content_type = Tools.make_multipart({ 'request_payload': ('', '', 'text/xml'), 'tableau_file': ('file', data, 'application/octet-stream') }) print("\tPublishing a chunk...") server_response = requests.put(put_url, data=payload, headers={ 'x-tableau-auth': auth_token, "content-type": content_type }) Tools.check_status(server_response, 200) # Finish building request for chunking method payload, content_type = Tools.make_multipart( {'request_payload': ('', xml_request, 'text/xml')}) # workbooks || datasources publish_url = server + "/api/{0}/sites/{1}/{2}".format( Tools.VERSION, site_id, upload_content) publish_url += "?uploadSessionId={0}".format(uploadID) # workbookType || datasourceType if upload_content_type == "workbookType": publish_url += "&{0}={1}&overwrite={2}".format( upload_content_type, file_extension, parameter) elif upload_content_type == "datasourceType" and file_extension == 'tde': publish_url += "&{0}={1}&append={2}".format( upload_content_type, file_extension, parameter) else: publish_url += "&{0}={1}&overwrite={2}".format( upload_content_type, file_extension, parameter) else: print("\n3. Publishing '" + upload_file + "' using the all-in-one method (workbook under 64MB)") # Read the contents of the file to publish with open(upload_file_path, 'rb') as f: workbook_bytes = f.read() # Finish building request for all-in-one method parts = { 'request_payload': ('', xml_request, 'text/xml'), 'tableau_workbook': (upload_file, workbook_bytes, 'application/octet-stream') } payload, content_type = Tools.make_multipart(parts) publish_url = server + "/api/{0}/sites/{1}/{2}?".format( Tools.VERSION, site_id, upload_content) if upload_content_type == "workbookType": publish_url += "&{0}={1}&overwrite={2}".format( upload_content_type, file_extension, parameter) elif upload_content_type == "datasourceType" and file_extension == 'tde': publish_url += "&{0}={1}&append={2}".format( upload_content_type, file_extension, parameter) else: publish_url += "&{0}={1}&overwrite={2}".format( upload_content_type, file_extension, parameter) # Make the request to publish and check status code print("\tUploading...") server_response = requests.post(publish_url, data=payload, headers={ 'x-tableau-auth': auth_token, 'content-type': content_type }) Tools.check_status(server_response, 201)