MEWTROS
Add server/app.py, uv.lock, openenv-core dependency, and project.scripts entry point
9aa6de6 | #!/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 ============== | |
| async def root(): | |
| """Health check endpoint.""" | |
| return {"status": "ok", "environment": "data-cleaning-openenv", "version": "1.0.0"} | |
| 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)) | |
| 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)) | |
| 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)) | |
| 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)) | |
| 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} | |
| ] | |
| } | |
| 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() | |