Skip to content
Draft
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@ pip install -e .
# Or using uv (faster)
uv pip install -e .

# For development with testing dependencies (pytest, etc.)
pip install -e ".[dev]"

# Or using uv
uv pip install -e ".[dev]"

# Run server locally without Docker
uv run server --host 0.0.0.0 --port 8000
```
Expand Down
4 changes: 4 additions & 0 deletions examples/local_coding_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def main():
print(f" {i}. Code: {code.replace(chr(10), '\\n')[:50]}...")
print(f" → stdout: {result.observation.stdout.strip()}")
print(f" → exit_code: {result.observation.exit_code}")
print(f" → reward: {result.reward}")
if result.observation.stderr:
print(f" → stderr: {result.observation.stderr}")

Expand All @@ -84,6 +85,8 @@ def main():
print(f" {i}. {description}")
print(f" Code: {code.replace(chr(10), '\\n')[:40]}...")
print(f" → exit_code: {result.observation.exit_code}")
print(f" → reward: {result.reward}")

if result.observation.stderr:
# Truncate long error messages
error_msg = result.observation.stderr[:100]
Expand Down Expand Up @@ -116,6 +119,7 @@ def main():
except Exception as e:
print(f"\n❌ Test failed: {e}")
import traceback

traceback.print_exc()
return False

Expand Down
7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "openenv"
version = "0.1.1"
version = "0.1.2"
description = "A unified framework for reinforcement learning environments"
readme = "README.md"
requires-python = ">=3.10"
Expand All @@ -26,6 +26,11 @@ dependencies = [
"tomli-w>=1.2.0"
]

[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
]

[project.scripts]
openenv = "openenv_cli.__main__:main"

Expand Down
33 changes: 26 additions & 7 deletions src/core/containers/runtime/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,23 +192,42 @@ def stop_container(self) -> None:
import subprocess

try:
# Stop container
# Try graceful stop first (Docker waits 5 seconds before SIGKILL)
# Subprocess timeout is 15 seconds to allow Docker's grace period
subprocess.run(
["docker", "stop", self._container_id],
["docker", "stop", "--time=5", self._container_id],
capture_output=True,
check=True,
timeout=10,
timeout=15,
)
except subprocess.TimeoutExpired:
# Graceful stop timed out, force kill the container
print(f"Warning: Container {self._container_id} did not stop gracefully, forcing kill...")
try:
subprocess.run(
["docker", "kill", self._container_id],
capture_output=True,
check=True,
timeout=5,
)
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
# Container might already be stopped
pass
except subprocess.CalledProcessError:
# Container might already be stopped
pass

# Remove container
# Always try to remove the container
try:
subprocess.run(
["docker", "rm", self._container_id],
["docker", "rm", "-f", self._container_id],
capture_output=True,
check=True,
timeout=10,
)
except subprocess.CalledProcessError:
# Container might already be stopped/removed
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
# Container might already be removed or removal failed
# Use -f flag to force removal even if still running
pass
finally:
self._container_id = None
Expand Down
174 changes: 147 additions & 27 deletions src/envs/coding_env/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,112 @@ tags:

A Python code execution environment that runs arbitrary Python code and returns results. Perfect for testing code execution infrastructure and demonstrating environment usage patterns.

## Quick Start
## Installation & Usage

The simplest way to use the Coding environment is through the `CodingEnv` class:
The Coding Environment supports two usage modes:

### Mode 1: In-Repository Development (Recommended for Contributors)

Use this mode when developing or contributing to OpenEnv.

**Setup:**
```bash
# 1. Clone the repository
git clone https://github.com/facebookresearch/OpenEnv.git
cd OpenEnv

# 2. Install in development mode
pip install -e .

# 3. Build the Docker image (from repo root)
docker build -t coding-env:latest -f src/envs/coding_env/server/Dockerfile .

# 4. Run the example
python ./examples/local_coding_env.py
```

**Code example:**
```python
# Use in-repo import paths
from envs.coding_env import CodeAction, CodingEnv

try:
# Create environment from Docker image
coding_env = CodingEnv.from_docker_image("coding-env:latest")

# Execute Python code
result = coding_env.step(CodeAction(code="print('Hello, World!')"))
print(f"stdout: {result.observation.stdout.strip()}")
print(f"exit_code: {result.observation.exit_code}")
finally:
coding_env.close()
```

### Mode 2: Standalone Package (For End Users)

Use this mode when using coding_env as a standalone package.

**Setup:**
```bash
# 1. Install openenv-core (once available on PyPI)
pip install openenv-core

# 2. Install coding_env package
pip install openenv-coding_env

# 3. Use the same Docker image as in-repo mode
# The client-server communicate over HTTP, so the Docker build mode doesn't matter for testing
# You can use the in-repo built image: coding-env:latest
```

**Code example:**
```python
# Use standalone import paths
from coding_env import CodeAction, CodingEnv

try:
# Connect to the same Docker image built in in-repo mode
coding_env = CodingEnv.from_docker_image("coding-env:latest")
result = coding_env.step(CodeAction(code="print('Hello, World!')"))
print(f"stdout: {result.observation.stdout.strip()}")
finally:
coding_env.close()
```

## Quick Start Example

**In-repo mode:**
```bash
# From OpenEnv repo root, after pip install -e .
python ./examples/local_coding_env.py
```

**Standalone mode:**

For standalone testing, use a separate test script (the repo example uses in-repo imports only):

```python
# save as test_standalone.py
from coding_env import CodeAction, CodingEnv

