Using v3 to connect to PowerBI | XM Community
Question

Using v3 to connect to PowerBI

  • 28 April 2020
  • 6 replies
  • 220 views

Userlevel 1
Badge +2

Has anyone had success creating a direct connection to Qualtrics in PowerBI? I'm currently running a python script to download responses using the v3 API directions here but am really hoping I can create a direction connection to PowerBI. I've created a Python script in my query editor and added some attempts to open the zip file created by the export script but haven't been successful. The answer I've seen frequently is either "you need to know how to code the API" or "you need our custom solutions" and so am trying to see if the collective wisdom of the community has solved this challenge yet.


6 replies

Badge

Hi Straister,
I am still waiting from Qualtrics to create a direct connection to PowerBI.
In the mean time I would really appreciate if you can you please share your python code to download responses through API. thanks

Userlevel 1
Badge +2

Hi Fabian,
Looks like Qualtrics has actually updated their API documentation with an even better script example. I'd just start here and customize as-needed.

Badge

Hi Straister,
Thanks. I followed the code as you advised. I am getting an error. Is API Key same as API token ?
xWarning: "is" with a literal. Did you mean "=="?
 if progressStatus is "failed":
set environment variables APIKEY and DATACENTER
Am I missing something here ? Can you please help ?
------------------------------------------------
# Python 3
import requests
import zipfile
import json
import io, os
import sys
import re

def exportSurvey(apiToken,surveyId, dataCenter, fileFormat):

  surveyId = "SV_********"
  fileFormat = "csv"
  dataCenter = "iad1"

  # Setting static parameters
  requestCheckProgress = 0.0
  progressStatus = "inProgress"
    baseUrl = "https://{0}.qualtrics.com/API/v3/surveys/{1}/export-responses/".format(dataCenter, surveyId)
  headers = {
  "content-type": "application/json",
  "x-api-token": "3P******"
  }

  # Step 1: Creating Data Export
  downloadRequestUrl = baseUrl
  downloadRequestPayload = '{"format":"' + fileFormat + '"}'
  downloadRequestResponse = requests.request("POST", downloadRequestUrl, data=downloadRequestPayload, headers=headers)
  progressId = downloadRequestResponse.json()["result"]["progressId"]
  print(downloadRequestResponse.text)

  # Step 2: Checking on Data Export Progress and waiting until export is ready
  while progressStatus != "complete" and progressStatus != "failed":
    print ("progressStatus=", progressStatus)
    requestCheckUrl = baseUrl + progressId
    requestCheckResponse = requests.request("GET", requestCheckUrl, headers=headers)
    requestCheckProgress = requestCheckResponse.json()["result"]["percentComplete"]
    print("Download is " + str(requestCheckProgress) + " complete")
    progressStatus = requestCheckResponse.json()["result"]["status"]

  #step 2.1: Check for error
  if progressStatus is "failed":
    raise Exception("export failed")

  fileId = requestCheckResponse.json()["result"]["fileId"]

  # Step 3: Downloading file
  requestDownloadUrl = baseUrl + fileId + '/file'
  requestDownload = requests.request("GET", requestDownloadUrl, headers=headers, stream=True)

  # Step 4: Unzipping the file
  zipfile.ZipFile(io.BytesIO(requestDownload.content)).extractall("MyQualtricsDownload")
  print('Complete')


def main():
   
  try:
   apiToken = os.environ['APIKEY']
   dataCenter = os.environ['DATACENTER']
  except KeyError:
   print("set environment variables APIKEY and DATACENTER")
   sys.exit(2)

  try:
    surveyId=sys.argv[1]
    fileFormat=sys.argv[2]
  except IndexError:
    print ("usage: surveyId fileFormat")
    sys.exit(2)

  if fileFormat not in ["csv", "tsv", "spss"]:
    print ('fileFormat must be either csv, tsv, or spss')
    sys.exit(2)
 
  r = re.compile('^SV_.*')
  m = r.match(surveyId)
  if not m:
    print ("survey Id must match ^SV_.*")
    sys.exit(2)

  exportSurvey(apiToken, surveyId,dataCenter, fileFormat)
 
if __name__== "__main__":
  main()

Userlevel 1
Badge +2

Hi Fabian. This is mostly a python question - I'd encourage you to try stackoverflow or talk with a developer in your org.
It appears that you've tried to hard-code your api key - the initial snippet that Qualtrics provides now uses environment variables, which you can set in the script. if you set these using the IDs you get from your account page, you should be able to run this.
I'm unfortunately not really available to help you troubleshoot this but you may be able to contract with Qualtrics to get their support here (though this is trivial for someone in your org who has python expertise or the experts on stackoverflow).

Badge

Hey thanks straister.. No problem. Thanks anyway for guiding me. Will check with stackoverflow.

Badge +2

Hey there! Looks like your error code is trying to tell you what to do (at least a little). Here's one issue:
#step 2.1: Check for error
if progressStatus is "failed":
    raise Exception("export failed")
