unsloth/studio/backend/routes/data_recipe/validate.py
Roland Tannous 47654cb91c Final cleanup
2026-03-12 18:28:04 +00:00

93 lines
3 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
"""Validation endpoints for data recipe."""
from __future__ import annotations
from typing import Any
from fastapi import APIRouter, HTTPException
from core.data_recipe.service import (
build_config_builder,
create_data_designer,
validate_recipe,
)
from models.data_recipe import RecipePayload, ValidateError, ValidateResponse
router = APIRouter()
def _collect_validation_errors(recipe: dict[str, Any]) -> list[ValidateError]:
try:
from data_designer.engine.compiler import (
_add_internal_row_id_column_if_needed,
_get_allowed_references,
_resolve_and_add_seed_columns,
)
from data_designer.engine.validation import (
ViolationLevel,
validate_data_designer_config,
)
except ImportError:
return []
try:
builder = build_config_builder(recipe)
designer = create_data_designer(recipe)
resource_provider = designer._create_resource_provider( # type: ignore[attr-defined]
"validate-configuration",
builder,
)
config = builder.build()
_resolve_and_add_seed_columns(config, resource_provider.seed_reader)
_add_internal_row_id_column_if_needed(config)
violations = validate_data_designer_config(
columns = config.columns,
processor_configs = config.processors or [],
allowed_references = _get_allowed_references(config),
)
except (TypeError, ValueError, AttributeError):
return []
errors: list[ValidateError] = []
for violation in violations:
if violation.level != ViolationLevel.ERROR:
continue
code = getattr(violation.type, "value", None)
path = violation.column if violation.column else None
message = str(violation.message).strip() or "Validation failed."
errors.append(
ValidateError(
message = message,
path = path,
code = code,
)
)
return errors
@router.post("/validate", response_model = ValidateResponse)
def validate(payload: RecipePayload) -> ValidateResponse:
recipe = payload.recipe
if not recipe.get("columns"):
return ValidateResponse(
valid = False,
errors = [ValidateError(message = "Recipe must include columns.")],
)
try:
validate_recipe(recipe)
except RuntimeError as exc:
raise HTTPException(status_code = 503, detail = str(exc)) from exc
except Exception as exc:
detail = str(exc).strip() or "Validation failed."
parsed_errors = _collect_validation_errors(recipe)
return ValidateResponse(
valid = False,
errors = parsed_errors or [ValidateError(message = detail)],
raw_detail = detail,
)
return ValidateResponse(valid = True)