|
4 | 4 | """ |
5 | 5 |
|
6 | 6 | import uuid |
| 7 | +from datetime import datetime |
7 | 8 | from typing import List, Dict, Any, Optional |
8 | 9 | from lab.task_template import TaskTemplate as TaskTemplateService |
9 | 10 |
|
| 11 | +# Keys that are never removed when syncing from task.yaml (system-owned). |
| 12 | +# Any other key in stored metadata that is not in the parsed task_data is |
| 13 | +# removed, so we don't need to maintain a list of YAML field names. |
| 14 | +_PROTECTED_METADATA_KEYS = frozenset({"id", "experiment_id", "type", "plugin", "created_at"}) |
| 15 | + |
10 | 16 |
|
11 | 17 | class TaskService: |
12 | 18 | """Service for managing tasks using filesystem storage""" |
@@ -85,6 +91,32 @@ async def update_task(self, task_id: str, new_task_data: Dict[str, Any]) -> bool |
85 | 91 | except FileNotFoundError: |
86 | 92 | return False |
87 | 93 |
|
| 94 | + async def update_task_from_yaml(self, task_id: str, task_data: Dict[str, Any]) -> bool: |
| 95 | + """Update task metadata from parsed task.yaml so it matches the YAML exactly. |
| 96 | + Keeps only protected (system) keys from existing metadata, then applies |
| 97 | + task_data; any other key not in task_data is removed (so removing a |
| 98 | + field in the editor actually removes it). |
| 99 | + """ |
| 100 | + try: |
| 101 | + task = await self.task_service.get(str(task_id)) |
| 102 | + data = await task.get_json_data() |
| 103 | + |
| 104 | + # Start from existing protected keys only; then apply parsed YAML |
| 105 | + out = {k: data[k] for k in _PROTECTED_METADATA_KEYS if k in data} |
| 106 | + out.update(task_data) |
| 107 | + |
| 108 | + # Preserve file_mounts when YAML omits it and existing has a value |
| 109 | + if "file_mounts" not in task_data: |
| 110 | + existing_mounts = data.get("file_mounts") |
| 111 | + if existing_mounts is True or (isinstance(existing_mounts, dict) and len(existing_mounts) > 0): |
| 112 | + out["file_mounts"] = existing_mounts |
| 113 | + |
| 114 | + out["updated_at"] = datetime.utcnow().isoformat() |
| 115 | + await task._set_json_data(out) |
| 116 | + return True |
| 117 | + except FileNotFoundError: |
| 118 | + return False |
| 119 | + |
88 | 120 | async def delete_task(self, task_id: str) -> bool: |
89 | 121 | """Delete a task""" |
90 | 122 | try: |
|
0 commit comments