Repository: projectmesa/mesa
Pull request #1784 (2023-08-30 18:44:47)

Add docstring for jupyterviz make_user_input that documents supported inputs
- add a docstring to jupyterviz `make_user_input` method that documents the supported solara inputs
- update parameters to `make_user_input` - drop unused parameter, use more readable variable names

some related conversation on #1772 and follow up work to #1777 

I haven't added any tests yet since there is no existing testing infrastructure for the jupyterviz stuff, but I did test locally with a simulation that uses a slider int and a switch parameter and the changes work

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/projectmesa/mesa/pull/1784?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) Report
Patch coverage: **`12.50%`** and no project coverage change.
> Comparison is base [(`79bde5a`)](https://app.codecov.io/gh/projectmesa/mesa/commit/79bde5a259cfab4adbbfb2a9bd0bb4219af2713c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) 80.02% compared to head [(`fb1ab47`)](https://app.codecov.io/gh/projectmesa/mesa/pull/1784?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) 80.02%.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main    #1784   +/-   ##
=======================================
  Coverage   80.02%   80.02%           
=======================================
  Files          15       15           
  Lines         881      881           
  Branches      188      188           
=======================================
  Hits          705      705           
  Misses        155      155           
  Partials       21       21           
```


| [Files Changed](https://app.codecov.io/gh/projectmesa/mesa/pull/1784?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) | Coverage Δ | |
|---|---|---|
| [mesa/experimental/jupyter\_viz.py](https://app.codecov.io/gh/projectmesa/mesa/pull/1784?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa#diff-bWVzYS9leHBlcmltZW50YWwvanVweXRlcl92aXoucHk=) | `20.00% <12.50%> (ø)` | |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/projectmesa/mesa/pull/1784?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa).

rht: This PR LGTM, but I'm merging #1786 first because that PR is a huge refactor, will cause a merge conflict, and it is easier to redo this PR instead of that one. Sorry for this.
rht: There is indeed a merge conflict.
rlskoeser: @rht thanks for letting me know. I'll see what I can figure out; looking forward to seeing the updates.

Patch:
diff --git a/mesa/experimental/jupyter_viz.py b/mesa/experimental/jupyter_viz.py
index c048be35..6007b9a8 100644
--- a/mesa/experimental/jupyter_viz.py
+++ b/mesa/experimental/jupyter_viz.py
@@ -32,10 +32,10 @@ def JupyterViz(
 
     # 1. User inputs
     user_inputs = {}
-    for k, v in model_params_input.items():
-        user_input = solara.use_reactive(v["value"])
-        user_inputs[k] = user_input.value
-        make_user_input(user_input, k, v)
+    for name, options in model_params_input.items():
+        user_input = solara.use_reactive(options["value"])
+        user_inputs[name] = user_input.value
+        make_user_input(user_input, name, options)
 
     # 2. Model
     def make_model():
@@ -142,29 +142,44 @@ def check_param_is_fixed(param):
         return True
 
 
-def make_user_input(user_input, k, v):
-    if v["type"] == "SliderInt":
+def make_user_input(user_input, name, options):
+    """Initialize a user input for configurable model parameters.
+    Currently supports :class:`solara.SliderInt`, :class:`solara.SliderFloat`,
+    and :class:`solara.Select`.
+
+    Args:
+        user_input: :class:`solara.reactive` object with initial value
+        name: field name; used as fallback for label if 'label' is not in options
+        options: dictionary with options for the input, including label,
+        min and max values, and other fields specific to the input type.
+    """
+    # label for the input is "label" from options or name
+    label = options.get("label", name)
+    input_type = options.get("type")
+    if input_type == "SliderInt":
         solara.SliderInt(
-            v.get("label", "label"),
+            label,
             value=user_input,
-            min=v.get("min"),
-            max=v.get("max"),
-            step=v.get("step"),
+            min=options.get("min"),
+            max=options.get("max"),
+            step=options.get("step"),
         )
-    elif v["type"] == "SliderFloat":
+    elif input_type == "SliderFloat":
         solara.SliderFloat(
-            v.get("label", "label"),
+            label,
             value=user_input,
-            min=v.get("min"),
-            max=v.get("max"),
-            step=v.get("step"),
+            min=options.get("min"),
+            max=options.get("max"),
+            step=options.get("step"),
         )
-    elif v["type"] == "Select":
+    elif input_type == "Select":
         solara.Select(
-            v.get("label", "label"),
-            value=v.get("value"),
-            values=v.get("values"),
+            label,
+            value=options.get("value"),
+            values=options.get("values"),
         )
+    else:
+        raise ValueError(f"{input_type} is not a supported input type")
 
 
 def make_space(model, agent_portrayal):


Test patch:
diff --git a/tests/test_jupyter_viz.py b/tests/test_jupyter_viz.py
new file mode 100644
index 00000000..b2e701df
--- /dev/null
+++ b/tests/test_jupyter_viz.py
@@ -0,0 +1,48 @@
+import unittest
+from unittest.mock import patch
+
+from mesa.experimental.jupyter_viz import make_user_input
+
+
+class TestMakeUserInput(unittest.TestCase):
+    def test_unsupported_type(self):
+        """unsupported input type should raise ValueError"""
+        # bogus type
+        with self.assertRaisesRegex(ValueError, "not a supported input type"):
+            make_user_input(10, "input", {"type": "bogus"})
+        # no type is specified
+        with self.assertRaisesRegex(ValueError, "not a supported input type"):
+            make_user_input(10, "input", {})
+
+    @patch("mesa.experimental.jupyter_viz.solara")
+    def test_slider_int(self, mock_solara):
+        value = 10
+        name = "num_agents"
+        options = {
+            "type": "SliderInt",
+            "label": "number of agents",
+            "min": 10,
+            "max": 20,
+            "step": 1,
+        }
+        make_user_input(value, name, options)
+        mock_solara.SliderInt.assert_called_with(
+            options["label"],
+            value=value,
+            min=options["min"],
+            max=options["max"],
+            step=options["step"],
+        )
+
+    @patch("mesa.experimental.jupyter_viz.solara")
+    def test_label_fallback(self, mock_solara):
+        """name should be used as fallback label"""
+        value = 10
+        name = "num_agents"
+        options = {
+            "type": "SliderInt",
+        }
+        make_user_input(value, name, options)
+        mock_solara.SliderInt.assert_called_with(
+            name, value=value, min=None, max=None, step=None
+        )