pending issues fixed

This commit is contained in:
Aditya Vaish 2025-10-15 15:17:58 +05:30
parent 7391a347ca
commit 62ffec9678
2 changed files with 103 additions and 35 deletions

View file

@ -9,6 +9,7 @@ PATCH /connector-schedules/{schedule_id}/toggle - Activate/deactivate a schedule
"""
import logging
from datetime import timezone
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.exc import IntegrityError
@ -87,15 +88,17 @@ async def create_connector_schedule(
detail=f"A schedule already exists for connector {schedule.connector_id} and search space {schedule.search_space_id}",
)
# Calculate next run time
next_run_at = calculate_next_run(
schedule.schedule_type,
schedule.cron_expression,
schedule.daily_time,
schedule.weekly_day,
schedule.weekly_time,
schedule.hourly_minute
)
# Calculate next run time only if schedule is active
next_run_at = None
if schedule.is_active:
next_run_at = calculate_next_run(
schedule.schedule_type,
schedule.cron_expression,
schedule.daily_time,
schedule.weekly_day,
schedule.weekly_time,
schedule.hourly_minute
)
# Create schedule (only DB columns)
db_data = schedule.model_dump(
@ -242,34 +245,78 @@ async def update_connector_schedule(
# Update fields that were provided
update_data = schedule_update.model_dump(exclude_unset=True)
# Effective types/values after this update (for validation and next-run calc)
effective_type = update_data.get("schedule_type", schedule.schedule_type)
effective_cron = update_data.get("cron_expression", schedule.cron_expression)
effective_daily = update_data.get("daily_time", schedule.daily_time)
effective_weekly_day = update_data.get("weekly_day", schedule.weekly_day)
effective_weekly_time = update_data.get("weekly_time", schedule.weekly_time)
effective_hourly_minute = update_data.get("hourly_minute", schedule.hourly_minute)
will_be_active = update_data.get("is_active", schedule.is_active)
# Disallow cron for non-CUSTOM schedules
if "cron_expression" in update_data and effective_type != ScheduleType.CUSTOM:
raise HTTPException(
status_code=400,
detail="cron_expression is only valid when schedule_type is CUSTOM",
)
# Check if any time-related fields changed (requiring next_run recalc)
time_fields_changed = any(
field in update_data
for field in ["daily_time", "weekly_day", "weekly_time", "hourly_minute"]
)
# If schedule_type is being updated, recalculate next_run_at
# If schedule_type is being updated or time fields changed, recalculate next_run_at
if "schedule_type" in update_data or time_fields_changed:
# Use the new schedule_type and existing values for calculation
new_schedule_type = update_data.get("schedule_type", schedule.schedule_type)
cron_expr = update_data.get("cron_expression", schedule.cron_expression)
daily_time = update_data.get("daily_time", schedule.daily_time)
weekly_day = update_data.get("weekly_day", schedule.weekly_day)
weekly_time = update_data.get("weekly_time", schedule.weekly_time)
hourly_minute = update_data.get("hourly_minute", schedule.hourly_minute)
update_data["next_run_at"] = calculate_next_run(
new_schedule_type, cron_expr, daily_time, weekly_day, weekly_time, hourly_minute
)
if will_be_active:
update_data["next_run_at"] = calculate_next_run(
effective_type,
effective_cron,
effective_daily,
effective_weekly_day,
effective_weekly_time,
effective_hourly_minute,
)
# Ensure timezone-aware UTC
if update_data["next_run_at"].tzinfo is None:
update_data["next_run_at"] = update_data["next_run_at"].replace(tzinfo=timezone.utc)
else:
update_data["next_run_at"] = None
elif "cron_expression" in update_data and schedule.schedule_type == ScheduleType.CUSTOM:
# If only cron_expression is updated for CUSTOM schedule, recalculate next_run_at
update_data["next_run_at"] = calculate_next_run(
schedule.schedule_type,
update_data["cron_expression"],
schedule.daily_time,
schedule.weekly_day,
schedule.weekly_time,
schedule.hourly_minute
)
if will_be_active:
update_data["next_run_at"] = calculate_next_run(
schedule.schedule_type,
update_data["cron_expression"],
schedule.daily_time,
schedule.weekly_day,
schedule.weekly_time,
schedule.hourly_minute,
)
# Ensure timezone-aware UTC
if update_data["next_run_at"].tzinfo is None:
update_data["next_run_at"] = update_data["next_run_at"].replace(tzinfo=timezone.utc)
else:
update_data["next_run_at"] = None
# Handle activation toggles
if "is_active" in update_data:
if update_data["is_active"] is False:
update_data["next_run_at"] = None
elif update_data["is_active"] is True and "next_run_at" not in update_data:
# Schedule is being activated and next_run_at wasn't already calculated above
next_run = calculate_next_run(
effective_type,
effective_cron,
effective_daily,
effective_weekly_day,
effective_weekly_time,
effective_hourly_minute,
)
if next_run.tzinfo is None:
next_run = next_run.replace(tzinfo=timezone.utc)
update_data["next_run_at"] = next_run
# Apply updates
for field, value in update_data.items():
@ -352,6 +399,23 @@ async def toggle_connector_schedule(
# Toggle the active status
schedule.is_active = not schedule.is_active
# Update next_run_at based on new active status
if schedule.is_active:
# Schedule is being activated, calculate next_run_at if it's None
if schedule.next_run_at is None:
schedule.next_run_at = calculate_next_run(
schedule.schedule_type,
schedule.cron_expression,
schedule.daily_time,
schedule.weekly_day,
schedule.weekly_time,
schedule.hourly_minute
)
else:
# Schedule is being deactivated, set next_run_at to None
schedule.next_run_at = None
await session.commit()
await session.refresh(schedule)

View file

@ -1,6 +1,6 @@
"""Helper utilities for calculating schedule next run times."""
from datetime import datetime, timedelta, time
from datetime import datetime, timedelta, time, timezone
from croniter import croniter
@ -27,18 +27,18 @@ def calculate_next_run(
hourly_minute: Optional minute for HOURLY schedules (0-59, default: 0)
Returns:
datetime: The next scheduled run time
datetime: The next scheduled run time (timezone-aware in UTC)
Raises:
ValueError: If schedule_type is CUSTOM but no cron_expression provided
"""
now = datetime.now()
now = datetime.now(timezone.utc)
if schedule_type == ScheduleType.HOURLY:
# Run at the specified minute of the next hour
minute = hourly_minute if hourly_minute is not None else 0
next_run = (now + timedelta(hours=1)).replace(minute=minute, second=0, microsecond=0)
return next_run
return next_run.replace(tzinfo=timezone.utc)
elif schedule_type == ScheduleType.DAILY:
# Run at the specified time next day
@ -46,7 +46,7 @@ def calculate_next_run(
next_run = now.replace(hour=target_time.hour, minute=target_time.minute, second=0, microsecond=0)
if next_run <= now:
next_run += timedelta(days=1)
return next_run
return next_run.replace(tzinfo=timezone.utc)
elif schedule_type == ScheduleType.WEEKLY:
# Run on the specified day at the specified time
@ -61,14 +61,18 @@ def calculate_next_run(
days_until_target = 7
next_run += timedelta(days=days_until_target)
return next_run
return next_run.replace(tzinfo=timezone.utc)
elif schedule_type == ScheduleType.CUSTOM:
if not cron_expression:
raise ValueError("cron_expression is required for CUSTOM schedule type")
try:
cron = croniter(cron_expression, now)
return cron.get_next(datetime)
next_run = cron.get_next(datetime)
# Ensure the returned datetime is timezone-aware in UTC
if next_run.tzinfo is None:
next_run = next_run.replace(tzinfo=timezone.utc)
return next_run
except Exception as e:
raise ValueError(f"Invalid cron expression: {cron_expression}") from e