def fetch_job_one(self, queues: Optional[Iterable[str]]) -> Dict: # Creating a copy of the iterable so that we can modify it while we iterate for job in self.jobs.values(): if ( job["status"] == "todo" and (queues is None or job["queue_name"] in queues) and (not job["scheduled_at"] or job["scheduled_at"] <= utils.utcnow()) and job["lock"] not in self.current_locks ): job["status"] = "doing" self.events[job["id"]].append({"type": "started", "at": utils.utcnow()}) return job return {"id": None}
def defer_job_one( self, task_name, lock, queueing_lock, args, scheduled_at, queue ) -> JobRow: if any( job for job in self.jobs.values() if job["queueing_lock"] == queueing_lock ): raise exceptions.UniqueViolation( constraint_name=connector.QUEUEING_LOCK_CONSTRAINT ) id = next(self.job_counter) self.jobs[id] = job_row = { "id": id, "queue_name": queue, "task_name": task_name, "lock": lock, "queueing_lock": queueing_lock, "args": args, "status": "todo", "scheduled_at": scheduled_at, "attempts": 0, } self.events[id] = [] if scheduled_at: self.events[id].append({"type": "scheduled", "at": scheduled_at}) self.events[id].append({"type": "deferred", "at": utils.utcnow()}) if self.notify_event: if "procrastinate_any_queue" in self.notify_channels or ( f"procrastinate_queue#{queue}" in self.notify_channels ): self.notify_event.set() return job_row
def configure_task( *, name: str, job_store: store.JobStore, lock: Optional[str] = None, queueing_lock: Optional[str] = None, task_kwargs: Optional[types.JSONDict] = None, schedule_at: Optional[datetime.datetime] = None, schedule_in: Optional[Dict[str, int]] = None, queue: str = jobs.DEFAULT_QUEUE, ) -> jobs.JobDeferrer: if schedule_at and schedule_in is not None: raise ValueError("Cannot set both schedule_at and schedule_in") if schedule_in is not None: schedule_at = utils.utcnow() + datetime.timedelta(**schedule_in) task_kwargs = task_kwargs or {} return jobs.JobDeferrer( job=jobs.Job( id=None, lock=lock, queueing_lock=queueing_lock, task_name=name, queue=queue, task_kwargs=task_kwargs, scheduled_at=schedule_at, ), job_store=job_store, )
def delete_old_jobs_run(self, nb_hours, queue, statuses): for id, job in list(self.jobs.items()): if (job["status"] in statuses and (max(e["at"] for e in self.events[id]) < utils.utcnow() - datetime.timedelta(hours=nb_hours)) and queue in (job["queue_name"], None)): self.jobs.pop(id)
def test_get_retry_exception_returns(): strategy = retry_module.RetryStrategy(max_attempts=10, wait=5.0) now = utils.utcnow() expected = now + datetime.timedelta(seconds=5, microseconds=0) exc = strategy.get_retry_exception(exception=None, attempts=1) assert isinstance(exc, exceptions.JobRetry) assert exc.scheduled_at == expected.replace(microsecond=0)
def get_retry_exception(self, exception: Exception, attempts: int) -> Optional[exceptions.JobRetry]: schedule_in = self.get_schedule_in(exception=exception, attempts=attempts) if schedule_in is None: return None schedule_at = utils.utcnow() + datetime.timedelta(seconds=schedule_in) return exceptions.JobRetry(schedule_at.replace(microsecond=0))
def retry_job_run(self, job_id: int, retry_at: datetime.datetime) -> None: job_row = self.jobs[job_id] job_row["status"] = "todo" job_row["attempts"] += 1 job_row["scheduled_at"] = retry_at self.events[job_id].append({"type": "scheduled", "at": retry_at}) self.events[job_id].append({ "type": "deferred_for_retry", "at": utils.utcnow() })
def finish_job_run(self, job_id: int, status: str, delete_job: bool) -> None: if delete_job: self.jobs.pop(job_id) return job_row = self.jobs[job_id] job_row["status"] = status job_row["attempts"] += 1 self.events[job_id].append({"type": status, "at": utils.utcnow()})
def select_stalled_jobs_all(self, nb_seconds, queue, task_name): return ( job for job in self.jobs.values() if job["status"] == "doing" and self.events[job["id"]][-1]["at"] < utils.utcnow() - datetime.timedelta(seconds=nb_seconds) and queue in (job["queue_name"], None) and task_name in (job["task_name"], None) )
def test_delete_old_jobs_run(connector): connector.jobs = { # We're not deleting this job because it's "doing" 1: { "id": 1, "status": "doing", "queue_name": "marsupilami" }, # This one because it's the wrong queue 2: { "id": 2, "status": "succeeded", "queue_name": "other_queue" }, # This one is not old enough 3: { "id": 3, "status": "succeeded", "queue_name": "marsupilami" }, # This one we delete 4: { "id": 4, "status": "succeeded", "queue_name": "marsupilami" }, } connector.events = { 1: [{ "type": "succeeded", "at": conftest.aware_datetime(2000, 1, 1) }], 2: [{ "type": "succeeded", "at": conftest.aware_datetime(2000, 1, 1) }], 3: [{ "type": "succeeded", "at": utils.utcnow() }], 4: [{ "type": "succeeded", "at": conftest.aware_datetime(2000, 1, 1) }], } connector.delete_old_jobs_run(queue="marsupilami", statuses=("succeeded"), nb_hours=0) assert 4 not in connector.jobs
def do_retry(self, arg: str) -> None: """ Retry a specific job (reset its status to todo). Usage: retry JOB_ID JOB_ID is the id (numeric) of the job. Example: retry 2 """ job_id = int(arg) self.job_manager.retry_job_by_id( # type: ignore job_id=job_id, retry_at=utils.utcnow().replace(microsecond=0)) (job, ) = self.job_manager.list_jobs(id=job_id) # type: ignore print_job(job)
def finish_job_run( self, job_id: int, status: str, scheduled_at: Optional[datetime.datetime] = None ) -> None: job_row = self.jobs[job_id] job_row["status"] = status event_type = status if status == "todo": job_row["attempts"] += 1 job_row["scheduled_at"] = scheduled_at if scheduled_at: self.events[job_id].append({"type": "scheduled", "at": scheduled_at}) event_type = "deferred_for_retry" self.events[job_id].append({"type": event_type, "at": utils.utcnow()})
async def retry_job( self, job: "jobs.Job", retry_at: Optional[datetime.datetime] = None, ) -> None: """ Indicates that a job should be retried later. Parameters ---------- job : `jobs.Job` retry_at : ``Optional[datetime.datetime]`` If set at present time or in the past, the job may be retried immediately. Otherwise, the job will be retried no sooner than this date & time. Should be timezone-aware (even if UTC). Defaults to present time. """ assert job.id # TODO remove this await self.retry_job_by_id_async(job_id=job.id, retry_at=retry_at or utils.utcnow())
def defer_job_one( self, task_name: str, lock: Optional[str], queueing_lock: Optional[str], args: types.JSONDict, scheduled_at: Optional[datetime.datetime], queue: str, ) -> JobRow: if queueing_lock is not None and any( job["queueing_lock"] == queueing_lock and job["status"] == "todo" for job in self.jobs.values()): from . import manager raise exceptions.UniqueViolation( constraint_name=manager.QUEUEING_LOCK_CONSTRAINT) id = next(self.job_counter) self.jobs[id] = job_row = { "id": id, "queue_name": queue, "task_name": task_name, "lock": lock, "queueing_lock": queueing_lock, "args": args, "status": "todo", "scheduled_at": scheduled_at, "attempts": 0, } self.events[id] = [] if scheduled_at: self.events[id].append({"type": "scheduled", "at": scheduled_at}) self.events[id].append({"type": "deferred", "at": utils.utcnow()}) if self.notify_event: if "procrastinate_any_queue" in self.notify_channels or ( f"procrastinate_queue#{queue}" in self.notify_channels): self.notify_event.set() return job_row
def test_utcnow(mocker): dt = mocker.patch("datetime.datetime") assert utils.utcnow() == dt.now.return_value dt.now.assert_called_once_with(tz=datetime.timezone.utc)