Files
local-deep-research/scripts/dev/kill_servers.py
LearningCircuit 0c6635ecc2 feat: Add pre-commit hook to enforce pathlib usage (issue #640) (#656)
* feat: Add pre-commit hook to enforce pathlib usage (issue #640)

- Created check-pathlib-usage.py pre-commit hook using AST parsing
- Detects os.path usage and suggests pathlib alternatives
- Fixed os.path.normpath usage in auth/routes.py to use PurePosixPath
- Added hook configuration to .pre-commit-config.yaml

The hook provides helpful suggestions for replacing os.path calls with
their pathlib equivalents for better cross-platform compatibility.

Co-Authored-By: djpetti <djpetti@users.noreply.github.com>

* feat: Add missing pathlib pre-commit hook script

Co-Authored-By: djpetti <djpetti@users.noreply.github.com>

* refactor: Migrate core src modules from os.path to pathlib

- Fixed web/app_factory.py, config/llm_config.py, metrics/token_counter.py
- Fixed utilities/es_utils.py, web/routes/benchmark_routes.py
- Fixed web/routes/settings_routes.py, web_search_engines/engines/search_engine_local.py
- Replaced os.path.join() with Path() / syntax
- Replaced os.path.exists() with Path().exists()
- Replaced os.path.basename() with Path().name
- Replaced os.path.dirname() with Path().parent

Part of the migration to modern pathlib API for better cross-platform
compatibility and cleaner code.

Co-Authored-By: djpetti <djpetti@users.noreply.github.com>

* refactor: Migrate from os.path to pathlib in src and tests (issue #640)

Replaced os.path usage with pathlib.Path throughout:
- src/local_deep_research/benchmarks: All os.path.join, exists, dirname, basename, abspath replaced
- tests directory: Complete migration of all test files
- Improved cross-platform compatibility and code readability
- Kept os.path.expandvars in env_settings.py (no pathlib equivalent)

Part of pre-commit hook enforcement for pathlib usage.
Remaining work: examples/ and scripts/ directories.

Co-Authored-By: djpetti

* fix: Complete migration from os.path to pathlib.Path (issue #640)

Completed manual migration of all os.path usage to pathlib.Path across:
- scripts/ directory (3 files)
- examples/ directory (25 files total)
  - examples/benchmarks/ (8 files)
  - examples/optimization/ (16 files)
  - examples/show_env_vars.py
- src/local_deep_research/settings/env_settings.py

Changes made:
- Replaced os.path.join() with Path() / syntax
- Replaced os.path.exists() with Path().exists()
- Replaced os.path.dirname() with Path().parent
- Replaced os.path.basename() with Path().name or Path().stem
- Replaced os.path.abspath() with Path().resolve()
- Replaced os.makedirs() with Path().mkdir(parents=True, exist_ok=True)
- Added pathlib import where needed

Note: Kept os.path.expandvars in env_settings.py as there is no pathlib
equivalent. Added comment explaining this limitation.

This completes the pathlib migration for issue #640.

Co-Authored-By: djpetti

* fix: Allow os.path.expandvars in pathlib pre-commit hook

Updated the check-pathlib-usage.py pre-commit hook to skip checking
os.path.expandvars since it has no pathlib equivalent.

Changes:
- Added exception for expandvars in both visit_Attribute and visit_Call methods
- Added comment in equivalents dictionary noting expandvars is allowed
- This allows env_settings.py to use os.path.expandvars without failing checks

This resolves the pre-commit CI failure while maintaining the pathlib
enforcement for all other os.path methods.

Co-Authored-By: djpetti

---------

Co-authored-by: djpetti
2025-08-17 22:52:35 +02:00

343 lines
11 KiB
Python

import os
import signal
import subprocess
import sys
import time
from pathlib import Path
import psutil
def kill_flask_servers():
"""
Kill all Python processes running main.py (Flask servers)
Uses psutil to find and kill processes more reliably than just using command line
"""
killed_pids = []
for proc in psutil.process_iter(["pid", "name", "cmdline"]):
try:
cmdline = proc.info["cmdline"]
if (
cmdline
and "python" in proc.info["name"].lower()
and any(
"local_deep_research.web.app" in arg
for arg in cmdline
if arg
)
):
pid = proc.info["pid"]
print(f"Found Flask server process {pid}, killing...")
# Windows requires SIGTERM
os.kill(pid, signal.SIGTERM)
killed_pids.append(pid)
except (
psutil.NoSuchProcess,
psutil.AccessDenied,
psutil.ZombieProcess,
):
pass
if killed_pids:
print(
f"Killed Flask server processes with PIDs: {', '.join(map(str, killed_pids))}"
)
else:
print("No Flask server processes found.")
# Give processes time to die
time.sleep(1)
# Verify all are dead
for pid in killed_pids:
if psutil.pid_exists(pid):
print(f"Warning: Process {pid} still exists after kill signal.")
else:
print(f"Confirmed process {pid} was terminated.")
return killed_pids
def check_flask_servers():
"""Check if any Flask servers are running and return their PIDs."""
running_servers = []
for proc in psutil.process_iter(["pid", "name", "cmdline"]):
try:
cmdline = proc.info["cmdline"]
if (
cmdline
and "python" in proc.info["name"].lower()
and any(
"local_deep_research.web.app" in arg
for arg in cmdline
if arg
)
):
pid = proc.info["pid"]
running_servers.append(pid)
except (
psutil.NoSuchProcess,
psutil.AccessDenied,
psutil.ZombieProcess,
):
pass
return running_servers
def start_flask_server(port=5000):
"""Start a Flask server in the background."""
print(f"Starting Flask server on port {port}...")
# Get the virtual environment Python executable
# Try multiple common venv locations
venv_paths = [
Path("venv_dev") / "bin" / "python", # Linux/Mac
Path("venv") / "bin" / "python", # Linux/Mac
Path(".venv") / "Scripts" / "python.exe", # Windows
Path("venv_dev") / "Scripts" / "python.exe", # Windows
Path("venv") / "Scripts" / "python.exe", # Windows
]
venv_path = None
for path in venv_paths:
if path.exists():
venv_path = str(path)
break
if not venv_path:
print(
"Error: Could not find Python executable in any common venv location"
)
return None
try:
# First, check if port is already in use
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
result = sock.connect_ex(("127.0.0.1", port))
sock.close()
if result == 0:
print(f"Error: Port {port} is already in use")
return None
# Start the Flask server directly without background flags
# to better catch any startup errors
process = subprocess.Popen(
[
venv_path,
"-m",
"local_deep_research.web.app",
"--port",
str(port),
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True, # Use text mode for easier output reading
)
# Wait a moment to ensure the server has time to start
time.sleep(3)
# Check if the process is still running
if process.poll() is None:
print(f"Flask server started on port {port} with PID {process.pid}")
# Try to read initial output to see if there are errors
stdout_data, stderr_data = "", ""
try:
# Read with timeout to avoid blocking
import select
if os.name == "nt": # Windows
# Windows doesn't support select on pipes, use non-blocking mode
import msvcrt
if (
process.stdout
and msvcrt.get_osfhandle(process.stdout.fileno()) != -1
):
stdout_data = process.stdout.read(1024) or ""
if (
process.stderr
and msvcrt.get_osfhandle(process.stderr.fileno()) != -1
):
stderr_data = process.stderr.read(1024) or ""
else: # Unix
if (
process.stdout
and select.select([process.stdout], [], [], 0.5)[0]
):
stdout_data = process.stdout.read(1024) or ""
if (
process.stderr
and select.select([process.stderr], [], [], 0.5)[0]
):
stderr_data = process.stderr.read(1024) or ""
except Exception as e:
print(f"Warning: Could not read process output: {e}")
# Log any output for debugging
if stdout_data:
print(f"Server stdout: {stdout_data.strip()}")
if stderr_data:
print(f"Server stderr: {stderr_data.strip()}")
# Test if the server is actually responding
try:
import urllib.request
with urllib.request.urlopen(
f"http://localhost:{port}/", timeout=2
) as response:
if response.status == 200:
print(
f"Server is responsive at http://localhost:{port}/"
)
else:
print(
f"Warning: Server responded with status {response.status}"
)
except Exception as e:
print(f"Warning: Could not connect to server: {e}")
print(
"The process is running but the server may not be responding to requests"
)
return process.pid
else:
stdout, stderr = process.communicate()
print(
f"Error starting Flask server. Server process exited with code {process.returncode}"
)
if stdout:
print(f"Server stdout: {stdout.strip()}")
if stderr:
print(f"Server stderr: {stderr.strip()}")
return None
except Exception as e:
print(f"Error starting Flask server: {e!s}")
return None
def start_flask_server_windows(port=5000):
"""Start a Flask server using Windows 'start' command which is more reliable for Windows environments."""
print(
f"Starting Flask server on port {port} using Windows 'start' command..."
)
# Get the virtual environment Python executable
# Try multiple common venv locations
venv_paths = [
Path("venv_dev") / "Scripts" / "python.exe", # Windows
Path("venv") / "Scripts" / "python.exe", # Windows
Path(".venv") / "Scripts" / "python.exe", # Windows
]
venv_path = None
for path in venv_paths:
if path.exists():
venv_path = str(path)
break
if not venv_path:
print(
"Error: Could not find Python executable in any common venv location"
)
return None
try:
# Use Windows 'start' command to launch in a new window
# This is more reliable on Windows for Flask apps
cmd = f'start "Flask Server" /MIN "{venv_path}" -m local_deep_research.web.app --port {port}'
subprocess.run(cmd, shell=True, check=True)
print(f"Flask server starting on port {port}")
print(
"Note: The process is running in a minimized window. Close that window to stop the server."
)
# Wait a moment to ensure the server has time to start
time.sleep(3)
# We can't get the PID easily with this method, but return True to indicate success
return True
except Exception as e:
print(f"Error starting Flask server: {e!s}")
return None
def restart_server(port=5000):
"""Kill all Flask servers and start a new one."""
kill_flask_servers()
print("All Flask server processes terminated.")
pid = start_flask_server(port)
if pid:
print(f"New Flask server started on port {port} with PID {pid}")
return True
else:
print("Failed to start new Flask server")
return False
def show_status():
"""Show status of running Flask servers."""
running_servers = check_flask_servers()
if running_servers:
print(f"Found {len(running_servers)} running Flask server(s):")
for pid in running_servers:
try:
proc = psutil.Process(pid)
cmdline = proc.cmdline()
port = None
# Try to extract the port number
for i, arg in enumerate(cmdline):
if arg == "--port" and i + 1 < len(cmdline):
port = cmdline[i + 1]
if port:
print(f" - PID {pid}: Running on port {port}")
else:
print(f" - PID {pid}: Running (port unknown)")
except (psutil.NoSuchProcess, psutil.AccessDenied):
print(f" - PID {pid}: [Process information unavailable]")
else:
print("No Flask servers currently running.")
return len(running_servers) > 0
if __name__ == "__main__":
if len(sys.argv) > 1:
if sys.argv[1] == "restart":
kill_flask_servers()
print("All Flask server processes terminated.")
if os.name == "nt": # Windows
start_flask_server_windows()
else:
start_flask_server()
elif sys.argv[1] == "start":
if os.name == "nt": # Windows
start_flask_server_windows()
else:
start_flask_server()
elif sys.argv[1] == "status":
show_status()
else:
print(
"Unknown command. Usage: python kill_servers.py [restart|start|status]"
)
else:
kill_flask_servers()