- Implemented HTML pages for datasets, models, training, testing, and results. - Created API endpoints for managing repositories, results, tests, and training sessions. - Added functionality for streaming training progress via Server-Sent Events (SSE). - Introduced a Dockerfile for the ML runner with necessary dependencies. - Developed an SDK for user code execution within the runner container. - Enhanced CSS styles for improved UI layout and navigation. - Established a layout template for consistent HTML structure across pages. - Added JavaScript for dynamic interactions on the models page. - Implemented WebSocket handling for real-time communication with kiosk devices and controllers. - Implemented model registration and management API at /api/models - Added Gitea proxy API for repository interactions at /api/repos - Created results API for listing and comparing training results at /api/results - Developed training management API for enqueueing and retrieving training jobs at /api/trainings - Introduced SSE endpoint for live training progress updates - Added HTML pages for models, datasets, and training management - Created a Dockerfile for the ML runner with necessary dependencies - Developed SDK for user code execution within the runner container - Enhanced CSS styles for improved UI/UX - Implemented WebSocket communication for real-time device and controller interactions in the kiosk system
91 lines
2.3 KiB
Python
91 lines
2.3 KiB
Python
"""Parse e validazione del contratto `model.yml` nelle repo utente.
|
|
|
|
Schema sintetico (vedi piano):
|
|
name, type, version, python
|
|
train: {entrypoint, inputs, outputs, metrics}
|
|
test: {entrypoint, io, input_schema[], output_schema[]}
|
|
resources: {cpu, mem_mb, gpu}
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Optional
|
|
|
|
import yaml
|
|
from pydantic import BaseModel, ValidationError
|
|
|
|
from core import gitea, redis_client
|
|
|
|
|
|
class _FieldSpec(BaseModel):
|
|
name: str
|
|
dtype: str
|
|
min: Optional[float] = None
|
|
max: Optional[float] = None
|
|
unit: Optional[str] = None
|
|
|
|
|
|
class _Train(BaseModel):
|
|
entrypoint: str
|
|
inputs: dict = {}
|
|
outputs: dict = {}
|
|
metrics: dict = {}
|
|
|
|
|
|
class _Test(BaseModel):
|
|
entrypoint: str
|
|
io: str = "stdio_json"
|
|
input_schema: list[_FieldSpec] = []
|
|
output_schema: list[_FieldSpec] = []
|
|
|
|
|
|
class ModelSpec(BaseModel):
|
|
name: str
|
|
type: str
|
|
version: str = "0.1.0"
|
|
python: str = "3.11"
|
|
train: _Train
|
|
test: Optional[_Test] = None
|
|
resources: dict = {}
|
|
|
|
|
|
def parse_yaml(content: bytes | str) -> dict:
|
|
"""Parsa stringa YAML → dict validato. Solleva ValueError su errore."""
|
|
if isinstance(content, bytes):
|
|
content = content.decode("utf-8")
|
|
try:
|
|
raw = yaml.safe_load(content) or {}
|
|
spec = ModelSpec(**raw)
|
|
return spec.model_dump()
|
|
except (yaml.YAMLError, ValidationError) as e:
|
|
raise ValueError(f"invalid model.yml: {e}") from e
|
|
|
|
|
|
async def fetch_and_parse_spec(owner_repo: str, ref: str) -> Optional[dict]:
|
|
"""Recupera model.yml dalla repo alla revisione e lo parsa.
|
|
Cache Redis `ml:modelspec:{repo}:{ref}` TTL 1h.
|
|
"""
|
|
cache_key = f"ml:modelspec:{owner_repo}:{ref}"
|
|
try:
|
|
cached = await redis_client.client().get(cache_key)
|
|
if cached:
|
|
import json
|
|
return json.loads(cached)
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
raw = await gitea.get_file_raw(owner_repo, ref, "model.yml")
|
|
except Exception:
|
|
try:
|
|
raw = await gitea.get_file_raw(owner_repo, ref, "model.yaml")
|
|
except Exception:
|
|
return None
|
|
spec = parse_yaml(raw)
|
|
|
|
try:
|
|
import json
|
|
await redis_client.client().set(cache_key, json.dumps(spec), ex=3600)
|
|
except Exception:
|
|
pass
|
|
return spec
|