MEWTROS
Add server/app.py, uv.lock, openenv-core dependency, and project.scripts entry point
9aa6de6
Raw
History Blame Contribute Delete
5.55 kB
#!/usr/bin/env python3
"""
OpenEnv Server Entry Point for Data Cleaning Environment.
This module provides the server entry point for multi-mode deployment.
"""
import sys
import os
# Add parent directory to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from typing import Optional, Dict, Any
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import uvicorn
from env.environment import DataCleaningEnvironment
# Create FastAPI app
app = FastAPI(
title="Data Cleaning OpenEnv API",
description="OpenEnv environment for data cleaning tasks",
version="1.0.0"
)
# Add CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Global environment instance
env_instance: Optional[DataCleaningEnvironment] = None
current_obs = None
# ============== API Models ==============
class ResetRequest(BaseModel):
task: str = "easy"
seed: Optional[int] = 42
class ActionRequest(BaseModel):
action_type: str
column: Optional[str] = None
fill_strategy: Optional[str] = None
fill_value: Optional[Any] = None
normalize_method: Optional[str] = None
text_case: Optional[str] = None
outlier_threshold: Optional[float] = None
# ============== API Endpoints ==============
@app.get("/")
async def root():
"""Health check endpoint."""
return {"status": "ok", "environment": "data-cleaning-openenv", "version": "1.0.0"}
@app.post("/reset")
async def api_reset(request: ResetRequest = None):
"""Reset the environment and return initial observation."""
global env_instance, current_obs
if request is None:
request = ResetRequest()
try:
task = request.task if request.task in ["easy", "medium", "hard"] else "easy"
seed = request.seed if request.seed is not None else 42
env_instance = DataCleaningEnvironment(task=task, seed=seed)
current_obs = env_instance.reset()
return {
"observation": current_obs.model_dump(),
"info": {
"task": task,
"seed": seed
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/reset")
async def api_reset_get(task: str = "easy", seed: int = 42):
"""Reset the environment (GET version for compatibility)."""
return await api_reset(ResetRequest(task=task, seed=seed))
@app.post("/step")
async def api_step(action: ActionRequest):
"""Execute an action and return the result."""
global env_instance, current_obs
if env_instance is None:
await api_reset(ResetRequest())
try:
action_dict = {"action_type": action.action_type}
if action.column:
action_dict["column"] = action.column
if action.fill_strategy:
action_dict["fill_strategy"] = action.fill_strategy
if action.fill_value is not None:
action_dict["fill_value"] = action.fill_value
if action.normalize_method:
action_dict["normalize_method"] = action.normalize_method
if action.text_case:
action_dict["text_case"] = action.text_case
if action.outlier_threshold is not None:
action_dict["outlier_threshold"] = action.outlier_threshold
result = env_instance.step(action_dict)
current_obs = result.observation
return {
"observation": result.observation.model_dump(),
"reward": result.reward.model_dump() if result.reward else {"total": 0},
"done": result.done,
"info": result.info
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/state")
async def api_state():
"""Get current environment state."""
global env_instance
if env_instance is None:
raise HTTPException(status_code=400, detail="Environment not initialized. Call /reset first.")
try:
state = env_instance.state()
return {"state": state.model_dump()}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/tasks")
async def api_tasks():
"""List available tasks."""
return {
"tasks": [
{"id": "easy", "description": "Simple dataset with missing values and duplicates", "max_steps": 15},
{"id": "medium", "description": "Medium complexity with formatting issues", "max_steps": 25},
{"id": "hard", "description": "Complex dataset with outliers and mixed issues", "max_steps": 40}
]
}
@app.get("/actions")
async def api_actions():
"""List available actions."""
return {
"actions": [
{"type": "fill_missing", "params": ["column", "fill_strategy", "fill_value"]},
{"type": "remove_duplicates", "params": []},
{"type": "normalize_column", "params": ["column", "normalize_method"]},
{"type": "detect_outliers", "params": ["column", "outlier_threshold"]},
{"type": "standardize_text", "params": ["column", "text_case"]},
{"type": "drop_column", "params": ["column"]},
{"type": "finish", "params": []}
]
}
def main():
"""Main entry point for the server."""
uvicorn.run(app, host="0.0.0.0", port=7860)
if __name__ == "__main__":
main()