Repository: wandb/wandb

Issue:
[CLI]: wandb sweep raises unnecessary `ValueError`
### Describe the bug

<!--- Description of the issue below  -->

It should be possible to have a hyperparameter value that accepts `None` as an option in a sweep, e.g.:

```yaml
parameters
      noise:
        values: [null, 0.05, 0.25, 0.5]
        probabilities: [0.4, 0.2, 0.2, 0.2]
```

However, [lazy handling of `None` values](https://github.com/wandb/wandb/blob/0e7311d8851a975a78c47197be2253c41e51028e/wandb/sdk/launch/sweeps/utils.py#L228-L230) prevents this by raising a `ValueError` on the assumption a value wasn't found, e.g:

```bash
ValueError: No "value" found for command["args"]["noise"]
```

This is an easy fix, just wrap a `config["value"]` call in a try... except block and raise the ValueError on KeyErrors.

### Additional Files

_No response_

### Environment

WandB version: 0.15.5

OS: MacOS 12.6

Python version: 3.8.17

### Additional Context

_No response_


Discussion:
Hello @ahalev !

Thank you for reporting this, I will be making a bug report for our team for this so that they can look into this. Thanks again!
Apologies, I went ahead and realized that Sweeps do accept `None` values, but we do not accept `null` values in sweeps. Would passing in `None` values instead of `null` be sufficient for your usecase?
WandB Internal User commented: 
rsanandres-wandb commented: 
Apologies, I went ahead and realized that Sweeps do accept `None` values, but we do not accept `null` values in sweeps. Would passing in `None` values instead of `null` be sufficient for your usecase?

There's no builtin `null` value in python just `None`. I wasn't entirely explicit here, but I'm doing some preprocessing on the YAML file, and (trying) to pass `None` values, e.g.

```python
config = {
    'parameters': {
        'noise': {
            'values': [None, 0.05, 0.25, 0.5]
    # other stuff here as well
}}}

wandb.sweep(config)
```.

This raises the `ValueError`.
I also ran into this when using a `.yaml` config for my sweep.
Using either `null` or `~` in the yaml file leads to a ValueError.

I think all variants of `None` should be valid. My current project alone has multiple use cases for this.

### Environment

WandB version: 0.15.10

OS: Ubuntu 22.04

Python version: 3.10.9

Same issue. Is there any plan to fix this?
Hi @ahalev @timoklein @shenmishajing, thanks for flagging this! Let me please raise the issue with our Engineering Team and get back to you

Resolving patch:
diff --git a/wandb/sdk/launch/sweeps/utils.py b/wandb/sdk/launch/sweeps/utils.py
--- a/wandb/sdk/launch/sweeps/utils.py
+++ b/wandb/sdk/launch/sweeps/utils.py
@@ -227,9 +227,12 @@ def create_sweep_command_args(command: Dict) -> Dict[str, Any]:
     # (5) flags without equals (e.g. --foo bar)
     args_no_equals: List[str] = []
     for param, config in command["args"].items():
-        _value: Any = config.get("value", None)
-        if _value is None:
+        # allow 'None' as a valid value, but error if no value is found
+        try:
+            _value: Any = config["value"]
+        except KeyError:
             raise ValueError('No "value" found for command["args"]["%s"]' % param)
+
         _flag: str = f"{param}={_value}"
         flags.append("--" + _flag)
         flags_no_hyphens.append(_flag)


Test patch:
diff --git a/tests/pytest_tests/system_tests/test_sweep/test_wandb_agent.py b/tests/pytest_tests/system_tests/test_sweep/test_wandb_agent.py
--- a/tests/pytest_tests/system_tests/test_sweep/test_wandb_agent.py
+++ b/tests/pytest_tests/system_tests/test_sweep/test_wandb_agent.py
@@ -12,7 +12,12 @@
 
 def test_agent_create_command_args():
     mock_command = {
-        "args": {"a": {"value": True}, "b": {"value": False}, "c": {"value": 1}}
+        "args": {
+            "a": {"value": True},
+            "b": {"value": False},
+            "c": {"value": 1},
+            "d": {"value": None},
+        }
     }
 
     _return = create_sweep_command_args(mock_command)
@@ -22,10 +27,10 @@ def test_agent_create_command_args():
     assert "args_no_boolean_flags" in _return
     assert "args_json" in _return
     # test fields are correct
-    assert _return["args"] == ["--a=True", "--b=False", "--c=1"]
-    assert _return["args_no_hyphens"] == ["a=True", "b=False", "c=1"]
-    assert _return["args_no_boolean_flags"] == ["--a", "--c=1"]
-    assert _return["args_json"] == ['{"a": true, "b": false, "c": 1}']
+    assert _return["args"] == ["--a=True", "--b=False", "--c=1", "--d=None"]
+    assert _return["args_no_hyphens"] == ["a=True", "b=False", "c=1", "d=None"]
+    assert _return["args_no_boolean_flags"] == ["--a", "--c=1", "--d=None"]
+    assert _return["args_json"] == ['{"a": true, "b": false, "c": 1, "d": null}']
 
 
 def test_agent_create_command_args_bad_command():
diff --git a/tests/pytest_tests/system_tests/test_sweep/test_wandb_sweep.py b/tests/pytest_tests/system_tests/test_sweep/test_wandb_sweep.py
--- a/tests/pytest_tests/system_tests/test_sweep/test_wandb_sweep.py
+++ b/tests/pytest_tests/system_tests/test_sweep/test_wandb_sweep.py
@@ -85,6 +85,12 @@
     "method": "random",
     "parameters": {"param1": {"values": [1, 2, 3]}},
 }
+SWEEP_CONFIG_BAYES_NONES: Dict[str, Any] = {
+    "name": "mock-sweep-bayes-with-none",
+    "method": "bayes",
+    "metric": {"name": "metric1", "goal": "maximize"},
+    "parameters": {"param1": {"values": [None, 1, 2, 3]}, "param2": {"value": None}},
+}
 
 # Minimal list of valid sweep configs
 VALID_SWEEP_CONFIGS_MINIMAL: List[Dict[str, Any]] = [
@@ -184,3 +190,10 @@ def test_add_run_to_existing_sweep(user, wandb_init, relay_server):
 
     run_attrs = relay.context.get_run_attrs(run.id)
     assert sweep_id == run_attrs.sweep_name
+
+
+def test_nones_validation():
+    api = wandb.apis.InternalApi()
+    filled = api.api._validate_config_and_fill_distribution(SWEEP_CONFIG_BAYES_NONES)
+    assert filled["parameters"]["param1"]["values"] == [None, 1, 2, 3]
+    assert filled["parameters"]["param2"]["value"] is None