Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
372 changes: 129 additions & 243 deletions README.md

Large diffs are not rendered by default.

37 changes: 34 additions & 3 deletions cleanup.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,38 @@
sudo pkill -f container
#!/bin/bash
#
# CodeRunner Cleanup Script
# Gracefully stops containers and cleans up resources
#

echo "CodeRunner Cleanup"
echo "=================="

container system start
# Step 1: Try graceful stop of coderunner container
echo "→ Stopping coderunner container gracefully..."
container stop coderunner 2>/dev/null && echo " Stopped coderunner" || echo " coderunner not running"

# Wait for graceful shutdown
sleep 2

container rm buildkit
# Step 2: Remove coderunner container if it still exists
echo "→ Removing coderunner container..."
container rm coderunner 2>/dev/null || true

# Step 3: Stop the container system
echo "→ Stopping container system..."
container system stop 2>/dev/null || true

# Step 4: Clean up buildkit if requested
if [ "$1" = "--full" ]; then
echo "→ Full cleanup: removing buildkit..."
container rm buildkit 2>/dev/null || true
fi

echo ""
echo "✅ Cleanup complete"
echo ""
echo "To restart CodeRunner:"
echo " ./install.sh"
echo ""
echo "For full cleanup (including buildkit):"
echo " ./cleanup.sh --full"
19 changes: 19 additions & 0 deletions coderunner/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""
CodeRunner - Sandboxed Python execution for Mac.

Usage:
from coderunner import CodeRunner, execute

# Using the client
cr = CodeRunner()
result = cr.execute("print('hello')")
print(result.stdout)

# One-liner
print(execute("2 + 2"))
"""

from .client import CodeRunner, ExecutionResult, BrowserResult, execute

__all__ = ["CodeRunner", "ExecutionResult", "BrowserResult", "execute"]
__version__ = "0.1.0"
164 changes: 164 additions & 0 deletions coderunner/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
"""
CodeRunner Python Client

Simple wrapper for the CodeRunner REST API.

Usage:
from coderunner import CodeRunner

cr = CodeRunner() # defaults to http://coderunner.local:8222
result = cr.execute("print('hello')")
print(result.stdout)
"""

import httpx
from dataclasses import dataclass
from typing import Optional


@dataclass
class ExecutionResult:
"""Result of code execution."""
stdout: str
stderr: str
execution_time: float
success: bool


@dataclass
class BrowserResult:
"""Result of browser content extraction."""
content: str
url: str
success: bool
error: Optional[str] = None


class CodeRunner:
"""
Python client for CodeRunner API.

Args:
base_url: Server URL. Defaults to http://coderunner.local:8222
timeout: Request timeout in seconds. Defaults to 300 (5 minutes).

Example:
>>> cr = CodeRunner()
>>> result = cr.execute("print('hello')")
>>> print(result.stdout)
hello
"""

def __init__(
self,
base_url: str = "http://coderunner.local:8222",
timeout: float = 300.0
):
self.base_url = base_url.rstrip("/")
self._client = httpx.Client(timeout=timeout)

def execute(self, code: str) -> ExecutionResult:
"""
Execute Python code and return result.

Args:
code: Python code to execute.

Returns:
ExecutionResult with stdout, stderr, execution_time, and success.

Example:
>>> result = cr.execute("print(2 + 2)")
>>> result.stdout
'4\\n'
"""
response = self._client.post(
f"{self.base_url}/v1/execute",
json={"code": code}
)
response.raise_for_status()
data = response.json()
return ExecutionResult(
stdout=data.get("stdout", ""),
stderr=data.get("stderr", ""),
execution_time=data.get("execution_time", 0.0),
success=not data.get("stderr")
)

def browse(self, url: str) -> BrowserResult:
"""
Navigate to URL and extract text content.

Args:
url: URL to navigate to.

Returns:
BrowserResult with extracted content.

Example:
>>> result = cr.browse("https://example.com")
>>> print(result.content[:50])
"""
response = self._client.post(
f"{self.base_url}/v1/browser/content",
json={"url": url}
)
response.raise_for_status()
data = response.json()
return BrowserResult(
content=data.get("readable_content", {}).get("content", ""),
url=url,
success=data.get("status") == "success",
error=data.get("error")
)

def health(self) -> bool:
"""
Check if server is healthy.

Returns:
True if server is healthy, False otherwise.
"""
try:
response = self._client.get(f"{self.base_url}/health")
return response.status_code == 200
except Exception:
return False
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Catching a generic Exception is too broad and can mask underlying issues in the code. It's better to catch more specific exceptions related to network requests. For httpx, httpx.RequestError is the base exception for request-related problems and would be more appropriate here.

Suggested change
except Exception:
return False
except httpx.RequestError:
return False


def close(self):
"""Close the HTTP client."""
self._client.close()

def __enter__(self):
return self

def __exit__(self, *args):
self.close()


def execute(code: str, base_url: str = "http://coderunner.local:8222") -> str:
"""
Execute Python code and return stdout.

Convenience function for one-off execution.

Args:
code: Python code to execute.
base_url: Server URL. Defaults to http://coderunner.local:8222

Returns:
stdout from execution.

Raises:
RuntimeError: If execution produces stderr.

Example:
>>> from coderunner import execute
>>> execute("print(2 + 2)")
'4\\n'
"""
with CodeRunner(base_url) as cr:
result = cr.execute(code)
if result.stderr:
raise RuntimeError(result.stderr)
return result.stdout
23 changes: 20 additions & 3 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,26 @@ sleep 2

# Start the container system (this is blocking and will wait for kernel download if needed)
echo "Starting the Sandbox Container system (this may take a few minutes if downloading kernel)..."
if ! container system start; then
echo "❌ Failed to start container system."
exit 1
echo "Note: First run may take 5+ minutes to download the kernel image."

# Use timeout to prevent indefinite blocking (10 minutes max)
if command -v timeout &> /dev/null; then
if ! timeout 600 container system start; then
if [ $? -eq 124 ]; then
echo "❌ Container system start timed out after 10 minutes."
echo "This usually means the kernel download is taking too long."
echo "Try running manually: container system start"
else
echo "❌ Failed to start container system."
fi
exit 1
fi
else
# timeout command not available (older macOS), run without timeout
if ! container system start; then
echo "❌ Failed to start container system."
exit 1
fi
fi

# Quick verification that system is ready
Expand Down
43 changes: 43 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "coderunner"
version = "0.1.0"
description = "Python client for CodeRunner - sandboxed code execution on Mac"
readme = "README.md"
license = {text = "Apache-2.0"}
requires-python = ">=3.10"
authors = [
{name = "InstaVM", email = "hello@instavm.io"}
]
keywords = ["sandbox", "code-execution", "jupyter", "mac", "apple-silicon"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Operating System :: MacOS",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"httpx>=0.24.0",
]

[project.urls]
Homepage = "https://github.com/instavm/coderunner"
Repository = "https://github.com/instavm/coderunner"
Issues = "https://github.com/instavm/coderunner/issues"

[project.optional-dependencies]
dev = [
"pytest>=7.0",
"pytest-asyncio>=0.21",
]

[tool.setuptools.packages.find]
include = ["coderunner*"]
Loading