-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Feat: Added Model sharing through links #2727
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e4bee05
f9c653b
e6df076
fab40bd
1e2dc89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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: | ||
| if file.endswith("app.py"): | ||
| app += f.read() | ||
| else: | ||
| file_dict = {} | ||
| file_dict["name"] = os.path.basename(file) | ||
| file_dict["content"] = f.read() | ||
| file_list.append(file_dict) | ||
| else: | ||
| for file in files: | ||
| with open(files[file]) as f: | ||
| file_content = f.read() | ||
| if file == "app.py": | ||
| app += file_content | ||
| else: | ||
| file_dict = {"name": file, "content": file_content} | ||
| file_list.append(file_dict) | ||
|
|
||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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 | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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 | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -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), | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add error handling for link generation. The current implementation assumes 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
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| "target": "_blank", | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| solara.lab.ThemeToggle() | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| with solara.Sidebar(), solara.Column(): | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
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)