Fix get_recipe to handle both YAML and S-expression formats

The upload endpoint accepts both YAML and S-expression recipes, but
get_recipe only tried to parse S-expression. Now it detects the format
and parses accordingly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-01-12 15:31:25 +00:00
parent f1b90fe65d
commit 6adef63fad

View File

@@ -32,6 +32,8 @@ class RecipeService:
async def get_recipe(self, recipe_id: str) -> Optional[Recipe]:
"""Get a recipe by ID (content hash)."""
import yaml
# Get from cache (content-addressed storage)
path = self.cache.get_by_cid(recipe_id)
if not path or not path.exists():
@@ -40,13 +42,34 @@ class RecipeService:
with open(path) as f:
content = f.read()
# Parse S-expression
try:
compiled = compile_string(content)
recipe_data = compiled.to_dict()
recipe_data["sexp"] = content
except (ParseError, CompileError) as e:
return {"error": str(e), "recipe_id": recipe_id}
# Detect format - check if it starts with ( after skipping comments
def is_sexp_format(text):
for line in text.split('\n'):
stripped = line.strip()
if not stripped or stripped.startswith(';'):
continue
return stripped.startswith('(')
return False
if is_sexp_format(content):
# Parse S-expression
try:
compiled = compile_string(content)
recipe_data = compiled.to_dict()
recipe_data["sexp"] = content
recipe_data["format"] = "sexp"
except (ParseError, CompileError) as e:
return {"error": str(e), "recipe_id": recipe_id}
else:
# Parse YAML
try:
recipe_data = yaml.safe_load(content)
if not isinstance(recipe_data, dict):
return {"error": "Invalid YAML: expected dictionary", "recipe_id": recipe_id}
recipe_data["yaml"] = content
recipe_data["format"] = "yaml"
except yaml.YAMLError as e:
return {"error": f"YAML parse error: {e}", "recipe_id": recipe_id}
# Add the recipe_id to the data for convenience
recipe_data["recipe_id"] = recipe_id
@@ -56,8 +79,12 @@ class RecipeService:
if ipfs_cid:
recipe_data["ipfs_cid"] = ipfs_cid
# Compute step_count from nodes
nodes = recipe_data.get("dag", {}).get("nodes", [])
# Compute step_count from nodes (handle both formats)
if recipe_data.get("format") == "sexp":
nodes = recipe_data.get("dag", {}).get("nodes", [])
else:
# YAML format: nodes might be at top level or under dag
nodes = recipe_data.get("nodes", recipe_data.get("dag", {}).get("nodes", []))
recipe_data["step_count"] = len(nodes) if isinstance(nodes, (list, dict)) else 0
return recipe_data