document_schedule_check_task.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. """
  2. Document Schedule Check Task
  3. Celery Beat task to check and execute scheduled document status changes
  4. """
  5. import logging
  6. import time
  7. import click
  8. import app
  9. from extensions.ext_database import db
  10. from models.dataset import Dataset, Document
  11. from services.dataset_operation_log_service import DatasetOperationLogService, DatasetOperationType
  12. from services.dataset_service import DocumentService
  13. from services.document_schedule_service import DocumentScheduleService, ScheduleAction
  14. logger = logging.getLogger(__name__)
  15. @app.celery.task(queue="dataset")
  16. def check_and_execute_document_schedules():
  17. """
  18. Check for pending scheduled document status changes and execute them
  19. This task should be run periodically (e.g., every 1-5 minutes) via Celery Beat
  20. """
  21. logger.info(click.style("Start checking scheduled document tasks", fg="green"))
  22. start_at = time.perf_counter()
  23. try:
  24. # Get all pending schedules that should be executed now
  25. pending_schedules = DocumentScheduleService.get_pending_schedules()
  26. if not pending_schedules:
  27. logger.info(click.style("No pending schedules found", fg="yellow"))
  28. return
  29. logger.info(click.style(f"Found {len(pending_schedules)} pending schedules", fg="green"))
  30. executed_count = 0
  31. failed_count = 0
  32. for schedule in pending_schedules:
  33. schedule_id = schedule.get("schedule_id")
  34. document_id = schedule.get("document_id")
  35. dataset_id = schedule.get("dataset_id")
  36. action = schedule.get("action")
  37. try:
  38. # Update status to executing
  39. DocumentScheduleService.update_schedule_status(schedule_id, "executing")
  40. # Execute the scheduled action
  41. success = _execute_schedule_action(schedule)
  42. if success:
  43. # Update status to completed
  44. DocumentScheduleService.update_schedule_status(schedule_id, "completed")
  45. executed_count += 1
  46. logger.info(
  47. click.style(
  48. f"Successfully executed schedule {schedule_id}: "
  49. f"document={document_id}, action={action}",
  50. fg="green",
  51. )
  52. )
  53. else:
  54. # Update status to failed
  55. DocumentScheduleService.update_schedule_status(
  56. schedule_id, "failed", error_message="Execution failed"
  57. )
  58. failed_count += 1
  59. logger.error(
  60. click.style(
  61. f"Failed to execute schedule {schedule_id}: "
  62. f"document={document_id}, action={action}",
  63. fg="red",
  64. )
  65. )
  66. except Exception as e:
  67. # Update status to failed with error message
  68. DocumentScheduleService.update_schedule_status(schedule_id, "failed", error_message=str(e))
  69. failed_count += 1
  70. logger.exception(
  71. f"Error executing schedule {schedule_id}: "
  72. f"document={document_id}, action={action}, error={str(e)}"
  73. )
  74. end_at = time.perf_counter()
  75. logger.info(
  76. click.style(
  77. f"Finished checking schedules: executed={executed_count}, "
  78. f"failed={failed_count}, latency={end_at - start_at:.2f}s",
  79. fg="green",
  80. )
  81. )
  82. except Exception as e:
  83. logger.exception(f"Error in check_and_execute_document_schedules: {str(e)}")
  84. finally:
  85. db.session.close()
  86. def _execute_schedule_action(schedule: dict) -> bool:
  87. """
  88. Execute a scheduled action
  89. Args:
  90. schedule: Schedule details dictionary
  91. Returns:
  92. bool: True if successful, False otherwise
  93. """
  94. document_id = schedule.get("document_id")
  95. dataset_id = schedule.get("dataset_id")
  96. action = schedule.get("action")
  97. created_by = schedule.get("created_by")
  98. tenant_id = schedule.get("tenant_id")
  99. try:
  100. # Get dataset
  101. dataset = db.session.query(Dataset).filter(Dataset.id == dataset_id).first()
  102. if not dataset:
  103. logger.error(f"Dataset not found: {dataset_id}")
  104. return False
  105. # Get document
  106. document = db.session.query(Document).filter(Document.id == document_id, Document.dataset_id == dataset_id).first()
  107. if not document:
  108. logger.error(f"Document not found: {document_id}")
  109. return False
  110. # Create a mock user object for the action
  111. class ScheduledUser:
  112. def __init__(self, user_id: str):
  113. self.id = user_id
  114. user = ScheduledUser(created_by)
  115. # Execute the action
  116. if action == ScheduleAction.ENABLE.value:
  117. DocumentService.batch_update_document_status(dataset, [document_id], "enable", user)
  118. operation_type = DatasetOperationType.DOCUMENT_ENABLE
  119. elif action == ScheduleAction.DISABLE.value:
  120. DocumentService.batch_update_document_status(dataset, [document_id], "disable", user)
  121. operation_type = DatasetOperationType.DOCUMENT_DISABLE
  122. elif action == ScheduleAction.ARCHIVE.value:
  123. DocumentService.batch_update_document_status(dataset, [document_id], "archive", user)
  124. operation_type = DatasetOperationType.DOCUMENT_ARCHIVE
  125. else:
  126. logger.error(f"Unknown action: {action}")
  127. return False
  128. # Log the operation
  129. try:
  130. DatasetOperationLogService.log_operation(
  131. tenant_id=tenant_id,
  132. account_id=created_by,
  133. action=DatasetOperationType.DOCUMENT_SCHEDULE_EXECUTE,
  134. dataset_id=dataset_id,
  135. resource_type="document",
  136. resource_id=document_id,
  137. changes={
  138. "action": action,
  139. "operation_type": operation_type.value,
  140. },
  141. metadata={
  142. "scheduled": True,
  143. "schedule_id": schedule.get("schedule_id"),
  144. "scheduled_at": schedule.get("scheduled_at"),
  145. },
  146. )
  147. except Exception as log_error:
  148. # Don't fail the entire operation if logging fails
  149. logger.error(f"Failed to log operation: {str(log_error)}")
  150. return True
  151. except Exception as e:
  152. logger.exception(f"Error executing schedule action: {str(e)}")
  153. db.session.rollback()
  154. return False