Fix JSON decode error by adding Accept header to all API requests
The CLI was getting empty responses from the server because it wasn't sending Accept: application/json header. The server uses content negotiation and returns HTML for browser requests. Changes: - Updated get_auth_header() to always include Accept: application/json - Simplified all commands to use get_auth_header(require_token=True) - Removed redundant token_data checks now handled by get_auth_header Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
87
artdag.py
87
artdag.py
@@ -53,18 +53,22 @@ def clear_token():
|
|||||||
TOKEN_FILE.unlink()
|
TOKEN_FILE.unlink()
|
||||||
|
|
||||||
|
|
||||||
def get_auth_header() -> dict:
|
def get_auth_header(require_token: bool = False) -> dict:
|
||||||
"""Get Authorization header if token exists."""
|
"""Get headers for API requests. Always includes Accept: application/json."""
|
||||||
|
headers = {"Accept": "application/json"}
|
||||||
token_data = load_token()
|
token_data = load_token()
|
||||||
token = token_data.get("access_token")
|
token = token_data.get("access_token")
|
||||||
if token:
|
if token:
|
||||||
return {"Authorization": f"Bearer {token}"}
|
headers["Authorization"] = f"Bearer {token}"
|
||||||
return {}
|
elif require_token:
|
||||||
|
click.echo("Not logged in. Use 'artdag login' first.", err=True)
|
||||||
|
sys.exit(1)
|
||||||
|
return headers
|
||||||
|
|
||||||
|
|
||||||
def api_get(path: str, auth: bool = False):
|
def api_get(path: str, auth: bool = False):
|
||||||
"""GET request to server."""
|
"""GET request to server."""
|
||||||
headers = get_auth_header() if auth else {}
|
headers = get_auth_header(require_token=auth)
|
||||||
resp = requests.get(f"{get_server()}{path}", headers=headers)
|
resp = requests.get(f"{get_server()}{path}", headers=headers)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
return resp.json()
|
return resp.json()
|
||||||
@@ -72,7 +76,7 @@ def api_get(path: str, auth: bool = False):
|
|||||||
|
|
||||||
def api_post(path: str, data: dict = None, params: dict = None, auth: bool = False):
|
def api_post(path: str, data: dict = None, params: dict = None, auth: bool = False):
|
||||||
"""POST request to server."""
|
"""POST request to server."""
|
||||||
headers = get_auth_header() if auth else {}
|
headers = get_auth_header(require_token=auth)
|
||||||
resp = requests.post(f"{get_server()}{path}", json=data, params=params, headers=headers)
|
resp = requests.post(f"{get_server()}{path}", json=data, params=params, headers=headers)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
return resp.json()
|
return resp.json()
|
||||||
@@ -263,7 +267,7 @@ def stats():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.get(f"{get_server()}/api/stats", headers=headers)
|
resp = requests.get(f"{get_server()}/api/stats", headers=headers)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
stats = resp.json()
|
stats = resp.json()
|
||||||
@@ -294,7 +298,7 @@ def clear_data(force):
|
|||||||
|
|
||||||
# Show current stats first
|
# Show current stats first
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.get(f"{get_server()}/api/stats", headers=headers)
|
resp = requests.get(f"{get_server()}/api/stats", headers=headers)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
stats = resp.json()
|
stats = resp.json()
|
||||||
@@ -409,13 +413,9 @@ def run(recipe, input_hash, name, wait):
|
|||||||
@click.option("--offset", "-o", default=0, help="Offset for pagination")
|
@click.option("--offset", "-o", default=0, help="Offset for pagination")
|
||||||
def list_runs(limit, offset):
|
def list_runs(limit, offset):
|
||||||
"""List all runs with pagination."""
|
"""List all runs with pagination."""
|
||||||
token_data = load_token()
|
headers = get_auth_header(require_token=True)
|
||||||
if not token_data.get("access_token"):
|
|
||||||
click.echo("Not logged in. Use 'artdag login' first.", err=True)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
|
||||||
resp = requests.get(f"{get_server()}/runs?offset={offset}&limit={limit}", headers=headers)
|
resp = requests.get(f"{get_server()}/runs?offset={offset}&limit={limit}", headers=headers)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
@@ -449,10 +449,7 @@ def list_runs(limit, offset):
|
|||||||
@click.option("--analysis", is_flag=True, help="Show audio analysis data")
|
@click.option("--analysis", is_flag=True, help="Show audio analysis data")
|
||||||
def status(run_id, plan, artifacts, analysis):
|
def status(run_id, plan, artifacts, analysis):
|
||||||
"""Get status of a run with optional detailed views."""
|
"""Get status of a run with optional detailed views."""
|
||||||
token_data = load_token()
|
headers = get_auth_header() # Optional auth, always has Accept header
|
||||||
headers = {}
|
|
||||||
if token_data.get("access_token"):
|
|
||||||
headers["Authorization"] = f"Bearer {token_data['access_token']}"
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp = requests.get(f"{get_server()}/runs/{run_id}", headers=headers)
|
resp = requests.get(f"{get_server()}/runs/{run_id}", headers=headers)
|
||||||
@@ -606,7 +603,7 @@ def delete_run(run_id, force):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.delete(f"{get_server()}/runs/{run_id}", headers=headers)
|
resp = requests.delete(f"{get_server()}/runs/{run_id}", headers=headers)
|
||||||
if resp.status_code == 400:
|
if resp.status_code == 400:
|
||||||
click.echo(f"Cannot delete: {resp.json().get('detail', 'Unknown error')}", err=True)
|
click.echo(f"Cannot delete: {resp.json().get('detail', 'Unknown error')}", err=True)
|
||||||
@@ -645,7 +642,7 @@ def delete_cache(cid, force):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.delete(f"{get_server()}/cache/{cid}", headers=headers)
|
resp = requests.delete(f"{get_server()}/cache/{cid}", headers=headers)
|
||||||
if resp.status_code == 400:
|
if resp.status_code == 400:
|
||||||
click.echo(f"Cannot delete: {resp.json().get('detail', 'Unknown error')}", err=True)
|
click.echo(f"Cannot delete: {resp.json().get('detail', 'Unknown error')}", err=True)
|
||||||
@@ -703,16 +700,12 @@ def matches_media_type(item: dict, media_type: str) -> bool:
|
|||||||
default="all", help="Filter by media type")
|
default="all", help="Filter by media type")
|
||||||
def cache(limit, offset, media_type):
|
def cache(limit, offset, media_type):
|
||||||
"""List cached content with pagination and optional type filter."""
|
"""List cached content with pagination and optional type filter."""
|
||||||
token_data = load_token()
|
headers = get_auth_header(require_token=True)
|
||||||
if not token_data.get("access_token"):
|
|
||||||
click.echo("Not logged in. Use 'artdag login' first.", err=True)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Fetch more items if filtering to ensure we get enough results
|
# Fetch more items if filtering to ensure we get enough results
|
||||||
fetch_limit = limit * 3 if media_type != "all" else limit
|
fetch_limit = limit * 3 if media_type != "all" else limit
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
|
||||||
resp = requests.get(f"{get_server()}/cache?offset={offset}&limit={fetch_limit}", headers=headers)
|
resp = requests.get(f"{get_server()}/cache?offset={offset}&limit={fetch_limit}", headers=headers)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
@@ -817,7 +810,7 @@ def upload(filepath):
|
|||||||
try:
|
try:
|
||||||
with open(filepath, "rb") as f:
|
with open(filepath, "rb") as f:
|
||||||
files = {"file": (Path(filepath).name, f)}
|
files = {"file": (Path(filepath).name, f)}
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.post(f"{get_server()}/cache/upload", files=files, headers=headers)
|
resp = requests.post(f"{get_server()}/cache/upload", files=files, headers=headers)
|
||||||
if resp.status_code == 401:
|
if resp.status_code == 401:
|
||||||
click.echo("Authentication failed. Please login again.", err=True)
|
click.echo("Authentication failed. Please login again.", err=True)
|
||||||
@@ -915,7 +908,7 @@ def meta(cid, origin, origin_url, origin_note, description, tags, folder, add_co
|
|||||||
click.echo("Not logged in. Please run: artdag login <username>", err=True)
|
click.echo("Not logged in. Please run: artdag login <username>", err=True)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
|
|
||||||
# Handle publish action
|
# Handle publish action
|
||||||
if publish_name:
|
if publish_name:
|
||||||
@@ -1269,7 +1262,7 @@ def storage_list():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.get(f"{get_server()}/storage", headers=headers)
|
resp = requests.get(f"{get_server()}/storage", headers=headers)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
@@ -1322,7 +1315,7 @@ def storage_add(provider_type, name, capacity):
|
|||||||
|
|
||||||
# Send to server
|
# Send to server
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
payload = {
|
payload = {
|
||||||
"provider_type": provider_type,
|
"provider_type": provider_type,
|
||||||
"config": config,
|
"config": config,
|
||||||
@@ -1355,7 +1348,7 @@ def storage_test(storage_id):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.post(f"{get_server()}/storage/{storage_id}/test", headers=headers)
|
resp = requests.post(f"{get_server()}/storage/{storage_id}/test", headers=headers)
|
||||||
if resp.status_code == 404:
|
if resp.status_code == 404:
|
||||||
click.echo(f"Storage provider not found: {storage_id}", err=True)
|
click.echo(f"Storage provider not found: {storage_id}", err=True)
|
||||||
@@ -1389,7 +1382,7 @@ def storage_delete(storage_id, force):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.delete(f"{get_server()}/storage/{storage_id}", headers=headers)
|
resp = requests.delete(f"{get_server()}/storage/{storage_id}", headers=headers)
|
||||||
if resp.status_code == 400:
|
if resp.status_code == 400:
|
||||||
click.echo(f"Error: {resp.json().get('detail', 'Bad request')}", err=True)
|
click.echo(f"Error: {resp.json().get('detail', 'Bad request')}", err=True)
|
||||||
@@ -1458,7 +1451,7 @@ def upload_recipe(filepath):
|
|||||||
try:
|
try:
|
||||||
with open(filepath, "rb") as f:
|
with open(filepath, "rb") as f:
|
||||||
files = {"file": (Path(filepath).name, f)}
|
files = {"file": (Path(filepath).name, f)}
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.post(f"{get_server()}/recipes/upload", files=files, headers=headers)
|
resp = requests.post(f"{get_server()}/recipes/upload", files=files, headers=headers)
|
||||||
if resp.status_code == 401:
|
if resp.status_code == 401:
|
||||||
click.echo("Authentication failed. Please login again.", err=True)
|
click.echo("Authentication failed. Please login again.", err=True)
|
||||||
@@ -1499,7 +1492,7 @@ def upload_effect(filepath):
|
|||||||
try:
|
try:
|
||||||
with open(filepath, "rb") as f:
|
with open(filepath, "rb") as f:
|
||||||
files = {"file": (Path(filepath).name, f)}
|
files = {"file": (Path(filepath).name, f)}
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.post(f"{get_server()}/effects/upload", files=files, headers=headers)
|
resp = requests.post(f"{get_server()}/effects/upload", files=files, headers=headers)
|
||||||
if resp.status_code == 401:
|
if resp.status_code == 401:
|
||||||
click.echo("Authentication failed. Please login again.", err=True)
|
click.echo("Authentication failed. Please login again.", err=True)
|
||||||
@@ -1530,13 +1523,9 @@ def upload_effect(filepath):
|
|||||||
@click.option("--offset", "-o", default=0, help="Offset for pagination")
|
@click.option("--offset", "-o", default=0, help="Offset for pagination")
|
||||||
def list_effects(limit, offset):
|
def list_effects(limit, offset):
|
||||||
"""List uploaded effects with pagination."""
|
"""List uploaded effects with pagination."""
|
||||||
token_data = load_token()
|
headers = get_auth_header(require_token=True)
|
||||||
if not token_data.get("access_token"):
|
|
||||||
click.echo("Not logged in. Use 'artdag login' first.", err=True)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
|
||||||
resp = requests.get(f"{get_server()}/effects?offset={offset}&limit={limit}", headers=headers)
|
resp = requests.get(f"{get_server()}/effects?offset={offset}&limit={limit}", headers=headers)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
result = resp.json()
|
result = resp.json()
|
||||||
@@ -1577,7 +1566,7 @@ def show_effect(cid, source):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.get(f"{get_server()}/effects/{cid}", headers=headers)
|
resp = requests.get(f"{get_server()}/effects/{cid}", headers=headers)
|
||||||
if resp.status_code == 404:
|
if resp.status_code == 404:
|
||||||
click.echo(f"Effect not found: {cid}", err=True)
|
click.echo(f"Effect not found: {cid}", err=True)
|
||||||
@@ -1644,13 +1633,9 @@ def show_effect(cid, source):
|
|||||||
@click.option("--offset", "-o", default=0, help="Offset for pagination")
|
@click.option("--offset", "-o", default=0, help="Offset for pagination")
|
||||||
def list_recipes(limit, offset):
|
def list_recipes(limit, offset):
|
||||||
"""List uploaded recipes for the current user with pagination."""
|
"""List uploaded recipes for the current user with pagination."""
|
||||||
token_data = load_token()
|
headers = get_auth_header(require_token=True)
|
||||||
if not token_data.get("access_token"):
|
|
||||||
click.echo("Not logged in. Use 'artdag login' first.", err=True)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
|
||||||
resp = requests.get(f"{get_server()}/recipes?offset={offset}&limit={limit}", headers=headers)
|
resp = requests.get(f"{get_server()}/recipes?offset={offset}&limit={limit}", headers=headers)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
@@ -1688,7 +1673,7 @@ def show_recipe(recipe_id):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.get(f"{get_server()}/recipes/{recipe_id}", headers=headers)
|
resp = requests.get(f"{get_server()}/recipes/{recipe_id}", headers=headers)
|
||||||
if resp.status_code == 404:
|
if resp.status_code == 404:
|
||||||
click.echo(f"Recipe not found: {recipe_id}", err=True)
|
click.echo(f"Recipe not found: {recipe_id}", err=True)
|
||||||
@@ -1745,7 +1730,7 @@ def run_recipe(recipe_id, inputs, wait):
|
|||||||
|
|
||||||
# Run
|
# Run
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
f"{get_server()}/recipes/{recipe_id}/run",
|
f"{get_server()}/recipes/{recipe_id}/run",
|
||||||
json={"inputs": input_dict},
|
json={"inputs": input_dict},
|
||||||
@@ -1808,7 +1793,7 @@ def delete_recipe(recipe_id, force):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.delete(f"{get_server()}/recipes/{recipe_id}", headers=headers)
|
resp = requests.delete(f"{get_server()}/recipes/{recipe_id}", headers=headers)
|
||||||
if resp.status_code == 401:
|
if resp.status_code == 401:
|
||||||
click.echo("Authentication failed. Please login again.", err=True)
|
click.echo("Authentication failed. Please login again.", err=True)
|
||||||
@@ -1872,7 +1857,7 @@ def generate_plan(recipe_file, inputs, features, output):
|
|||||||
|
|
||||||
# Submit to API
|
# Submit to API
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
f"{get_server()}/api/v2/plan",
|
f"{get_server()}/api/v2/plan",
|
||||||
json=request_data,
|
json=request_data,
|
||||||
@@ -1933,7 +1918,7 @@ def execute_plan(plan_file, wait):
|
|||||||
|
|
||||||
# Submit to API
|
# Submit to API
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
f"{get_server()}/api/v2/execute",
|
f"{get_server()}/api/v2/execute",
|
||||||
json={"plan_json": plan_json},
|
json={"plan_json": plan_json},
|
||||||
@@ -2011,7 +1996,7 @@ def run_recipe_v2(recipe_file, inputs, features, wait):
|
|||||||
click.echo(f"Inputs: {len(input_hashes)}")
|
click.echo(f"Inputs: {len(input_hashes)}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
f"{get_server()}/api/v2/run-recipe",
|
f"{get_server()}/api/v2/run-recipe",
|
||||||
json=request_data,
|
json=request_data,
|
||||||
@@ -2046,7 +2031,7 @@ def run_recipe_v2(recipe_file, inputs, features, wait):
|
|||||||
def _wait_for_v2_run(token_data: dict, run_id: str):
|
def _wait_for_v2_run(token_data: dict, run_id: str):
|
||||||
"""Poll v2 run status until completion."""
|
"""Poll v2 run status until completion."""
|
||||||
click.echo("Waiting for completion...")
|
click.echo("Waiting for completion...")
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
@@ -2094,7 +2079,7 @@ def run_status_v2(run_id):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
|
headers = get_auth_header(require_token=True)
|
||||||
resp = requests.get(
|
resp = requests.get(
|
||||||
f"{get_server()}/api/v2/run/{run_id}",
|
f"{get_server()}/api/v2/run/{run_id}",
|
||||||
headers=headers
|
headers=headers
|
||||||
|
|||||||
Reference in New Issue
Block a user