HKUDS/DeepTutor · error · ValueError

cron expressions need the 'croniter' package — use an 'every

Error message

cron expressions need the 'croniter' package — use an 'every' or 'at' schedule instead

What it means

compute_next_run catches ImportError from importing croniter and re-raises as ValueError, telling you cron expressions are unsupported without the optional dependency. Only 'cron' kind schedules need croniter; 'every' and 'at' schedules use builtin logic.

Source

Thrown at deeptutor/services/cron/service.py:141

        return None

    if schedule.kind == "every":
        if not schedule.every_seconds or schedule.every_seconds <= 0:
            return None
        return now_ms + schedule.every_seconds * 1000

    if schedule.kind == "cron" and schedule.expr:
        try:
            from zoneinfo import ZoneInfo

            from croniter import croniter

            tz = ZoneInfo(schedule.tz) if schedule.tz else datetime.now().astimezone().tzinfo
            base = datetime.fromtimestamp(now_ms / 1000, tz=tz)
            next_dt = croniter(schedule.expr, base).get_next(datetime)
            return int(next_dt.timestamp() * 1000)
        except ImportError:
            raise ValueError(
                "cron expressions need the 'croniter' package — "
                "use an 'every' or 'at' schedule instead"
            ) from None
        except Exception as exc:
            raise ValueError(f"invalid cron expression {schedule.expr!r}: {exc}") from None

    return None


def validate_schedule(schedule: CronSchedule) -> None:
    """Reject schedules that could never run (raises ValueError)."""
    if schedule.kind == "at":
        if not schedule.at_ms:
            raise ValueError("'at' schedules need a time")
        if schedule.at_ms <= _now_ms():
            raise ValueError("'at' time is in the past")
        return
    if schedule.kind == "every":

View on GitHub (pinned to 3e82f13042)

Solutions

  1. pip install croniter (or add it to project deps / extras group)
  2. Switch the schedule to kind='every' with every_seconds or kind='at' with at_ms, which need no extra dependency

Example fix

# before
schedule = CronSchedule(kind="cron", expr="0 9 * * *")
service.add_job(schedule=schedule, ...)  # ValueError: needs croniter
# after
pip install croniter
# or without croniter:
schedule = CronSchedule(kind="every", every_seconds=86400)
Defensive patterns

Strategy: validation

Validate before calling

def croniter_available() -> bool:
    try:
        import croniter  # noqa: F401
        return True
    except ImportError:
        return False

if schedule.kind == "cron" and not croniter_available():
    schedule = CronSchedule(kind="every", every_seconds=3600)

Try / catch

try:
    svc.add_job(schedule, ...)
except ValueError as e:
    if "croniter" in str(e):
        schedule = CronSchedule(kind="every", every_seconds=fallback_seconds)
        svc.add_job(schedule, ...)
    else:
        raise

Prevention

When it happens

Trigger: Creating CronSchedule(kind="cron", expr="*/5 * * * *") and calling add_job/validate_schedule/compute_next_run where croniter is not installed.

Common situations: Slim Docker images or serverless envs omitting the optional extra; upgrading the package without reinstalling extras; CI/prod dependency drift.

Related errors


AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27). Data as JSON: /api/errors/7093b8db32ded81c. Report an issue: GitHub.