Spaces:
Sleeping
Sleeping
File size: 13,272 Bytes
519c06d 356ac4f 519c06d 356ac4f 519c06d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 |
#!/usr/bin/env python3
"""
ResearchMate Development Server
A complete Python-based development environment for ResearchMate
"""
import os
import sys
import subprocess
import threading
import time
import logging
from pathlib import Path
from typing import Dict, List, Optional
import signal
import webbrowser
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import platform
import uvicorn
import socket
# Add the project root to Python path
sys.path.append(str(Path(__file__).parent.parent.parent))
# Import the main app from main.py
from ResearchMate.app import app
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(Path(__file__).parent.parent.parent / 'logs' / 'development.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
class FileChangeHandler(FileSystemEventHandler):
"""Handle file changes for auto-reload"""
def __init__(self, callback):
self.callback = callback
self.last_modified = {}
def on_modified(self, event):
if event.is_directory:
return
# Only watch Python files
if not event.src_path.endswith('.py'):
return
# Debounce rapid changes
current_time = time.time()
if event.src_path in self.last_modified:
if current_time - self.last_modified[event.src_path] < 1:
return
self.last_modified[event.src_path] = current_time
logger.info(f"File changed: {event.src_path}")
self.callback()
class ResearchMateDevServer:
"""Development server for ResearchMate"""
def __init__(self, project_root: Optional[Path] = None):
self.project_root = project_root or Path(__file__).parent.parent.parent
self.venv_path = self.project_root / "venv"
self.server_thread = None
self.observer = None
self.is_running = False
self.is_windows = platform.system() == "Windows"
# Store server config for restarts
self.server_host = "127.0.0.1"
self.server_port = 8000
def print_banner(self):
"""Print development server banner"""
banner = """
ResearchMate Development Server
=================================
AI Research Assistant - Development Mode
Auto-reload enabled for Python files
"""
print(banner)
logger.info("Starting ResearchMate development server")
def get_venv_python(self) -> Path:
"""Get path to Python executable in virtual environment"""
# If we're already in a virtual environment (including Conda), use the current Python executable
if sys.prefix != sys.base_prefix or 'CONDA_DEFAULT_ENV' in os.environ:
return Path(sys.executable)
# Otherwise, construct the path to the venv Python executable
if self.is_windows:
return self.venv_path / "Scripts" / "python.exe"
else:
return self.venv_path / "bin" / "python"
def check_virtual_environment(self) -> bool:
"""Check if virtual environment exists"""
# Since we're importing directly, just check if we can import the modules
try:
import ResearchMate.app as app
logger.info("Successfully imported main application")
return True
except ImportError as e:
logger.error(f"Failed to import main application: {e}")
logger.error("Make sure you're in the correct environment with all dependencies installed")
return False
def check_port_available(self, host: str, port: int) -> bool:
"""Check if a port is available"""
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.bind((host, port))
return True
except OSError:
return False
def find_available_port(self, host: str, start_port: int = 8000, max_attempts: int = 10) -> Optional[int]:
"""Find an available port starting from start_port"""
for port in range(start_port, start_port + max_attempts):
if self.check_port_available(host, port):
return port
return None
def start_server_process(self, host: str = "127.0.0.1", port: int = 8000):
"""Start the server using uvicorn directly with the imported app"""
try:
# Check if the requested port is available
if not self.check_port_available(host, port):
logger.warning(f"Port {port} is already in use on {host}")
available_port = self.find_available_port(host, port)
if available_port:
logger.info(f"Using available port {available_port} instead")
port = available_port
self.server_port = port # Update stored port
else:
logger.error(f"No available ports found starting from {port}")
return False
logger.info(f"Starting server on {host}:{port}")
# Run uvicorn with the imported app in a separate thread
def run_server():
uvicorn.run(
app,
host=host,
port=port,
reload=False, # We handle reload ourselves with file watcher
log_level="info"
)
# Start server in background thread
self.server_thread = threading.Thread(target=run_server, daemon=True)
self.server_thread.start()
# Wait a moment for server to start
time.sleep(2)
logger.info("Server process started successfully")
return True
except Exception as e:
logger.error(f"Failed to start server: {e}")
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
return False
def stop_server_process(self):
"""Stop the server process"""
if self.server_thread:
logger.info("Stopping server...")
# Note: For development, we'll let the thread finish naturally
# In a real implementation, you might want to implement graceful shutdown
self.server_thread = None
def restart_server(self):
"""Restart the server"""
logger.info("File change detected - restarting server...")
logger.info("Note: For full restart, please stop and start the dev server manually")
# Note: Auto-restart is complex with embedded uvicorn
# For now, just log the change. User can manually restart.
def setup_file_watcher(self):
"""Setup file watcher for auto-reload"""
try:
self.observer = Observer()
# Watch source files
watch_paths = [
self.project_root / "src",
self.project_root / "main.py"
]
handler = FileChangeHandler(self.restart_server)
for path in watch_paths:
if path.exists():
if path.is_file():
self.observer.schedule(handler, str(path.parent), recursive=False)
else:
self.observer.schedule(handler, str(path), recursive=True)
self.observer.start()
logger.info("File watcher started")
except Exception as e:
logger.error(f"Failed to setup file watcher: {e}")
def stop_file_watcher(self):
"""Stop file watcher"""
if self.observer:
self.observer.stop()
self.observer.join()
self.observer = None
def open_browser(self, url: str):
"""Open browser after server starts"""
def open_after_delay():
time.sleep(3) # Wait for server to start
try:
webbrowser.open(url)
logger.info(f"Opened browser at {url}")
except Exception as e:
logger.warning(f"Could not open browser: {e}")
thread = threading.Thread(target=open_after_delay)
thread.daemon = True
thread.start()
def run_tests(self):
"""Run project tests"""
try:
logger.info("Running tests...")
logger.info("No tests configured - skipping test run")
except Exception as e:
logger.error(f"Failed to run tests: {e}")
def check_code_quality(self):
"""Check code quality with linting"""
try:
logger.info("Checking code quality...")
python_path = self.get_venv_python()
# Run flake8 if available
try:
result = subprocess.run([
str(python_path), "-m", "flake8",
"src/", "main.py", "--max-line-length=88"
], cwd=self.project_root, capture_output=True, text=True)
if result.returncode == 0:
logger.info("Code quality checks passed")
else:
logger.warning("Code quality issues found:")
print(result.stdout)
except FileNotFoundError:
logger.info("flake8 not installed, skipping code quality check")
except Exception as e:
logger.error(f"Failed to check code quality: {e}")
def start(self, host: str = "127.0.0.1", port: int = 8000, open_browser: bool = True):
"""Start the development server"""
self.print_banner()
if not self.check_virtual_environment():
return False
# Setup signal handlers
def signal_handler(signum, frame):
logger.info("Received interrupt signal")
self.stop()
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
try:
self.is_running = True
# Store server config for restarts
self.server_host = host
self.server_port = port
# Start server
if not self.start_server_process(host, port):
return False
# Use the actual port (might have changed if original was busy)
actual_port = self.server_port
# Setup file watcher
self.setup_file_watcher()
# Open browser
if open_browser:
self.open_browser(f"http://{host}:{actual_port}")
logger.info("Development server started successfully!")
logger.info(f"Web Interface: http://{host}:{actual_port}")
logger.info(f"API Documentation: http://{host}:{actual_port}/docs")
logger.info("File watcher enabled (manual restart required for changes)")
logger.info("Use Ctrl+C to stop")
# Keep the main thread alive
while self.is_running:
time.sleep(1)
except KeyboardInterrupt:
logger.info("Server stopped by user")
except Exception as e:
logger.error(f"Development server error: {e}")
finally:
self.stop()
def stop(self):
"""Stop the development server"""
self.is_running = False
self.stop_file_watcher()
self.stop_server_process()
logger.info("Development server stopped")
def main():
"""Main development server function"""
import argparse
parser = argparse.ArgumentParser(description="ResearchMate Development Server")
parser.add_argument("--host", default="127.0.0.1", help="Host to bind to")
parser.add_argument("--port", type=int, default=8000, help="Port to bind to")
parser.add_argument("--no-browser", action="store_true", help="Don't open browser")
parser.add_argument("--test", action="store_true", help="Run tests only")
parser.add_argument("--lint", action="store_true", help="Check code quality only")
args = parser.parse_args()
dev_server = ResearchMateDevServer()
if args.test:
dev_server.run_tests()
elif args.lint:
dev_server.check_code_quality()
else:
dev_server.start(
host=args.host,
port=args.port,
open_browser=not args.no_browser
)
if __name__ == "__main__":
main()
|