Python is object-oriented. So, you're actually asking if "failed" is the same object as progressStatus, not if project status failed. It's like asking if it is literally the word "failed" rather than whether or not it worked. You want a boolean operator like you have above:
if progressStatus == "failed" 
That error is printing alongside this message that you're seeing, which is defined inside the script:
except KeyError:
   print("set environment variables APIKEY and DATACENTER")
   sys.exit(2)
More info here:
Comparing Python Objects the Right Way: "is" vs "==" – Real Python
Now, there are a lot of things bad about this snippet, and I think it came from the API docs that way, so it's most likely not your fault.
For example, you've got those sys.exit() commands all over the place in try and except loops. They're killing your process any time one of those very specific requirements isn't met. It's set up this way because it's meant to be run as a super-simple CLI. That's why you have this at the bottom:
if __name__ == "__main__":
main()
If it were up to me, I'd let the requests package kill my script and get rid of all of that. You're setting local variables for everything the sys.arg inputs are looking for anyway (surveyId, token, etc.). It's more likely you'll just adjust the script as needed.
If you want those input prompts, I'd just use python's input() function. It's lighter and more intuitive. Try this:
def exportSurvey(apiToken, surveyId, dataCenter, fileFormat):


# Step 1: Creating Data Export
baseUrl = f"https://{dataCenter}.qualtrics.com/API/v3/surveys/{surveyId}/export-responses/"
headers = {"content-type": "application/json", "x-api-token": apiToken}
downloadRequestPayload = {"format": fileFormat}
downloadRequestResponse = requests.request(
"POST", baseUrl, json=downloadRequestPayload, headers=headers
)

# Step 2: Checking on Data Export Progress and waiting until export is ready
progressId = downloadRequestResponse.json()
progressId = progressId["result"]["progressId"]



# Setting static parameters
requestCheckProgress = 0.0
progressStatus = "inProgress"


while progressStatus != "complete" and progressStatus != "failed":
requestCheckUrl = baseUrl + progressId
requestCheckResponse = requests.request("GET", requestCheckUrl, headers=headers)
requestCheckProgress = requestCheckResponse.json()["result"]["percentComplete"]
progressStatus = requestCheckResponse.json()["result"]["status"]


# step 2.1: Check for error
if progressStatus == "failed":
raise Exception("export failed")


# Step 3: Downloading file
fileId = requestCheckResponse.json()["result"]["fileId"]
requestDownloadUrl = baseUrl + fileId + "/file"
requestDownload = requests.request(
"GET", requestDownloadUrl, headers=headers, stream=True
)


# Step 4: Unzipping the file
zipfile.ZipFile(io.BytesIO(requestDownload.content)).extractall(
"MyQualtricsDownload"
)


def main(surveyId="SV_...", dataCenter="iad1", fileFormat="csv"):
"""
set your default variables here instead of in the export function above, or delete
and just add them above as kwargs
"""


if not os.environ.get("APIKEY"): # environ.get won't hault your script like os.environ["APIKEY"]
apiToken = input("Add your api token")
if input("set as environment variable?").lower() == "yes":
os.environ["APIKEY"] = apiToken

if not os.environ.get("DATACENTER"): # when the key is not in your environment variables
while True:
dataCenter = input("Datacenter?")
pat = re.compile(r"[a-z]{2,3}\\d")
if pat.match(dataCenter):
if input("set as environment variable?").lower() == "yes":
os.environ["DATACENTER"] = dataCenter
break


survey_check = input(
f"Survey id is set to {surveyId}, would you like to change it?"
)
if survey_check == "yes":
while True:
survey_input = input("Add the id for the survey you want to export")
r = re.compile("^SV_.*")
m = r.match(surveyId)
if not m:
print("survey Id must match ^SV_.*")
else:
surveyId = survey_input
break


format_check = input(
f"File format for the download is currently set to {fileFormat}. Would you like to change it?"
)
if format_check == "yes":
format_input = input("Add the file extension for the format you'd like to use")
fileFormat = format_input


exportSurvey(apiToken, surveyId, dataCenter, fileFormat="csv")

main()
All that said, I think there's a better way:
import SimpleQualtrics
import pandas as pd

session = SimpleQualtrics.Session(yaml="qualtrics_credentials.yaml")
# this is a file you configure when you install the package
# it contains all your sensitive stuff so you don't need to add the variables each time

def get_qualtrics_data(surveyId):

# only get necessary metadata
surveyMetadataIds = ["startDate", "endDate", "finished", "_recordId"]

# read it in with pandas (skip 2nd and 3rd rows which are extra headers)
allresponses = pd.read_csv(
session.fileFromPost( # all the checking progress happens in the background now
"surveys/{}/export-responses".format(surveyId),
{
"format": "csv",
"newlineReplacement": " ",
"surveyMetadataIds": surveyMetadataIds,
},
),
skiprows=[1, 2],
)
allresponses.to_csv(f"{surveyId}.csv")
return allresponses # returns a pandas dataframe in-memory so you can do what you need


Leave a Reply