Repository: projectmesa/mesa
Pull request #1693 (2023-05-12 06:35:02)

breaking: Remove UserSettableParameter

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/projectmesa/mesa/pull/1693?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) Report
Patch coverage: **`100.00`**% and project coverage change: **`-0.25`** :warning:
> Comparison is base [(`a999396`)](https://app.codecov.io/gh/projectmesa/mesa/commit/a999396b708bc02790c52b114466292fc35efd54?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) 81.52% compared to head [(`3cca12d`)](https://app.codecov.io/gh/projectmesa/mesa/pull/1693?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) 81.27%.

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


```diff
@@            Coverage Diff             @@
##             main    #1693      +/-   ##
==========================================
- Coverage   81.52%   81.27%   -0.25%     
==========================================
  Files          18       18              
  Lines        1402     1346      -56     
  Branches      272      257      -15     
==========================================
- Hits         1143     1094      -49     
+ Misses        213      209       -4     
+ Partials       46       43       -3     
```


| [Impacted Files](https://app.codecov.io/gh/projectmesa/mesa/pull/1693?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) | Coverage Δ | |
|---|---|---|
| [mesa/visualization/UserParam.py](https://app.codecov.io/gh/projectmesa/mesa/pull/1693?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa#diff-bWVzYS92aXN1YWxpemF0aW9uL1VzZXJQYXJhbS5weQ==) | `100.00% <ø> (+5.03%)` | :arrow_up: |
| [mesa/visualization/ModularVisualization.py](https://app.codecov.io/gh/projectmesa/mesa/pull/1693?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa#diff-bWVzYS92aXN1YWxpemF0aW9uL01vZHVsYXJWaXN1YWxpemF0aW9uLnB5) | `67.07% <100.00%> (ø)` | |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/projectmesa/mesa/pull/1693?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](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: Merge conflict resolved.

Patch:
diff --git a/mesa/space.py b/mesa/space.py
index 0820aee3..abdb1869 100644
--- a/mesa/space.py
+++ b/mesa/space.py
@@ -1069,22 +1069,27 @@ class NetworkGrid:
         self.G.nodes[node_id]["agent"].append(agent)
         agent.pos = node_id
 
-    def get_neighbors(
+    def get_neighborhood(
         self, node_id: int, include_center: bool = False, radius: int = 1
     ) -> list[int]:
         """Get all adjacent nodes within a certain radius"""
         if radius == 1:
-            neighbors = list(self.G.neighbors(node_id))
+            neighborhood = list(self.G.neighbors(node_id))
             if include_center:
-                neighbors.append(node_id)
+                neighborhood.append(node_id)
         else:
             neighbors_with_distance = nx.single_source_shortest_path_length(
                 self.G, node_id, radius
             )
             if not include_center:
                 del neighbors_with_distance[node_id]
-            neighbors = sorted(neighbors_with_distance.keys())
-        return neighbors
+            neighborhood = sorted(neighbors_with_distance.keys())
+        return neighborhood
+
+    def get_neighbors(self, node_id: int, include_center: bool = False) -> list[Agent]:
+        """Get all agents in adjacent nodes."""
+        neighborhood = self.get_neighborhood(node_id, include_center)
+        return self.get_cell_list_contents(neighborhood)
 
     def move_agent(self, agent: Agent, node_id: int) -> None:
         """Move an agent from its current node to a new node."""
diff --git a/mesa/visualization/ModularVisualization.py b/mesa/visualization/ModularVisualization.py
index 6e594f79..2df25938 100644
--- a/mesa/visualization/ModularVisualization.py
+++ b/mesa/visualization/ModularVisualization.py
@@ -106,7 +106,7 @@ import tornado.ioloop
 import tornado.web
 import tornado.websocket
 
-from mesa.visualization.UserParam import UserParam, UserSettableParameter
+from mesa.visualization.UserParam import UserParam
 
 # Suppress several pylint warnings for this file.
 # Attributes being defined outside of init is a Tornado feature.
@@ -121,9 +121,7 @@ CHART_JS_FILE = "external/chart-3.6.1.min.js"
 
 
 def is_user_param(val):
-    return isinstance(val, UserSettableParameter) or issubclass(
-        val.__class__, UserParam
-    )
+    return issubclass(val.__class__, UserParam)
 
 
 class VisualizationElement:
diff --git a/mesa/visualization/UserParam.py b/mesa/visualization/UserParam.py
index 7fd2103b..d97d196c 100644
--- a/mesa/visualization/UserParam.py
+++ b/mesa/visualization/UserParam.py
@@ -1,5 +1,4 @@
 import numbers
-from warnings import warn
 
 NUMBER = "number"
 CHECKBOX = "checkbox"
@@ -8,134 +7,6 @@ SLIDER = "slider"
 STATIC_TEXT = "static_text"
 
 
-class UserSettableParameter:
-    """A class for providing options to a visualization for a given parameter.
-
-    UserSettableParameter can be used instead of keyword arguments when specifying model parameters in an
-    instance of a `ModularServer` so that the parameter can be adjusted in the UI without restarting the server.
-
-    Validation of correctly-specified params happens on startup of a `ModularServer`. Each param is handled
-    individually in the UI and sends callback events to the server when an option is updated. That option is then
-    re-validated, in the `value.setter` property method to ensure input is correct from the UI to `reset_model`
-    callback.
-
-    Parameter types include:
-        - 'number' - a simple numerical input
-        - 'checkbox' - boolean checkbox
-        - 'choice' - String-based dropdown input, for selecting choices within a model
-        - 'slider' - A number-based slider input with settable increment
-        - 'static_text' - A non-input textbox for displaying model info.
-
-    Examples:
-
-    # Simple number input
-    number_option = UserSettableParameter('number', 'My Number', value=123)
-
-    # Checkbox input
-    boolean_option = UserSettableParameter('checkbox', 'My Boolean', value=True)
-
-    # Choice input
-    choice_option = UserSettableParameter('choice', 'My Choice', value='Default choice',
-                                          choices=['Default Choice', 'Alternate Choice'])
-
-    # Slider input
-    slider_option = UserSettableParameter('slider', 'My Slider', value=123, min_value=10, max_value=200, step=0.1)
-
-    # Static text
-    static_text = UserSettableParameter('static_text', value="This is a descriptive textbox")
-    """
-
-    NUMBER = NUMBER
-    CHECKBOX = CHECKBOX
-    CHOICE = CHOICE
-    SLIDER = SLIDER
-    STATIC_TEXT = STATIC_TEXT
-
-    TYPES = (NUMBER, CHECKBOX, CHOICE, SLIDER, STATIC_TEXT)
-
-    _ERROR_MESSAGE = "Missing or malformed inputs for '{}' Option '{}'"
-
-    def __init__(
-        self,
-        param_type=None,
-        name="",
-        value=None,
-        min_value=None,
-        max_value=None,
-        step=1,
-        choices=None,
-        description=None,
-    ):
-        warn(
-            "UserSettableParameter is deprecated in favor of UserParam objects "
-            "such as Slider, Checkbox, Choice, StaticText, NumberInput. "
-            "See the examples folder for how to use them. "
-            "UserSettableParameter will be removed in the next major release."
-        )
-        if choices is None:
-            choices = []
-        if param_type not in self.TYPES:
-            raise ValueError(f"{param_type} is not a valid Option type")
-        self.param_type = param_type
-        self.name = name
-        self._value = value
-        self.min_value = min_value
-        self.max_value = max_value
-        self.step = step
-        self.choices = choices
-        self.description = description
-
-        # Validate option types to make sure values are supplied properly
-        msg = self._ERROR_MESSAGE.format(self.param_type, name)
-        valid = True
-
-        if self.param_type == self.NUMBER:
-            valid = self.value is not None
-
-        elif self.param_type == self.SLIDER:
-            valid = not (
-                self.value is None or self.min_value is None or self.max_value is None
-            )
-
-        elif self.param_type == self.CHOICE:
-            valid = not (self.value is None or len(self.choices) == 0)
-
-        elif self.param_type == self.CHECKBOX:
-            valid = isinstance(self.value, bool)
-
-        elif self.param_type == self.STATIC_TEXT:
-            valid = isinstance(self.value, str)
-
-        if not valid:
-            raise ValueError(msg)
-
-    @property
-    def value(self):
-        return self._value
-
-    @value.setter
-    def value(self, value):
-        self._value = value
-        if self.param_type == self.SLIDER:
-            if self._value < self.min_value:
-                self._value = self.min_value
-            elif self._value > self.max_value:
-                self._value = self.max_value
-        elif (self.param_type == self.CHOICE) and self._value not in self.choices:
-            print(
-                "Selected choice value not in available choices, selected first choice from 'choices' list"
-            )
-            self._value = self.choices[0]
-
-    @property
-    def json(self):
-        result = self.__dict__.copy()
-        result["value"] = result.pop(
-            "_value"
-        )  # Return _value as value, value is the same
-        return result
-
-
 class UserParam:
     _ERROR_MESSAGE = "Missing or malformed inputs for '{}' Option '{}'"
 


Test patch:
diff --git a/tests/test_space.py b/tests/test_space.py
index a7aba252..c445135c 100644
--- a/tests/test_space.py
+++ b/tests/test_space.py
@@ -354,10 +354,10 @@ class TestSingleNetworkGrid(unittest.TestCase):
             assert a.pos == pos
 
     def test_get_neighbors(self):
-        assert len(self.space.get_neighbors(0, include_center=True)) == 3
-        assert len(self.space.get_neighbors(0, include_center=False)) == 2
-        assert len(self.space.get_neighbors(2, include_center=True, radius=3)) == 7
-        assert len(self.space.get_neighbors(2, include_center=False, radius=3)) == 6
+        assert len(self.space.get_neighborhood(0, include_center=True)) == 3
+        assert len(self.space.get_neighborhood(0, include_center=False)) == 2
+        assert len(self.space.get_neighborhood(2, include_center=True, radius=3)) == 7
+        assert len(self.space.get_neighborhood(2, include_center=False, radius=3)) == 6
 
     def test_move_agent(self):
         initial_pos = 1
@@ -426,11 +426,11 @@ class TestMultipleNetworkGrid(unittest.TestCase):
 
     def test_get_neighbors(self):
         assert (
-            len(self.space.get_neighbors(0, include_center=True))
+            len(self.space.get_neighborhood(0, include_center=True))
             == TestMultipleNetworkGrid.GRAPH_SIZE
         )
         assert (
-            len(self.space.get_neighbors(0, include_center=False))
+            len(self.space.get_neighborhood(0, include_center=False))
             == TestMultipleNetworkGrid.GRAPH_SIZE - 1
         )
 
diff --git a/tests/test_usersettableparam.py b/tests/test_usersettableparam.py
index 9106d050..8f02fa4b 100644
--- a/tests/test_usersettableparam.py
+++ b/tests/test_usersettableparam.py
@@ -6,61 +6,50 @@ from mesa.visualization.UserParam import (
     NumberInput,
     Slider,
     StaticText,
-    UserSettableParameter,
 )
 
 
 class TestOption(TestCase):
     def setUp(self):
-        self.number_option = UserSettableParameter("number", value=123)
-        self.number_option_standalone = NumberInput("number", value=123)
-        self.checkbox_option = UserSettableParameter("checkbox", value=True)
-        self.checkbox_option_standalone = Checkbox(value=True)
-        self.choice_option = UserSettableParameter(
-            "choice",
+        self.number_option = NumberInput("number", value=123)
+        self.checkbox_option = Checkbox(value=True)
+        self.choice_option = Choice(
             value="I am your default choice",
             choices=["I am your default choice", "I am your other choice"],
         )
-        self.choice_option_standalone = Choice(
-            value="I am your default choice",
-            choices=["I am your default choice", "I am your other choice"],
-        )
-        self.slider_option = UserSettableParameter(
-            "slider", value=123, min_value=100, max_value=200
-        )
-        self.slider_option_standalone = Slider(value=123, min_value=100, max_value=200)
+        self.slider_option = Slider(value=123, min_value=100, max_value=200)
         self.static_text_option = StaticText("Hurr, Durr Im'a Sheep")
 
     def test_number(self):
-        for option in [self.number_option, self.number_option_standalone]:
-            assert option.value == 123
-            option.value = 321
-            assert option.value == 321
+        option = self.number_option
+        assert option.value == 123
+        option.value = 321
+        assert option.value == 321
 
     def test_checkbox(self):
-        for option in [self.checkbox_option, self.checkbox_option_standalone]:
-            assert option.value
-            option.value = False
-            assert not option.value
+        option = self.checkbox_option
+        assert option.value
+        option.value = False
+        assert not option.value
 
     def test_choice(self):
-        for option in [self.choice_option, self.choice_option_standalone]:
-            assert option.value == "I am your default choice"
-            option.value = "I am your other choice"
-            assert option.value == "I am your other choice"
-            option.value = "I am not an available choice"
-            assert option.value == "I am your default choice"
+        option = self.choice_option
+        assert option.value == "I am your default choice"
+        option.value = "I am your other choice"
+        assert option.value == "I am your other choice"
+        option.value = "I am not an available choice"
+        assert option.value == "I am your default choice"
 
     def test_slider(self):
-        for option in [self.slider_option, self.slider_option_standalone]:
-            assert option.value == 123
-            option.value = 150
-            assert option.value == 150
-            option.value = 0
-            assert option.value == 100
-            option.value = 300
-            assert option.value == 200
-            assert option.json["value"] == 200
+        option = self.slider_option
+        assert option.value == 123
+        option.value = 150
+        assert option.value == 150
+        option.value = 0
+        assert option.value == 100
+        option.value = 300
+        assert option.value == 200
+        assert option.json["value"] == 200
         with self.assertRaises(ValueError):
             Slider()
 
diff --git a/tests/test_visualization.py b/tests/test_visualization.py
index 86e05b0c..2c3ebd4c 100644
--- a/tests/test_visualization.py
+++ b/tests/test_visualization.py
@@ -7,7 +7,10 @@ from mesa.space import MultiGrid
 from mesa.time import SimultaneousActivation
 from mesa.visualization.ModularVisualization import ModularServer
 from mesa.visualization.modules import CanvasGrid, TextElement
-from mesa.visualization.UserParam import UserSettableParameter
+from mesa.visualization.UserParam import (
+    NumberInput,
+    Slider,
+)
 
 
 class MockAgent(mesa.Agent):
@@ -65,8 +68,8 @@ class TestModularServer(TestCase):
         self.user_params = {
             "width": 1,
             "height": 1,
-            "key1": UserSettableParameter("number", "Test Parameter", 101),
-            "key2": UserSettableParameter("slider", "Test Parameter", 200, 0, 300, 10),
+            "key1": NumberInput("Test Parameter", 101),
+            "key2": Slider("Test Parameter", 200, 0, 300, 10),
         }
 
         self.viz_elements = [
@@ -95,8 +98,6 @@ class TestModularServer(TestCase):
     def test_user_params(self):
         print(self.server.user_params)
         assert self.server.user_params == {
-            "key1": UserSettableParameter("number", "Test Parameter", 101).json,
-            "key2": UserSettableParameter(
-                "slider", "Test Parameter", 200, 0, 300, 10
-            ).json,
+            "key1": NumberInput("Test Parameter", 101).json,
+            "key2": Slider("Test Parameter", 200, 0, 300, 10).json,
         }