Reputation: 286
I'm trying to import the Superset dashboard through API but currently not successful yet. I'm following Superset API docs to import with endpoint:
/api/v1/dashboard/import
My import payload as below:
POST /api/v1/dashboard/import/ HTTP/1.1
Host: localhost:8088
Authorization: Bearer <access token>
Content-Length: 289
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="formData"; filename="20210615_065115.json"
Content-Type: application/json
(data)
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="overwrite"
True
----WebKitFormBoundary7MA4YWxkTrZu0gW
I got a response with status 200
but the dashboard was not imported, and in the preview response on Postman I got this output:
Anybody can help with this issue?
Upvotes: 8
Views: 7875
Reputation: 2755
Using Superset 4.0.2 (2024-07-18), I found importing using the API to be very challenging. CSRF tokens may now be required and there is practically no documentation on this. However, building on Harshad's code, the following obtains a JWT then a CSRF token, it then calls the import API and more thoroughly checks for success.
import requests
SUPERSET_PROTOCOL = "http"
SUPERSET_HOST = "localhost:8088"
SUPERSET_USERNAME = "admin"
SUPERSET_PASSWORD = "admin"
EXPORTED_ZIP_FILE="example.zip"
DATABASE_NAME = "MySQL.yaml" # View the contents of EXPORTED_ZIP_FILE to get this name
DATABASE_PASSWORD = "password"
class Importer:
def __init__(self):
self.session = requests.Session()
self.get_superset_access_token()
self.get_csrf_token()
def get_superset_access_token(self):
# Authenticate and get access token
endpoint = "/api/v1/security/login"
url = f"{SUPERSET_PROTOCOL}://{SUPERSET_HOST}{endpoint}"
response = self.session.post(
url,
json={
"username": SUPERSET_USERNAME,
"password": SUPERSET_PASSWORD,
"provider": "db",
"refresh": True
},
)
if response.status_code != 200:
raise Exception(f"Got HTTP code of {response.status_code} from {url}; expected 200")
access_token = response.json()["access_token"]
print("Received access token")
self.session.headers.update({
"Authorization": f"Bearer {access_token}"
})
def get_csrf_token(self):
endpoint = "/api/v1/security/csrf_token/" # Trailing slash required to avoid redirect"
url = f"{SUPERSET_PROTOCOL}://{SUPERSET_HOST}{endpoint}"
response = self.session.get(url)
if response.status_code != 200:
raise Exception(f"Got HTTP code of {response.status_code} from {url}; expected 200")
token = response.json()["result"]
print("Received CSRF token")
self.session.headers.update({
"X-CSRFToken": token
})
def import_dashboard(self, zip_file_path, database_name, database_password):
endpoint = "/api/v1/dashboard/import/"
url = f"{SUPERSET_PROTOCOL}://{SUPERSET_HOST}{endpoint}"
with open(zip_file_path, 'rb') as infile:
files = {'formData': ('dashboard.zip', infile.read(), 'application/zip')}
payload={
'passwords': '{"databases/'+database_name+'": "'+database_password+'"}',
'overwrite': 'true'
}
response = self.session.post(
url,
files=files,
data=payload
)
output_filename = "out.html"
with open(output_filename, "wt", encoding="utf-8") as outfile:
outfile.write(response.text)
# Ensure we got the expected 200 status code
if response.status_code != 200:
raise Exception(
f"Got HTTP code of {response.status_code} from {url}; expected 200. See {output_filename} or server logs for possible hints"
)
# Ensure we can parse the response as JSON
try:
response_json = response.json()
except Exception as exception:
raise Exception(f"Could not parse response from {url} as JSON (see {output_filename} or server logs for possible hints)")
# Ensure that the JSON has a 'message' field
try:
message = response_json["message"]
except KeyError as exception:
raise Exception(f"Could not find 'message' field in response from {url}, got {response_json}")
# Ensure that the 'message' field contains 'OK'
if message != "OK":
raise Exception(f"Got message '{message}' from {url}; expected 'OK'")
print("Dashboard imported successfully")
if __name__ == "__main__":
importer = Importer()
importer.import_dashboard(EXPORTED_ZIP_FILE, DATABASE_NAME, DATABASE_PASSWORD)
Upvotes: 0
Reputation: 608
In my case, I had to provide a password for the database connection for the import to work. Here's the way to do that via Python code. Source
IMPORT_API_ENDPOINT = "/api/v1/dashboard/import/"
def get_superset_access_token():
# Authenticate and get access token
response = requests.post(
"%s/api/v1/security/login" % SUPERSET_HOST,
json={"username": SUPERSET_USERNAME, "password": SUPERSET_PASSWORD, "provider": "db", "refresh": True},
)
access_token = response.json()["access_token"]
print("Received access token.")
return access_token
# Function to import dashboard from a zip file
def import_dashboard(zip_file_path):
with open(zip_file_path, 'rb') as file:
files = {'formData': ('dashboard.zip', file, 'application/zip')}
payload={'passwords': '{"databases/{{DatabaseYAMLFile}}": "{{DatabasePassword}}"}','overwrite': 'true'}
headers = {"Authorization": f"Bearer {get_superset_access_token()}", 'Accept': 'application/json'}
response = requests.post(SUPERSET_HOST + IMPORT_API_ENDPOINT, headers=headers, files=files, data=payload)
if response.status_code != 200:
raise Exception(f"Failed to import dashboard: {response.text}")
print("Dashboard imported successfully!")
overwrite
is an optional param. I had 2 yaml files in my zip in the databases folder, the import failed when I added both the files and passwords in the API call. It worked only when I specified just 1 file.
Upvotes: 2
Reputation: 2389
Superset documentation isn't very clear about this but I finally managed to solve this problem.
As you can see your response is redirecting you to a login page.
What you need to do is to first make a GET
request to /api/v1/security/csrf_token/
And add header in your request to /api/v1/dashboard/import
'X-CSRFToken': csrf_token
Another incorrect thing in the documentation is that Content-type
is not multipart/form-data;
but it is text/html; charset=utf-8
So basically in your call you don't need to pass Content-type
in headers
Python example:
import requests
headers = {
'accept': 'application/json',
'Authorization': f'Bearer {jwt_token}',
'X-CSRFToken': csrf_token
}
files = {
'formData': (
dashboard_path,
open(dashboard_path, 'rb'),
'application/json'
)
}
response = self.session.post(url, files=files, headers=headers)
I've noticed that for some reason when I was running Superset with AUTH_TYPE = AUTH_OAUTH
on production the solution above stopped working.
It requires additionally header Referer
to be included in headers, so more safe option would be
import requests
headers = {
'accept': 'application/json',
'Authorization': f'Bearer {jwt_token}',
'X-CSRFToken': csrf_token,
'Referer': url
}
files = {
'formData': (
dashboard_path,
open(dashboard_path, 'rb'),
'application/json'
)
}
response = self.session.post(url, files=files, headers=headers)
Upvotes: 8