Repository: projectmesa/mesa
Pull request #1880 (2023-11-17 09:30:10)

Replace Black with ruff format
See https://astral.sh/blog/the-ruff-formatter, with `ruff format` being
30x faster.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/projectmesa/mesa/pull/1880?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`eb7d439`)](https://app.codecov.io/gh/projectmesa/mesa/commit/eb7d439eb0b61c57523bbe967a7a207214f8122f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) 77.10% compared to head [(`f5b3c12`)](https://app.codecov.io/gh/projectmesa/mesa/pull/1880?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=projectmesa) 77.10%.


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


```diff
@@           Coverage Diff           @@
##             main    #1880   +/-   ##
=======================================
  Coverage   77.10%   77.10%           
=======================================
  Files          15       15           
  Lines         974      974           
  Branches      213      213           
=======================================
  Hits          751      751           
  Misses        195      195           
  Partials       28       28           
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/projectmesa/mesa/pull/1880?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: I marked this as draft because #1870 hasn't been merged yet.
rht: @EwoutH the pre-commit Ruff and the GH Actions Ruff are redundant. Which one do you think should be kept? I find pre-commit to be faster.
rht: And the versions will be automatically updated, and hence doesn't require manual maintenance #1864.
EwoutH: > the pre-commit Ruff and the GH Actions Ruff are redundant. Which one do you think should be kept? I find pre-commit to be faster.

I would say pre-commit, since linting is primarily a pre-commit action and not necessarily tests.
rht: OK, I have removed it in this PR.
rht: Rebased. This PR is ready.

Patch:
diff --git a/.github/workflows/build_lint.yml b/.github/workflows/build_lint.yml
index ab9f22cd..3ba7340b 100644
--- a/.github/workflows/build_lint.yml
+++ b/.github/workflows/build_lint.yml
@@ -61,29 +61,3 @@ jobs:
     - if: matrix.os == 'ubuntu'
       name: Codecov
       uses: codecov/codecov-action@v3
-
-  lint-ruff:
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v4
-    - name: Set up Python 3.12
-      uses: actions/setup-python@v4
-      with:
-        python-version: "3.12"
-    - run: pip install ruff~=0.1.1  # Update periodically
-    - name: Lint with ruff
-      # Include `--format=github` to enable automatic inline annotations.
-      # Use settings from pyproject.toml.
-      run: ruff . --output-format=github --extend-exclude 'mesa/cookiecutter-mesa/*'
-
-  lint-black:
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v4
-    - name: Set up Python 3.12
-      uses: actions/setup-python@v4
-      with:
-        python-version: "3.12"
-    - run: pip install black[jupyter]
-    - name: Lint with black
-      run: black --check --exclude=mesa/cookiecutter-mesa/* .
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0326176e..ce38ed84 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,11 +3,16 @@ ci:
     autofix_prs: false
 
 repos:
--   repo: https://github.com/psf/black
-    rev: 23.10.1
-    hooks:
-      - id: black-jupyter
-        exclude: ^mesa/cookiecutter-mesa/
+- repo: https://github.com/astral-sh/ruff-pre-commit
+  # Ruff version.
+  rev: v0.1.5
+  hooks:
+    # Run the linter.
+    - id: ruff
+      types_or: [ python, pyi, jupyter ]
+    # Run the formatter.
+    - id: ruff-format
+      types_or: [ python, pyi, jupyter ]
 -   repo: https://github.com/asottile/pyupgrade
     rev: v3.15.0
     hooks:
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index c901713c..839fcec8 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -80,7 +80,7 @@ We test by implementing simple models and through traditional unit tests in the
 
     py.test --cov=mesa tests/
 
-With respect to code standards, we follow `PEP8`_ and the `Google Style Guide`_. We recommend to use `black`_ as an automated code formatter. You can automatically format your code using `pre-commit`_, which will prevent `git commit` of unstyled code and will automatically apply black style so you can immediately re-run `git commit`. To set up pre-commit run the following commands:
+With respect to code standards, we follow `PEP8`_ and the `Google Style Guide`_. We use `ruff format`_ (a more performant alternative to `black`) as an automated code formatter. You can automatically format your code using `pre-commit`_, which will prevent `git commit` of unstyled code and will automatically apply black style so you can immediately re-run `git commit`. To set up pre-commit run the following commands:
 
 .. code-block:: bash
 
diff --git a/MANIFEST.in b/MANIFEST.in
index e07a78ea..2d4a80a9 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,7 +1,6 @@
 include MANIFEST.in
 include LICENSE
 include HISTORY.rst
-include README.rst
 include setup.py
 graft mesa/cookiecutter-mesa
 global-exclude *.py[co]
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..a1f617b8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,119 @@
+# Mesa: Agent-based modeling in Python 3+
+
+[![GitHub Actions build status](https://github.com/projectmesa/mesa/workflows/build/badge.svg)](https://github.com/projectmesa/mesa/actions)
+[![Coverage status](https://codecov.io/gh/projectmesa/mesa/branch/main/graph/badge.svg)](https://codecov.io/gh/projectmesa/mesa)
+[![code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
+[![chat](https://img.shields.io/matrix/project-mesa:matrix.org?label=chat&logo=Matrix)](https://matrix.to/#/#project-mesa:matrix.org)
+
+Mesa allows users to quickly create agent-based models using built-in
+core components (such as spatial grids and agent schedulers) or
+customized implementations; visualize them using a browser-based
+interface; and analyze their results using Python's data analysis
+tools. Its goal is to be the Python 3-based alternative to NetLogo,
+Repast, or MASON.
+
+![A screenshot of the Schelling Model in Mesa](https://raw.githubusercontent.com/projectmesa/mesa/main/docs/images/Mesa_Screenshot.png)
+
+*Above: A Mesa implementation of the Schelling segregation model, this
+can be displayed in browser windows or Jupyter.*
+
+## Features
+
+-   Modular components
+-   Browser-based visualization
+-   Built-in tools for analysis
+-   Example model library
+
+## Using Mesa
+
+Getting started quickly:
+
+``` bash
+pip install mesa
+```
+
+You can also use [pip]{.title-ref} to install the github version:
+
+``` bash
+pip install -U -e git+https://github.com/projectmesa/mesa@main#egg=mesa
+```
+
+Or any other (development) branch on this repo or your own fork:
+
+``` bash
+pip install -U -e git+https://github.com/YOUR_FORK/mesa@YOUR_BRANCH#egg=mesa
+```
+
+For resources or help on using Mesa, check out the following:
+
+-   [Intro to Mesa Tutorial](http://mesa.readthedocs.org/en/stable/tutorials/intro_tutorial.html) (An introductory model, the Boltzmann
+    Wealth Model, for beginners or those new to Mesa.)
+-   [Complexity Explorer Tutorial](https://www.complexityexplorer.org/courses/172-agent-based-models-with-python-an-introduction-to-mesa) (An advanced-beginner model,
+    SugarScape with Traders, with instructional videos)
+-   [Mesa Examples](https://github.com/projectmesa/mesa-examples/tree/main/examples) (A repository of seminal ABMs using Mesa and
+    examples of employing specific Mesa Features)
+-   [Docs](http://mesa.readthedocs.org/) (Mesa's documentation, API and useful snippets)
+-   [Discussions](https://github.com/projectmesa/mesa/discussions) (GitHub threaded discussions about Mesa)
+-   [Matrix Chat](https://matrix.to/#/#project-mesa:matrix.org) (Chat Forum via Matrix to talk about Mesa)
+
+## Running Mesa in Docker
+
+You can run Mesa in a Docker container in a few ways.
+
+If you are a Mesa developer, first [install Docker
+Compose](https://docs.docker.com/compose/install/) and then, in the
+folder containing the Mesa Git repository, you run:
+
+``` bash
+$ docker compose up
+# If you want to make it run in the background, you instead run
+$ docker compose up -d
+```
+
+This runs the Schelling model, as an example.
+
+With the docker-compose.yml file in this Git repository, the `docker compose up` command does two important things:
+
+-   It mounts the mesa root directory (relative to the
+    docker-compose.yml file) into /opt/mesa and runs pip install -e on
+    that directory so your changes to mesa should be reflected in the
+    running container.
+-   It binds the docker container's port 8765 to your host system's
+    port 8765 so you can interact with the running model as usual by
+    visiting localhost:8765 on your browser
+
+If you are a model developer that wants to run Mesa on a model, you need
+to:
+
+-   make sure that your model folder is inside the folder containing the
+    docker-compose.yml file
+-   change the `MODEL_DIR` variable in docker-compose.yml to point to
+    the path of your model
+-   make sure that the model folder contains an app.py file
+
+Then, you just need to run [docker compose up -d]{.title-ref} to have it
+accessible from `localhost:8765`.
+
+## Contributing to Mesa
+
+Want to join the Mesa team or just curious about what is happening with
+Mesa? You can\...
+
+> -   Join our [Matrix chat room](https://matrix.to/#/#project-mesa:matrix.org) in which questions, issues, and
+>     ideas can be (informally) discussed.
+> -   Come to a monthly dev session (you can find dev session times,
+>     agendas and notes on [Mesa discussions](https://github.com/projectmesa/mesa/discussions)).
+> -   Just check out the code on [GitHub](https://github.com/projectmesa/mesa/).
+
+If you run into an issue, please file a [ticket](https://github.com/projectmesa/mesa/issues) for us to discuss. If
+possible, follow up with a pull request.
+
+If you would like to add a feature, please reach out via [ticket](https://github.com/projectmesa/mesa/issues) or
+join a dev session (see [Mesa discussions](https://github.com/projectmesa/mesa/discussions)). A feature is most likely
+to be added if you build it!
+
+Don't forget to checkout the [Contributors guide](https://github.com/projectmesa/mesa/blob/main/CONTRIBUTING.rst).
+
+## Citing Mesa
+
+To cite Mesa in your publication, you can use the [CITATION.bib](https://github.com/projectmesa/mesa/blob/main/CITATION.bib).
diff --git a/README.rst b/README.rst
deleted file mode 100644
index b3278af6..00000000
--- a/README.rst
+++ /dev/null
@@ -1,132 +0,0 @@
-Mesa: Agent-based modeling in Python 3+
-=========================================
-
-.. image:: https://github.com/projectmesa/mesa/workflows/build/badge.svg
-        :target: https://github.com/projectmesa/mesa/actions
-
-.. image:: https://codecov.io/gh/projectmesa/mesa/branch/main/graph/badge.svg
-        :target: https://codecov.io/gh/projectmesa/mesa
-
-.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
-        :target: https://github.com/psf/black
-
-.. image:: https://img.shields.io/matrix/project-mesa:matrix.org?label=chat&logo=Matrix
-        :target: https://matrix.to/#/#project-mesa:matrix.org
-
-Mesa allows users to quickly create agent-based models using built-in core components (such as spatial grids and agent schedulers) or customized implementations; visualize them using a browser-based interface; and analyze their results using Python's data analysis tools. Its goal is to be the Python 3-based alternative to NetLogo, Repast, or MASON.
-
-
-.. image:: https://raw.githubusercontent.com/projectmesa/mesa/main/docs/images/Mesa_Screenshot.png
-   :width: 100%
-   :scale: 100%
-   :alt: A screenshot of the Schelling Model in Mesa
-
-*Above: A Mesa implementation of the Schelling segregation model,
-this can be displayed in browser windows or Jupyter.*
-
-.. _`Mesa` : https://github.com/projectmesa/mesa/
-
-
-Features
-------------
-
-* Modular components
-* Browser-based visualization
-* Built-in tools for analysis
-* Example model library
-
-Using Mesa
-------------
-
-Getting started quickly:
-
-.. code-block:: bash
-
-    pip install mesa
-
-You can also use `pip` to install the github version:
-
-.. code-block:: bash
-
-    pip install -U -e git+https://github.com/projectmesa/mesa@main#egg=mesa
-
-Or any other (development) branch on this repo or your own fork:
-
-.. code-block:: bash
-
-    pip install -U -e git+https://github.com/YOUR_FORK/mesa@YOUR_BRANCH#egg=mesa
-
-For resources or help on using Mesa, check out the following:
-
-* `Intro to Mesa Tutorial`_ (An introductory model, the Boltzmann Wealth Model, for beginners or those new to Mesa.)
-* `Complexity Explorer Tutorial`_ (An advanced-beginner model, SugarScape with Traders, with instructional videos)
-* `Mesa Examples`_ (A repository of seminal ABMs using Mesa and examples of employing specific Mesa Features)
-* `Docs`_ (Mesa's documentation, API and useful snippets)
-* `Discussions`_ (GitHub threaded discussions about Mesa)
-* `Matrix Chat`_ (Chat Forum via Matrix to talk about Mesa)
-
-.. _`Intro to Mesa Tutorial` : http://mesa.readthedocs.org/en/stable/tutorials/intro_tutorial.html
-.. _`Complexity Explorer Tutorial` : https://www.complexityexplorer.org/courses/172-agent-based-models-with-python-an-introduction-to-mesa
-.. _`Mesa Examples` : https://github.com/projectmesa/mesa-examples/tree/main/examples
-.. _`Docs` : http://mesa.readthedocs.org/
-.. _`Discussions` : https://github.com/projectmesa/mesa/discussions
-.. _`Matrix Chat` : https://matrix.to/#/#project-mesa:matrix.org
-
-Running Mesa in Docker
-------------------------
-
-You can run Mesa in a Docker container in a few ways.
-
-If you are a Mesa developer, first `install Docker Compose <https://docs.docker.com/compose/install/>`_ and then, in the folder containing the Mesa Git repository, you run:
-
-.. code-block:: bash
-
-    $ docker compose up
-    # If you want to make it run in the background, you instead run
-    $ docker compose up -d
-
-This runs the Schelling model, as an example.
-
-With the docker-compose.yml file in this Git repository, the `docker compose up` command does two important things:
-
-* It mounts the mesa root directory (relative to the docker-compose.yml file) into /opt/mesa and runs pip install -e on that directory so your changes to mesa should be reflected in the running container.
-* It binds the docker container's port 8765 to your host system's port 8765 so you can interact with the running model as usual by visiting localhost:8765 on your browser
-
-
-If you are a model developer that wants to run Mesa on a model, you need to:
-
-* make sure that your model folder is inside the folder containing the docker-compose.yml file
-* change the ``MODEL_DIR`` variable in docker-compose.yml to point to the path of your model
-* make sure that the model folder contains an app.py file
-
-Then, you just need to run `docker compose up -d` to have it accessible from ``localhost:8765``.
-
-Contributing to Mesa
-----------------------------
-
-Want to join the Mesa team or just curious about what is happening with Mesa? You can...
-
-  * Join our `Matrix chat room`_ in which questions, issues, and ideas can be (informally) discussed.
-  * Come to a monthly dev session (you can find dev session times, agendas and notes on `Mesa discussions`_).
-  * Just check out the code on `GitHub`_.
-
-If you run into an issue, please file a `ticket`_ for us to discuss. If possible, follow up with a pull request.
-
-If you would like to add a feature, please reach out via `ticket`_ or join a dev session (see `Mesa discussions`_).
-A feature is most likely to be added if you build it!
-
-Don't forget to checkout the `Contributors guide`_.
-
-.. _`Matrix chat room` : https://matrix.to/#/#project-mesa:matrix.org
-.. _`Mesa discussions` : https://github.com/projectmesa/mesa/discussions
-.. _`GitHub` : https://github.com/projectmesa/mesa/
-.. _`ticket` : https://github.com/projectmesa/mesa/issues
-.. _`Contributors guide` : https://github.com/projectmesa/mesa/blob/main/CONTRIBUTING.rst
-
-
-Citing Mesa
-----------------------------
-
-To cite Mesa in your publication, you can use the `CITATION.bib`_.
-
-.. _`CITATION.bib` : https://github.com/projectmesa/mesa/blob/main/CITATION.bib
diff --git a/docs/conf.py b/docs/conf.py
index d5459150..1a54ab02 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -42,15 +42,12 @@ extensions = [
     "sphinx.ext.mathjax",
     "sphinx.ext.ifconfig",
     "sphinx.ext.viewcode",
-    "myst_nb",
+    "myst_nb",  # For Markdown and Jupyter notebooks
 ]
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ["_templates"]
 
-# The suffix of source filenames.
-source_suffix = ".rst"
-
 # The encoding of source files.
 # source_encoding = 'utf-8-sig'
 
diff --git a/mesa/experimental/jupyter_viz.py b/mesa/experimental/jupyter_viz.py
index 552a1982..d0843448 100644
--- a/mesa/experimental/jupyter_viz.py
+++ b/mesa/experimental/jupyter_viz.py
@@ -1,5 +1,6 @@
 import sys
 import threading
+from typing import List, Optional
 
 import matplotlib.pyplot as plt
 import networkx as nx
@@ -33,7 +34,7 @@ def JupyterViz(
         name: name for display
         agent_portrayal: options for rendering agents (dictionary)
         space_drawer: method to render the agent space for
-            the model; default implementation is :meth:`make_space`;
+            the model; default implementation is the `SpaceMatplotlib` component;
             simulations with no space to visualize should
             specify `space_drawer=False`
         play_interval: play interval (default: 150)
@@ -71,7 +72,9 @@ def JupyterViz(
                 rv.CardTitle(children=["Space"])
                 if space_drawer == "default":
                     # draw with the default implementation
-                    make_space(model, agent_portrayal)
+                    SpaceMatplotlib(
+                        model, agent_portrayal, dependencies=[current_step.value]
+                    )
                 elif space_drawer:
                     # if specified, draw agent space with an alternate renderer
                     space_drawer(model, agent_portrayal)
@@ -103,7 +106,9 @@ def JupyterViz(
             # 4. Space
             if space_drawer == "default":
                 # draw with the default implementation
-                make_space(model, agent_portrayal)
+                SpaceMatplotlib(
+                    model, agent_portrayal, dependencies=[current_step.value]
+                )
             elif space_drawer:
                 # if specified, draw agent space with an alternate renderer
                 space_drawer(model, agent_portrayal)
@@ -310,7 +315,8 @@ def UserInputs(user_params, on_change=None):
             raise ValueError(f"{input_type} is not a supported input type")
 
 
-def make_space(model, agent_portrayal):
+@solara.component
+def SpaceMatplotlib(model, agent_portrayal, dependencies: Optional[List[any]] = None):
     space_fig = Figure()
     space_ax = space_fig.subplots()
     space = getattr(model, "grid", None)
@@ -324,7 +330,7 @@ def make_space(model, agent_portrayal):
     else:
         _draw_grid(space, space_ax, agent_portrayal)
     space_ax.set_axis_off()
-    solara.FigureMatplotlib(space_fig, format="png")
+    solara.FigureMatplotlib(space_fig, format="png", dependencies=dependencies)
 
 
 def _draw_grid(space, space_ax, agent_portrayal):
diff --git a/pyproject.toml b/pyproject.toml
index 7b1fe3a9..ca20d2d8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -36,7 +36,7 @@ classifiers = [
   "Development Status :: 3 - Alpha",
   "Natural Language :: English",
 ]
-readme = "README.rst"
+readme = "README.md"
 dependencies = [
   "click",
   "cookiecutter",
@@ -52,7 +52,6 @@ dynamic = ["version"]
 
 [project.optional-dependencies]
 dev = [
-  "black",
   "ruff~=0.1.1",  # Update periodically
   "coverage",
   "pytest >= 4.6",
@@ -69,6 +68,7 @@ docs = [
   "pydata_sphinx_theme",
   "seaborn",
   "myst-nb",
+  "myst-parser",  # Markdown in Sphinx
 ]
 
 [project.urls]
@@ -131,6 +131,7 @@ extend-ignore = [
     "RUF012",  # Mutable class attributes should be annotated with `typing.ClassVar` TODO
     "S310",  # Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
     "S603",  # `subprocess` call: check for execution of untrusted input
+    "ISC001",  # ruff format asks to disable this feature
 ]
 extend-exclude = ["docs", "build"]
 # Hardcode to Python 3.8.


Test patch:
diff --git a/tests/test_jupyter_viz.py b/tests/test_jupyter_viz.py
index 97021252..dd125ffc 100644
--- a/tests/test_jupyter_viz.py
+++ b/tests/test_jupyter_viz.py
@@ -82,13 +82,15 @@ class TestMakeUserInput(unittest.TestCase):
 
 
 class TestJupyterViz(unittest.TestCase):
-    @patch("mesa.experimental.jupyter_viz.make_space")
-    def test_call_space_drawer(self, mock_make_space):
+    @patch("mesa.experimental.jupyter_viz.SpaceMatplotlib")
+    def test_call_space_drawer(self, mock_space_matplotlib):
         mock_model_class = Mock()
         agent_portrayal = {
             "Shape": "circle",
             "color": "gray",
         }
+        current_step = 0
+        dependencies = [current_step]
         # initialize with space drawer unspecified (use default)
         # component must be rendered for code to run
         solara.render(
@@ -99,13 +101,13 @@ class TestJupyterViz(unittest.TestCase):
             )
         )
         # should call default method with class instance and agent portrayal
-        mock_make_space.assert_called_with(
-            mock_model_class.return_value, agent_portrayal
+        mock_space_matplotlib.assert_called_with(
+            mock_model_class.return_value, agent_portrayal, dependencies=dependencies
         )
 
         # specify no space should be drawn; any false value should work
         for falsy_value in [None, False, 0]:
-            mock_make_space.reset_mock()
+            mock_space_matplotlib.reset_mock()
             solara.render(
                 JupyterViz(
                     model_class=mock_model_class,
@@ -115,7 +117,7 @@ class TestJupyterViz(unittest.TestCase):
                 )
             )
             # should call default method with class instance and agent portrayal
-            assert mock_make_space.call_count == 0
+            assert mock_space_matplotlib.call_count == 0
 
         # specify a custom space method
         altspace_drawer = Mock()