Repository: valohai/valohai-yaml

Issue:
Normalize Task object keys to be kebab-case instead of snake_case
Everything else in our YAML is kebab-case, but due to a review oversight on my part the Task keys added in #98 are snake_case.

They should be made kebab-case.

Based on analytics, no one has used task definitions in YAML yet (and happily the snake_cased keys are also optional, and so used even less), so we can safely make this change before it's too late.

Discussion:

Patch:
diff --git a/examples/task-example.yaml b/examples/task-example.yaml
index efbe2ee..1405282 100644
--- a/examples/task-example.yaml
+++ b/examples/task-example.yaml
@@ -46,7 +46,11 @@
 - task:
     step: run training
     name: task 4 logspace
-    type: random_search
+    type: RANDOM-SEARCH # will be normalized
+    execution-batch-size: 42
+    execution-count: 420
+    optimization-target-metric: goodness
+    optimization-target-value: 7.2
     parameters:
       - name: A
         style: logspace
diff --git a/valohai_yaml/objs/task.py b/valohai_yaml/objs/task.py
index c6774ad..eb00644 100644
--- a/valohai_yaml/objs/task.py
+++ b/valohai_yaml/objs/task.py
@@ -29,7 +29,7 @@ class TaskType(Enum):
             return TaskType.GRID_SEARCH
         if isinstance(value, TaskType):
             return value
-        value = str(value).lower()
+        value = str(value).lower().replace("-", "_")
         return TaskType(value)
 
 
@@ -75,8 +75,7 @@ class Task(Item):
     def parse(cls, data: Any) -> Task:
         kwargs = data.copy()
         kwargs["parameters"] = consume_array_of(kwargs, "parameters", VariantParameter)
-        kwargs["stop_condition"] = kwargs.pop("stop-condition", None)
-        inst = cls(**kwargs)
+        inst = cls(**{key.replace("-", "_"): value for key, value in kwargs.items()})
         inst._original_data = data
         return inst
 
diff --git a/valohai_yaml/schema/task.yaml b/valohai_yaml/schema/task.yaml
index cbe188b..7b84fb9 100644
--- a/valohai_yaml/schema/task.yaml
+++ b/valohai_yaml/schema/task.yaml
@@ -8,24 +8,18 @@ properties:
   step:
     type: string
     description: The step to run with.
-  execution_count:
+  execution-count:
     type: integer
-  execution_batch_size:
+  execution-batch-size:
     type: integer
-  optimization_target_metric:
+  optimization-target-metric:
     type: string
-  optimization_target_value:
-    type: float
+  optimization-target-value:
+    type: number
   engine:
     type: string
   type:
     type: string
-    enum:
-      - bayesian_tpe
-      - distributed
-      - grid_search
-      - manual_search
-      - random_search
   name:
     type: string
     description: The unique name for this task.


Test patch:
diff --git a/tests/test_schemata.py b/tests/test_schemata.py
new file mode 100644
index 0000000..91d6f11
--- /dev/null
+++ b/tests/test_schemata.py
@@ -0,0 +1,14 @@
+from pathlib import Path
+
+from valohai_yaml.validation import SCHEMATA_DIRECTORY
+
+
+def test_schemata_has_no_underscores():
+    for yaml_path in Path(SCHEMATA_DIRECTORY).rglob("*.yaml"):
+        with yaml_path.open("r", encoding="utf-8") as f:
+            for lineno, line in enumerate(f, 1):
+                clean_line = line.partition("#")[0].strip()
+                if clean_line.endswith(":") and "_" in clean_line:
+                    raise AssertionError(
+                        f"Underscore in YAML key in {yaml_path!r}:{lineno}: {line!r}",
+                    )
diff --git a/tests/test_task.py b/tests/test_task.py
index 42acaf4..7595524 100644
--- a/tests/test_task.py
+++ b/tests/test_task.py
@@ -1,4 +1,5 @@
 from valohai_yaml.objs import Config, Task
+from valohai_yaml.objs.task import TaskType
 from valohai_yaml.objs.variant_parameter import VariantParameterStyle
 
 
@@ -11,3 +12,12 @@ def test_tasks_parameters(task_config: Config):
         if len(task.parameters) > 0:
             assert task.parameters[0].name == "A"
             assert isinstance(task.parameters[0].style, VariantParameterStyle)
+
+
+def test_task_additional_fields(task_config: Config):
+    task = task_config.tasks["task 4 logspace"]
+    assert task.execution_batch_size == 42
+    assert task.execution_count == 420
+    assert task.optimization_target_metric == "goodness"
+    assert task.optimization_target_value == 7.2
+    assert task.type == TaskType.RANDOM_SEARCH