pdf-tei-editor / fastapi_app /lib /utils /server_startup.py
cmboulanger's picture
disaster-recovery deploy of pdf-tei-editor
6a49f21 verified
Raw
History Blame Contribute Delete
6 kB
"""
Shared utilities for starting the FastAPI server in development and production modes.
This module provides common functionality for server startup scripts to avoid code duplication.
"""
import os
import socket
import subprocess
import sys
from pathlib import Path
from typing import Optional, Tuple
def load_environment(project_root: Path) -> None:
"""
Load environment variables from .env file if it exists.
Args:
project_root: Root directory of the project
"""
env_file = project_root / '.env'
if env_file.exists():
from dotenv import load_dotenv
load_dotenv(env_file)
def check_port_in_use(port: int = 8000) -> bool:
"""
Check if the specified port is already in use.
Args:
port: Port number to check
Returns:
True if port is in use, False otherwise
"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex(('localhost', port)) == 0
def get_pid_on_port(port: int = 8000) -> Optional[int]:
"""
Get the PID of the process using the specified port.
Args:
port: Port number to check
Returns:
PID if found, None otherwise
"""
try:
if sys.platform == 'win32':
# Windows: use netstat
result = subprocess.run(
['netstat', '-ano'],
capture_output=True,
text=True,
check=False
)
for line in result.stdout.split('\n'):
if f':{port}' in line and 'LISTENING' in line:
parts = line.split()
if parts:
return int(parts[-1])
else:
# Unix/Linux/macOS: use lsof
result = subprocess.run(
['lsof', '-ti', f':{port}'],
capture_output=True,
text=True,
check=False
)
if result.stdout.strip():
return int(result.stdout.strip().split('\n')[0])
except (subprocess.SubprocessError, ValueError):
pass
return None
def kill_server_on_port(port: int = 8000) -> bool:
"""
Kill the server process running on the specified port.
Args:
port: Port number to check
Returns:
True if a process was killed, False otherwise
"""
pid = get_pid_on_port(port)
if pid:
try:
print(f"Killing server process on port {port} (PID: {pid})...")
if sys.platform == 'win32':
# Windows: use taskkill
subprocess.run(['taskkill', '/F', '/PID', str(pid)], check=True)
else:
# Unix/Linux/macOS: use kill
subprocess.run(['kill', str(pid)], check=True)
# Wait a moment for the process to terminate
import time
time.sleep(1)
return True
except subprocess.SubprocessError as e:
print(f"Error killing process: {e}", file=sys.stderr)
return False
return False
def get_host_and_port() -> Tuple[str, int]:
"""
Determine host and port from environment variables or CLI arguments.
Priority: env vars > CLI arguments > defaults
Returns:
Tuple of (host, port)
"""
host = os.environ.get('HOST') or (sys.argv[1] if len(sys.argv) > 1 else "localhost")
port = int(os.environ.get('PORT') or (sys.argv[2] if len(sys.argv) > 2 else 8000))
return host, port
def setup_log_directory(project_root: Path) -> Path:
"""
Create log directory and return path to server log file.
This returns the path for uvicorn/server logs (access logs, startup messages).
Application logs are written separately by the Python logging FileHandler.
The log directory can be configured via the LOG_DIR environment variable.
Args:
project_root: Root directory of the project
Returns:
Path to the server log file
"""
log_dir_env = os.environ.get("LOG_DIR")
if log_dir_env:
log_dir = Path(log_dir_env)
else:
log_dir = project_root / "log"
log_dir.mkdir(parents=True, exist_ok=True)
return log_dir / "server.log"
def print_server_running_message(host: str, port: int, pid: Optional[int] = None) -> None:
"""
Print message when server is already running.
Args:
host: Server host
port: Server port
pid: Process ID if available
"""
print()
print("=" * 86)
print(f"FastAPI server is already running on port {port}" + (f" (PID: {pid})" if pid else ""))
print(f"The server automatically reloads on file changes.")
print(f"To restart, kill the process and run this command again.")
if pid:
print(f" kill {pid}")
print(f"Open http://{host}:{port}?dev to access app in development mode.")
print(f"API docs available at: http://{host}:{port}/docs")
print("=" * 86)
print()
def print_server_starting_message(host: str, port: int, log_file: Path, mode: str = "development", auto_reload: bool = True) -> None:
"""
Print message when starting the server.
Args:
host: Server host
port: Server port
log_file: Path to log file
mode: Server mode ("development" or "production")
auto_reload: Whether auto-reload is enabled
"""
print()
print("=" * 86)
print(f"Starting FastAPI {mode} server.")
print(f"Server output being logged to: {log_file}")
if mode == "development":
if auto_reload:
print(f"The server will automatically reload when files change.")
else:
print(f"Auto-reload is disabled. Use 'bin/start-dev --restart' to restart after changes.")
print(f"Open http://{host}:{port}?dev to access app in development mode.")
else:
print(f"Open http://{host}:{port} to access the application.")
print(f"API docs available at: http://{host}:{port}/docs")
print("=" * 86)
print()