Use Tableau PAT for automated refresh
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
import urllib.request
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from tableauhyperapi import Connection, HyperProcess, Telemetry
|
||||
|
||||
|
||||
SERVER = os.environ.get("TABLEAU_SERVER", "https://mars.ushareit.org").rstrip("/")
|
||||
API_VERSION = os.environ.get("TABLEAU_API_VERSION", "3.15")
|
||||
SITE_CONTENT_URL = os.environ.get("TABLEAU_SITE_CONTENT_URL", "")
|
||||
WORKBOOK_ID = os.environ.get("TABLEAU_WORKBOOK_ID", "07c673fb-bac8-4c78-83e6-c60f28e04e13")
|
||||
|
||||
|
||||
def request_json(url, *, body=None, headers=None):
|
||||
request = urllib.request.Request(url, data=body, headers=headers or {})
|
||||
with urllib.request.urlopen(request, timeout=120) as response:
|
||||
return json.load(response)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
raise SystemExit("usage: refresh_tableau.py YYYY-MM-DD OUTPUT.csv")
|
||||
target_date, output_path = sys.argv[1:]
|
||||
if not re.fullmatch(r"\d{4}-\d{2}-\d{2}", target_date):
|
||||
raise SystemExit("invalid target date")
|
||||
|
||||
pat_name = os.environ.get("TABLEAU_PAT_NAME", "")
|
||||
pat_secret = os.environ.get("TABLEAU_PAT_SECRET", "")
|
||||
if not pat_name or not pat_secret:
|
||||
raise SystemExit("TABLEAU_PAT_NAME and TABLEAU_PAT_SECRET are required")
|
||||
|
||||
signin_body = json.dumps({"credentials": {
|
||||
"personalAccessTokenName": pat_name,
|
||||
"personalAccessTokenSecret": pat_secret,
|
||||
"site": {"contentUrl": SITE_CONTENT_URL},
|
||||
}}).encode()
|
||||
signin = request_json(
|
||||
f"{SERVER}/api/{API_VERSION}/auth/signin",
|
||||
body=signin_body,
|
||||
headers={"Content-Type": "application/json", "Accept": "application/json"},
|
||||
)["credentials"]
|
||||
auth_token = signin["token"]
|
||||
site_id = signin["site"]["id"]
|
||||
|
||||
try:
|
||||
with tempfile.TemporaryDirectory(prefix="manta-tableau-") as temp_dir:
|
||||
archive_path = Path(temp_dir) / "workbook.twbx"
|
||||
download = urllib.request.Request(
|
||||
f"{SERVER}/api/{API_VERSION}/sites/{site_id}/workbooks/{WORKBOOK_ID}/content",
|
||||
headers={"X-Tableau-Auth": auth_token},
|
||||
)
|
||||
with urllib.request.urlopen(download, timeout=180) as response:
|
||||
archive_path.write_bytes(response.read())
|
||||
with zipfile.ZipFile(archive_path) as archive:
|
||||
hyper_members = [name for name in archive.namelist() if name.endswith(".hyper")]
|
||||
if len(hyper_members) != 1:
|
||||
raise SystemExit(f"expected one Hyper extract, found {len(hyper_members)}")
|
||||
archive.extract(hyper_members[0], temp_dir)
|
||||
hyper_path = Path(temp_dir) / hyper_members[0]
|
||||
|
||||
query = f'''
|
||||
SELECT "pkg", "source", "dt", "campaign_name",
|
||||
SUM("impression"), SUM("click"), SUM("conversions"), SUM("spend")
|
||||
FROM "Extract"."Extract"
|
||||
WHERE "dt" = '{target_date}'
|
||||
AND LOWER("source") IN ('bigo', 'kwai', 'mintegral')
|
||||
GROUP BY "pkg", "source", "dt", "campaign_name"
|
||||
ORDER BY "pkg", "source", "campaign_name"
|
||||
'''
|
||||
with HyperProcess(Telemetry.DO_NOT_SEND_USAGE_DATA_TO_TABLEAU) as process:
|
||||
with Connection(process.endpoint, str(hyper_path)) as connection:
|
||||
rows = connection.execute_list_query(query)
|
||||
if not rows:
|
||||
raise SystemExit(f"no Tableau rows for {target_date}")
|
||||
with open(output_path, "w", encoding="utf-8", newline="") as handle:
|
||||
writer = csv.writer(handle)
|
||||
writer.writerow(("pkg", "source", "dt", "campaign_name",
|
||||
"impression", "click", "conversions", "spend"))
|
||||
writer.writerows(rows)
|
||||
print(json.dumps({"date": target_date, "rawRows": len(rows)}))
|
||||
finally:
|
||||
try:
|
||||
signout = urllib.request.Request(
|
||||
f"{SERVER}/api/{API_VERSION}/auth/signout",
|
||||
data=b"",
|
||||
headers={"X-Tableau-Auth": auth_token},
|
||||
)
|
||||
urllib.request.urlopen(signout, timeout=30).close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user