2025-10-13 00:47:08 +03:00

200 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Task Worker for sequential review processing"""
import asyncio
import logging
from datetime import datetime
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import AsyncSessionLocal
from app.models import ReviewTask, PullRequest, Repository, Review
from app.models.review_task import TaskStatusEnum
from app.agents.reviewer import ReviewerAgent
from app.config import settings
logger = logging.getLogger(__name__)
class ReviewTaskWorker:
"""Worker that processes review tasks sequentially"""
def __init__(self):
self.running = False
self.current_task_id = None
self.poll_interval = 10 # секунд между проверками
async def start(self):
"""Start the worker"""
self.running = True
logger.info("🚀 Task Worker запущен")
while self.running:
try:
await self._process_next_task()
except Exception as e:
logger.error(f"❌ Ошибка в Task Worker: {e}")
import traceback
traceback.print_exc()
# Подождать перед следующей проверкой
await asyncio.sleep(self.poll_interval)
async def stop(self):
"""Stop the worker"""
self.running = False
logger.info("⏹️ Task Worker остановлен")
async def _process_next_task(self):
"""Process next pending task"""
async with AsyncSessionLocal() as db:
# Проверяем есть ли уже выполняющаяся задача
in_progress_query = select(ReviewTask).where(
ReviewTask.status == TaskStatusEnum.IN_PROGRESS
)
result = await db.execute(in_progress_query)
in_progress = result.scalar_one_or_none()
if in_progress:
# Уже есть задача в работе, ждем
logger.debug(f"⏳ Задача #{in_progress.id} уже выполняется")
return
# Берем следующую pending задачу (с приоритетом)
pending_query = select(ReviewTask).where(
ReviewTask.status == TaskStatusEnum.PENDING
).order_by(
ReviewTask.priority.desc(), # HIGH > NORMAL > LOW
ReviewTask.created_at.asc() # Старые первыми
).limit(1)
result = await db.execute(pending_query)
task = result.scalar_one_or_none()
if not task:
# Нет задач в очереди
return
logger.info(f"\n{'='*80}")
logger.info(f"📋 Начало обработки задачи #{task.id}")
logger.info(f" PR ID: {task.pull_request_id}")
logger.info(f" Приоритет: {task.priority}")
logger.info(f"={'='*80}\n")
# Отмечаем задачу как in_progress
task.status = TaskStatusEnum.IN_PROGRESS
task.started_at = datetime.utcnow()
self.current_task_id = task.id
await db.commit()
try:
# Выполняем review
await self._execute_review(task, db)
# Успешно завершено
task.status = TaskStatusEnum.COMPLETED
task.completed_at = datetime.utcnow()
logger.info(f"✅ Задача #{task.id} успешно завершена")
except Exception as e:
# Ошибка при выполнении
task.retry_count += 1
task.error_message = str(e)
if task.retry_count >= task.max_retries:
# Превышено количество попыток
task.status = TaskStatusEnum.FAILED
task.completed_at = datetime.utcnow()
logger.error(f"❌ Задача #{task.id} провалена после {task.retry_count} попыток: {e}")
else:
# Вернуть в pending для повторной попытки
task.status = TaskStatusEnum.PENDING
logger.warning(f"⚠️ Задача #{task.id} вернулась в очередь (попытка {task.retry_count}/{task.max_retries}): {e}")
import traceback
traceback.print_exc()
finally:
self.current_task_id = None
await db.commit()
async def _execute_review(self, task: ReviewTask, db: AsyncSession):
"""Execute review for the task"""
# Get PR with repository
result = await db.execute(
select(PullRequest).where(PullRequest.id == task.pull_request_id)
)
pull_request = result.scalar_one_or_none()
if not pull_request:
raise ValueError(f"PullRequest {task.pull_request_id} not found")
# Get repository
result = await db.execute(
select(Repository).where(Repository.id == pull_request.repository_id)
)
repository = result.scalar_one_or_none()
if not repository:
raise ValueError(f"Repository {pull_request.repository_id} not found")
# Check if review already exists and is not failed
existing_review = await db.execute(
select(Review).where(
Review.pull_request_id == pull_request.id
).order_by(Review.started_at.desc())
)
review = existing_review.scalar_one_or_none()
if review and review.status not in ["failed", "pending"]:
logger.info(f" Review already exists with status: {review.status}")
return
# Create new review if doesn't exist
if not review:
review = Review(
pull_request_id=pull_request.id,
status="pending"
)
db.add(review)
await db.commit()
await db.refresh(review)
# Run review agent
logger.info(f" 🤖 Запуск AI review для PR #{pull_request.pr_number}")
agent = ReviewerAgent(db)
await agent.review_pull_request(
repository_id=repository.id,
pr_number=pull_request.pr_number,
review_id=review.id
)
logger.info(f" ✅ Review завершен для PR #{pull_request.pr_number}")
# Global worker instance
_worker_instance: ReviewTaskWorker | None = None
async def start_worker():
"""Start the global worker instance"""
global _worker_instance
if _worker_instance is None:
_worker_instance = ReviewTaskWorker()
# Запускаем в фоне
asyncio.create_task(_worker_instance.start())
async def stop_worker():
"""Stop the global worker instance"""
global _worker_instance
if _worker_instance:
await _worker_instance.stop()
_worker_instance = None
def get_worker() -> ReviewTaskWorker | None:
"""Get the current worker instance"""
return _worker_instance