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