Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions mesa/model_share.py
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the most logical place for this file? (also looking at my co-maintainers)

Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""This module contains functions to generate a shareable py.cafe link containing Python code and dependencies."""

import base64
import gzip
import json
import os
from pathlib import Path
from urllib.parse import quote


def get_pycafe_link(
files: dict[str, str] | None = None,
requirements: str | None = None,
autodetect_files: bool = False,
) -> str:
r"""Generate a shareable py.cafe link containing Python code and dependencies.
Args:
files(dict[str, str] | None): Map of file names to paths, if files are not in the current directory.
requirements(str | None): Package dependencies, one per line other than the essential ones.
autodetect_files(bool): If True, scans directory for Python files instead of using `files`. Mutually exclusive with `files`.
Example:
>>> files = {
>>> "model.py": "wolf_sheep\\model.py",
>>> "agents.py": "wolf_sheep\\agents.py",
>>> "app.py": "wolf_sheep\\app.py"
>>> }
>>> get_pycafe_link(files=files)
Returns:
str: URL with encoded application code and dependencies.
"""
requirements = (
requirements or "mesa\nmatplotlib\nnumpy\nnetworkx\nsolara\naltair\npandas"
)

app = ""
file_list = []

if autodetect_files:
all_files = _scan_python_files()
for file in all_files:
with open(file) as f:

Check warning on line 47 in mesa/model_share.py

View check run for this annotation

Codecov / codecov/patch

mesa/model_share.py#L47

Added line #L47 was not covered by tests
if file.endswith("app.py"):
app += f.read()

Check warning on line 49 in mesa/model_share.py

View check run for this annotation

Codecov / codecov/patch

mesa/model_share.py#L49

Added line #L49 was not covered by tests
else:
file_dict = {}
file_dict["name"] = os.path.basename(file)
file_dict["content"] = f.read()
file_list.append(file_dict)

Check warning on line 54 in mesa/model_share.py

View check run for this annotation

Codecov / codecov/patch

mesa/model_share.py#L51-L54

Added lines #L51 - L54 were not covered by tests
else:
for file in files:
with open(files[file]) as f:
file_content = f.read()

Check warning on line 58 in mesa/model_share.py

View check run for this annotation

Codecov / codecov/patch

mesa/model_share.py#L57-L58

Added lines #L57 - L58 were not covered by tests
if file == "app.py":
app += file_content

Check warning on line 60 in mesa/model_share.py

View check run for this annotation

Codecov / codecov/patch

mesa/model_share.py#L60

Added line #L60 was not covered by tests
else:
file_dict = {"name": file, "content": file_content}
file_list.append(file_dict)

Check warning on line 63 in mesa/model_share.py

View check run for this annotation

Codecov / codecov/patch

mesa/model_share.py#L62-L63

Added lines #L62 - L63 were not covered by tests

json_object = {"code": app, "requirements": requirements, "files": file_list}
json_text = json.dumps(json_object)
# Compress using gzip to make the url shorter
compressed_json_text = gzip.compress(json_text.encode("utf8"))
# Encode in base64
base64_text = base64.b64encode(compressed_json_text).decode("utf8")
c = quote(base64_text)
url = f"https://py.cafe/snippet/solara/v1#c={c}"

return url


def _scan_python_files(directory_path: str = ".") -> list[str]:
"""Scan a directory for specific Python files (model.py, app.py, agents.py)."""
path = Path(directory_path)
python_files = [
str(file)
for file in path.glob("*.py")
if file.name in ["model.py", "app.py", "agents.py"]
]

return python_files
9 changes: 9 additions & 0 deletions mesa/visualization/solara_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import mesa.visualization.components.altair_components as components_altair
from mesa.experimental.devs.simulator import Simulator
from mesa.mesa_logging import create_module_logger, function_logger
from mesa.model_share import get_pycafe_link
from mesa.visualization.command_console import CommandConsole
from mesa.visualization.space_renderer import SpaceRenderer
from mesa.visualization.user_param import Slider
Expand Down Expand Up @@ -146,6 +147,14 @@ def SolaraViz(

with solara.AppBar():
solara.AppBarTitle(name if name else model.value.__class__.__name__)
solara.Button(
label="Open on PyCafe",
color="blue",
attributes={
"href": get_pycafe_link(autodetect_files=True),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for link generation.

The current implementation assumes get_pycafe_link will always succeed, but file operations might fail if the required files aren't found or are inaccessible. Consider implementing error handling to provide a graceful fallback.

 solara.Button(
     label="Open on PyCafe",
     color="blue",
     attributes={
-        "href": get_pycafe_link(autodetect_files=True),
+        "href": _get_pycafe_link_with_fallback(),
         "target": "_blank",
     },
 )

+# Add this helper function elsewhere in the file
+def _get_pycafe_link_with_fallback():
+    try:
+        return get_pycafe_link(autodetect_files=True)
+    except Exception as e:
+        _mesa_logger.warning(f"Failed to generate PyCafe link: {e}")
+        return "https://py.cafe/snippet/solara/v1"  # Fallback to empty project
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"href": get_pycafe_link(autodetect_files=True),
solara.Button(
label="Open on PyCafe",
color="blue",
attributes={
- "href": get_pycafe_link(autodetect_files=True),
+ "href": _get_pycafe_link_with_fallback(),
"target": "_blank",
},
)
# Add this helper function elsewhere in the file
def _get_pycafe_link_with_fallback():
try:
return get_pycafe_link(autodetect_files=True)
except Exception as e:
_mesa_logger.warning(f"Failed to generate PyCafe link: {e}")
return "https://py.cafe/snippet/solara/v1" # Fallback to empty project

"target": "_blank",
},
)
solara.lab.ThemeToggle()

with solara.Sidebar(), solara.Column():
Expand Down