agronholm / apscheduler

Task scheduling library for Python
MIT License
5.98k stars 694 forks source link

Job.started_at not populated #848

Closed peterschutt closed 2 months ago

peterschutt commented 6 months ago

Things to check first

Version

master

What happened?

Job.started_at is never set.

My use case is a structlog processor that adds info from Job into the log document that I generate.

As Job is frozen, simply setting the value in AsyncScheduler._run_job() won't work, perhaps a setter method on Job that puts the value directly into Job.__dict__ uses object.__setattr__()?

How can we reproduce the bug?

from __future__ import annotations

from asyncio import run

from apscheduler import AsyncScheduler, current_job
from apscheduler.triggers.interval import IntervalTrigger

def tick():
    job = current_job.get()
    print(f"Job started at: {job.started_at}")

async def main():
    async with AsyncScheduler() as scheduler:
        await scheduler.add_schedule(tick, IntervalTrigger(seconds=1))
        await scheduler.run_until_stopped()

run(main())

Example output:

Job started at: None
Job started at: None
Job started at: None
...

Possible solution

diff --git a/src/apscheduler/_schedulers/async_.py b/src/apscheduler/_schedulers/async_.py
index 5363dd3..2e33bf2 100644
--- a/src/apscheduler/_schedulers/async_.py
+++ b/src/apscheduler/_schedulers/async_.py
@@ -957,6 +957,7 @@ class AsyncScheduler:
             except KeyError:
                 return

+            job.set_started_at(start_time)
             token = current_job.set(job)
             try:
                 retval = await job_executor.run_job(func, job)
diff --git a/src/apscheduler/_structures.py b/src/apscheduler/_structures.py
index ed9023f..c8fd319 100644
--- a/src/apscheduler/_structures.py
+++ b/src/apscheduler/_structures.py
@@ -235,6 +235,9 @@ class Job:

         return marshalled

+    def set_started_at(self, started_at: datetime) -> None:
+        object.__setattr__(self, "started_at", started_at)
+
     @classmethod
     def unmarshal(cls, serializer: Serializer, marshalled: dict[str, Any]) -> Job:
         marshalled["args"] = serializer.deserialize(marshalled["args"])
agronholm commented 2 months ago

This has been moved to JobResult in 8feb98fa0e671f85049603694663b7bb471be974, as Job is immutable.

peterschutt commented 2 months ago

Thanks @agronholm