docs: Update README and add run script for Redis and RQ worker integration

This commit enhances the README with detailed instructions for installing and starting Redis, including commands for various operating systems. It clarifies the automatic startup of the RQ worker with the FastAPI server and updates the project status for Phase 2 features. Additionally, a new script `run_api_with_worker.sh` is introduced to streamline the process of starting the FastAPI server alongside the RQ worker, ensuring a smoother setup for users. The worker now has a unique name to prevent conflicts during execution.
This commit is contained in:
tanyar09
2025-10-31 13:01:58 -04:00
parent 4c2148f7fc
commit 2f039a1d48
5 changed files with 237 additions and 35 deletions
+83 -1
View File
@@ -1,5 +1,11 @@
from __future__ import annotations
import os
import subprocess
import sys
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
@@ -14,10 +20,86 @@ from src.web.api.tags import router as tags_router
from src.web.api.version import router as version_router
from src.web.settings import APP_TITLE, APP_VERSION
# Global worker process (will be set in lifespan)
_worker_process: subprocess.Popen | None = None
def start_worker() -> None:
"""Start RQ worker in background subprocess."""
global _worker_process
try:
from redis import Redis
# Check Redis connection first
redis_conn = Redis(host="localhost", port=6379, db=0, decode_responses=False)
redis_conn.ping()
# Start worker as a subprocess (avoids signal handler issues)
project_root = Path(__file__).parent.parent.parent
python_executable = sys.executable
_worker_process = subprocess.Popen(
[
python_executable,
"-m",
"src.web.worker",
],
cwd=str(project_root),
stdout=None, # Don't capture - let output go to console
stderr=None, # Don't capture - let errors go to console
env={
**{k: v for k, v in os.environ.items()},
"PYTHONPATH": str(project_root),
}
)
# Give it a moment to start, then check if it's still running
import time
time.sleep(0.5)
if _worker_process.poll() is not None:
# Process already exited - there was an error
print(f"❌ Worker process exited immediately with code {_worker_process.returncode}")
print(" Check worker errors above")
else:
print(f"✅ RQ worker started in background subprocess (PID: {_worker_process.pid})")
except Exception as e:
print(f"⚠️ Failed to start RQ worker: {e}")
print(" Background jobs will not be processed. Ensure Redis is running.")
def stop_worker() -> None:
"""Stop RQ worker gracefully."""
global _worker_process
if _worker_process:
try:
_worker_process.terminate()
try:
_worker_process.wait(timeout=5)
except subprocess.TimeoutExpired:
_worker_process.kill()
print("✅ RQ worker stopped")
except Exception:
pass
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Lifespan context manager for startup and shutdown events."""
# Startup
start_worker()
yield
# Shutdown
stop_worker()
def create_app() -> FastAPI:
"""Create and configure the FastAPI application instance."""
app = FastAPI(title=APP_TITLE, version=APP_VERSION)
app = FastAPI(
title=APP_TITLE,
version=APP_VERSION,
lifespan=lifespan,
)
app.add_middleware(
CORSMiddleware,
+6 -2
View File
@@ -6,8 +6,9 @@ import signal
import sys
from typing import NoReturn
import uuid
from rq import Worker
from rq.connections import use_connection
from redis import Redis
from src.web.services.tasks import import_photos_task
@@ -24,12 +25,15 @@ def main() -> NoReturn:
signal.signal(signal.SIGTERM, _handle_sigterm)
signal.signal(signal.SIGINT, _handle_sigterm)
# Generate unique worker name to avoid conflicts
worker_name = f"punimtag-worker-{uuid.uuid4().hex[:8]}"
# Register tasks with worker
# Tasks are imported from services.tasks
worker = Worker(
["default"],
connection=redis_conn,
name="punimtag-worker",
name=worker_name,
)
# Start worker