try:
# Uses the same Docker image as in-repo mode
client = CodingEnv.from_docker_image("coding-env:latest")
result = client.step(CodeAction(code="print('Hello from standalone!')"))
print(f"stdout: {result.observation.stdout.strip()}")
finally:
client.close()
```

**Note:** The client (your Python code) and server (Docker container) are independent. The standalone client can connect to the in-repo Docker image because they communicate over HTTP.

### Manual Usage Example

Once set up (either mode), the usage is identical:

```python
from coding_env import CodeAction, CodingEnv # or: from envs.coding_env import ...

try:
# Create environment from Docker image
coding_env = CodingEnv.from_docker_image("coding-env:latest")
Expand All @@ -48,21 +147,12 @@ finally:
coding_env.close()
```

That's it! The `CodingEnv.from_docker_image()` method handles:
The `CodingEnv.from_docker_image()` method handles:
- Starting the Docker container
- Waiting for the server to be ready
- Connecting to the environment
- Container cleanup when you call `close()`

## Building the Docker Image

Before using the environment, you need to build the Docker image:

```bash
# From project root
docker build -t coding-env:latest -f src/envs/coding_env/server/Dockerfile .
```

## Environment Details

### Action
Expand All @@ -88,10 +178,14 @@ docker build -t coding-env:latest -f src/envs/coding_env/server/Dockerfile .
If you already have a Coding environment server running, you can connect directly:

```python
# In-repo mode
from envs.coding_env import CodingEnv

# OR standalone mode
from coding_env import CodingEnv

# Connect to existing server
coding_env = CodingEnv(base_url="<ENV_HTTP_URL_HERE>")
coding_env = CodingEnv(base_url="http://localhost:8000")

# Use as normal
result = coding_env.reset()
Expand All @@ -100,34 +194,60 @@ result = coding_env.step(CodeAction(code="print('Hello!')"))

Note: When connecting to an existing server, `coding_env.close()` will NOT stop the server.

## Development & Testing
## Docker Build Options

The Dockerfile supports two build modes:

```bash
# In-repo build (default) - from OpenEnv repo root
docker build -t coding-env:latest -f src/envs/coding_env/server/Dockerfile .

# Standalone build - from coding_env package directory (for distribution only)
docker build -t coding-env:standalone -f server/Dockerfile --build-arg BUILD_MODE=standalone .
```

**When to use each mode:**

- **In-repo mode (default)**: For development and testing (works with both client modes)
- **Standalone mode**: Only needed when distributing the Docker image without the full OpenEnv repo

### Running the Full Example
**Important:** For local testing, the in-repo Docker image works with both in-repo and standalone clients. The client and server communicate over HTTP, so they're independent. The BUILD_MODE distinction is primarily for distribution/packaging purposes.

## Development & Testing

Run the complete example that demonstrates the full workflow:
### Running Tests

```bash
python3 src/envs/coding_env/client/example_usage.py
# From repo root
pytest tests/envs/test_python_codeact_reset.py
```

This example shows:
- Creating an environment from a Docker image
- Resetting and executing code through the environment
- Automatic cleanup with `close()`
### Building Packages Locally

```bash
# Build openenv-core
cd src
python -m build -w

# Build coding_env
cd envs/coding_env
python -m build -w
```

## Project Structure

```
coding_env/
├── README.md # This file
├── models.py # Action, Observation, and State models
├── client/
├── coding_env_client.py # CodingEnv client implementation
│ └── example_usage.py # Usage examples
├── README.md # This file
├── pyproject.toml # Package configuration
├── __init__.py # Package exports
├── models.py # Action, Observation, and State models
── client.py # CodingEnv client implementation
└── server/
├── python_codeact_env.py # Core environment logic
├── python_executor.py # Code execution wrapper
├── app.py # FastAPI application
├── transforms.py # Observation transforms
├── Dockerfile # Container image definition
├── Dockerfile # Container image (dual-mode)
└── README.md # Server-specific documentation
```
17 changes: 12 additions & 5 deletions src/envs/coding_env/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,18 @@

from __future__ import annotations

from openenv_core.client_types import StepResult

from openenv_core.http_env_client import HTTPEnvClient

from coding_env.models import CodeAction, CodeObservation, CodeState
# Support both standalone and in-repo imports
try:
# Standalone imports (when installed from pip)
from openenv_core.client_types import StepResult
from openenv_core.http_env_client import HTTPEnvClient
except ImportError:
# In-repo imports (when running from OpenEnv repository)
from core.client_types import StepResult
from core.http_env_client import HTTPEnvClient

# Use relative imports for sibling modules - works in both modes
from .models import CodeAction, CodeObservation, CodeState


class CodingEnv(HTTPEnvClient[CodeAction, CodeObservation]):
Expand Down
8 changes: 7 additions & 1 deletion src/envs/coding_env/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@

from dataclasses import dataclass

from openenv_core.env_server.interfaces import Action, Observation, State
# Support both standalone and in-repo imports
try:
# Standalone imports (when installed from pip)
from openenv_core.env_server.types import Action, Observation, State
except ImportError:
# In-repo imports (when running from OpenEnv repository)
from core.env_server.types import Action, Observation, State


@dataclass
Expand Down
2 changes: 1 addition & 1 deletion src/envs/coding_env/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "openenv-coding_env"
version = "0.1.0"
version = "0.1.1"
description = "Coding Environment for OpenEnv"
requires-python = ">=3.10"
dependencies = [
Expand Down
Loading