Skip to content
Merged
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
65 changes: 65 additions & 0 deletions examples/share_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""
Example demonstrating automatic run sharing with p95.

When share=True, a public share link (https://p95.run/{slug}) is printed
after the run completes. Anyone with the link can view the run without
needing an account.

Usage:
export P95_URL=https://p.ninetyfive.gg
export P95_API_KEY=ss67_your_key_here

python examples/share_run.py
"""

import math
import os
import random
import time

from p95 import Run, configure

configure(
base_url=os.environ.get("P95_URL", "https://p.ninetyfive.gg"),
)


def main():
config = {
"learning_rate": 0.01,
"batch_size": 64,
"epochs": 10,
"optimizer": "sgd",
}

with Run(
project="peepo/peepo",
name=f"shared-run-{int(time.time())}",
tags=["example", "shared"],
config=config,
share=True,
) as run:
print(f"Run ID: {run.id}")
print()

for epoch in range(config["epochs"]):
loss = math.exp(-0.3 * epoch) + random.gauss(0, 0.02) + 0.05
accuracy = 1.0 - loss * 0.7 + random.gauss(0, 0.01)

run.log_metrics(
{
"train/loss": max(0.01, loss),
"train/accuracy": max(0.0, min(1.0, accuracy)),
},
step=epoch,
)

print(
f"Epoch {epoch + 1}/{config['epochs']} loss={loss:.4f} acc={accuracy:.4f}"
)
time.sleep(0.1)


if __name__ == "__main__":
main()
5 changes: 5 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ depends = ["build-release"]
env = { P95_LOGDIR = "./logs", P95_BINARY = "./bin/pnf" }
run = "python examples/multi_stage_training.py"

[tasks.demo-share]
description = "Run shared run example (remote mode, prints public share link on completion)"
depends = ["build-sdk"]
run = "python examples/share_run.py"

# ===================
# Development tasks
# ===================
Expand Down
4 changes: 4 additions & 0 deletions sdk/python/src/p95/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,3 +277,7 @@ def link_run_to_job(self, job_id: str, run_id: str) -> Dict[str, Any]:
return self._request(
"POST", f"/jobs/{job_id}/link-run", data={"run_id": run_id}
)

def share_run(self, run_id: str) -> Dict[str, Any]:
"""Create a public share link for a run."""
return self._request("POST", f"/runs/{run_id}/share")
16 changes: 16 additions & 0 deletions sdk/python/src/p95/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ def __init__(
# Server option (local mode only)
start_server: bool = False,
start_tui: bool = False,
# Sharing option (remote mode only)
share: bool = False,
):
"""
Initialize a new run.
Expand All @@ -93,6 +95,9 @@ def __init__(
browser (local mode only). The server stops when the run ends.
start_tui: Automatically open the p95 TUI in a new terminal window
(local mode only). The TUI manages its own internal server.
share: Automatically create a public share link when the run finishes
(remote mode only). The link is printed to stdout in the form
https://p95.run/{slug}.

Raises:
ValidationError: If project format is invalid (remote mode)
Expand Down Expand Up @@ -137,6 +142,7 @@ def __init__(
self._server_manager: Optional["ServerManager"] = None
self._start_server = start_server
self._start_tui = start_tui
self._share = share

# Capture info before creating run
self._git_info = None
Expand Down Expand Up @@ -522,12 +528,22 @@ def _finalize(self, status: str, error: Optional[str] = None) -> None:
# Launch TUI after the script fully exits
if self._server_manager is not None and self._start_tui:
atexit.register(self._server_manager.start)
if self._share:
print("p95: Warning: share=True is only available in remote mode.")
else:
# Flush and stop remote batcher
self._remote_batcher.flush()
self._remote_batcher.stop()
# Update status on server
self._remote_client.update_run_status(self._run_id, status, error=error)
if self._share:
try:
share_response = self._remote_client.share_run(self._run_id)
slug = share_response.get("slug")
if slug:
print(f"p95: Share your run at https://p95.run/{slug}")
except Exception as e:
print(f"p95: Warning: Failed to create share link: {e}")

def __enter__(self) -> "Run":
"""Enter context manager."""
Expand Down
Loading