{"record":{"id":"7093b8db32ded81c","repo":"HKUDS/DeepTutor","slug":"cron-expressions-need-the-croniter-package-use","errorCode":null,"errorMessage":"cron expressions need the 'croniter' package — use an 'every' or 'at' schedule instead","messagePattern":"cron expressions need the 'croniter' package — use an 'every' or 'at' schedule instead","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"deeptutor/services/cron/service.py","lineNumber":141,"sourceCode":"        return None\n\n    if schedule.kind == \"every\":\n        if not schedule.every_seconds or schedule.every_seconds <= 0:\n            return None\n        return now_ms + schedule.every_seconds * 1000\n\n    if schedule.kind == \"cron\" and schedule.expr:\n        try:\n            from zoneinfo import ZoneInfo\n\n            from croniter import croniter\n\n            tz = ZoneInfo(schedule.tz) if schedule.tz else datetime.now().astimezone().tzinfo\n            base = datetime.fromtimestamp(now_ms / 1000, tz=tz)\n            next_dt = croniter(schedule.expr, base).get_next(datetime)\n            return int(next_dt.timestamp() * 1000)\n        except ImportError:\n            raise ValueError(\n                \"cron expressions need the 'croniter' package — \"\n                \"use an 'every' or 'at' schedule instead\"\n            ) from None\n        except Exception as exc:\n            raise ValueError(f\"invalid cron expression {schedule.expr!r}: {exc}\") from None\n\n    return None\n\n\ndef validate_schedule(schedule: CronSchedule) -> None:\n    \"\"\"Reject schedules that could never run (raises ValueError).\"\"\"\n    if schedule.kind == \"at\":\n        if not schedule.at_ms:\n            raise ValueError(\"'at' schedules need a time\")\n        if schedule.at_ms <= _now_ms():\n            raise ValueError(\"'at' time is in the past\")\n        return\n    if schedule.kind == \"every\":","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/services/cron/service.py#L123-L159","documentation":"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.","triggerScenarios":"Creating CronSchedule(kind=\"cron\", expr=\"*/5 * * * *\") and calling add_job/validate_schedule/compute_next_run where croniter is not installed.","commonSituations":"Slim Docker images or serverless envs omitting the optional extra; upgrading the package without reinstalling extras; CI/prod dependency drift.","solutions":["pip install croniter (or add it to project deps / extras group)","Switch the schedule to kind='every' with every_seconds or kind='at' with at_ms, which need no extra dependency"],"exampleFix":"# before\nschedule = CronSchedule(kind=\"cron\", expr=\"0 9 * * *\")\nservice.add_job(schedule=schedule, ...)  # ValueError: needs croniter\n# after\npip install croniter\n# or without croniter:\nschedule = CronSchedule(kind=\"every\", every_seconds=86400)","handlingStrategy":"validation","validationCode":"def croniter_available() -> bool:\n    try:\n        import croniter  # noqa: F401\n        return True\n    except ImportError:\n        return False\n\nif schedule.kind == \"cron\" and not croniter_available():\n    schedule = CronSchedule(kind=\"every\", every_seconds=3600)","typeGuard":null,"tryCatchPattern":"try:\n    svc.add_job(schedule, ...)\nexcept ValueError as e:\n    if \"croniter\" in str(e):\n        schedule = CronSchedule(kind=\"every\", every_seconds=fallback_seconds)\n        svc.add_job(schedule, ...)\n    else:\n        raise","preventionTips":["Add croniter to deployment dependency checks/startup doctor","Advertise capability: only expose cron expressions in the UI when croniter is importable"],"tags":["cron","optional-dependency","scheduling"],"backgroundTag":"missing-optional-dependency","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}