@@ -454,6 +454,7 @@ async def declare_ex(
454454 "rate_limit" : queue .rate_limit ,
455455 "rate_limit_window" : queue .rate_limit_window ,
456456 "resume_at" : queue .resume_at ,
457+ "eager_polling" : queue .eager_polling ,
457458 },
458459 )
459460
@@ -494,6 +495,7 @@ def sync_declare_ex(
494495 "rate_limit" : queue .rate_limit ,
495496 "rate_limit_window" : queue .rate_limit_window ,
496497 "resume_at" : queue .resume_at ,
498+ "eager_polling" : queue .eager_polling ,
497499 },
498500 )
499501
@@ -739,6 +741,31 @@ async def get_job(self, ref: Reference) -> QueuedJob | None:
739741 return None
740742 return QueuedJob .unpack (record )
741743
744+ @_ensure_pool_is_open
745+ async def get_jobs (self , refs : list [Reference ]) -> list [QueuedJob ]:
746+ """
747+ Resolve multiple references to job instances.
748+
749+ If a job no longer exists, it will be skipped and not included in
750+ the returned list.
751+
752+ :param refs: The references to the jobs to retrieve.
753+ :return: A list of the resolved jobs.
754+ """
755+ if not refs :
756+ return []
757+
758+ async with self .pool .connection () as conn :
759+ async with conn .cursor (row_factory = dict_row ) as cursor :
760+ await cursor .execute (
761+ self ._get_jobs_sql (), [[ref .identifier for ref in refs ]]
762+ )
763+ return [
764+ QueuedJob .unpack (record )
765+ async for record in cursor
766+ if record is not None
767+ ]
768+
742769 @_ensure_sync_pool_is_open
743770 def sync_get_job (self , ref : Reference ) -> QueuedJob | None :
744771 """
@@ -762,6 +789,7 @@ async def wait_for_job(
762789 * ,
763790 interval : int = 1 ,
764791 timeout : float | int | None = None ,
792+ states : set [QueuedJob .State ] | None = None ,
765793 ) -> QueuedJob | None :
766794 """
767795 Wait for a job to complete.
@@ -777,17 +805,63 @@ async def wait_for_job(
777805 :param interval: The number of seconds to wait between checks.
778806 :param timeout: The maximum number of seconds to wait for the job to
779807 complete. If not provided, the method will wait indefinitely.
808+ :param states: A set of additional states to consider as "complete". If
809+ the job enters any of these states, it will be considered complete
810+ and the method will return. By default, only the SUCCEEDED and
811+ FAILED states are considered complete.
780812 """
813+ states = states or {QueuedJob .State .SUCCEEDED , QueuedJob .State .FAILED }
781814 async with asyncio .timeout (timeout ):
782815 while True :
783816 job = await self .get_job (ref )
784- if job is None or job .state in {
785- QueuedJob .State .SUCCEEDED ,
786- QueuedJob .State .FAILED ,
787- }:
817+ if job is None or job .state in states :
788818 return job
789819 await asyncio .sleep (interval )
790820
821+ async def wait_for_jobs (
822+ self ,
823+ refs : list [Reference ],
824+ * ,
825+ interval : int = 1 ,
826+ timeout : float | int | None = None ,
827+ states : set [QueuedJob .State ] | None = None ,
828+ ):
829+ """
830+ Wait for multiple jobs to complete.
831+
832+ This method will loop until all jobs referenced by the provided
833+ references have completed. The interval parameter controls how often
834+ the job status is checked. This will not block the event loop, so
835+ other tasks can run while waiting for the jobs to complete.
836+
837+ If a job no longer exists, it will be skipped and not included in
838+ the returned list.
839+
840+ :param refs: The references to the jobs to wait for.
841+ :param interval: The number of seconds to wait between checks.
842+ :param timeout: The maximum number of seconds to wait for the jobs to
843+ complete. If not provided, the method will wait indefinitely.
844+ :param states: A set of additional states to consider as "complete". If
845+ a job enters any of these states, it will be considered complete
846+ and removed from the list of jobs to wait for. By default, only
847+ the SUCCEEDED and FAILED states are considered complete.
848+ :return: A list of the completed jobs. Jobs that no longer exist will
849+ not be included in the list.
850+ """
851+ states = states or {QueuedJob .State .SUCCEEDED , QueuedJob .State .FAILED }
852+ async with asyncio .timeout (timeout ):
853+ pending = set (refs )
854+ completed = []
855+ while pending :
856+ jobs = await self .get_jobs (list (pending ))
857+ for job in jobs :
858+ if job is None or job .state in states :
859+ completed .append (job )
860+ pending .remove (Reference (job .id ))
861+ if pending :
862+ await asyncio .sleep (interval )
863+ return completed
864+
791865 @_ensure_pool_is_open
792866 async def get_all_queues (self ) -> list [Queue ]:
793867 """
@@ -1075,7 +1149,7 @@ def _push_job_sql(self):
10751149 AND state NOT IN ('succeeded', 'failed')
10761150 DO UPDATE
10771151 SET
1078- state = EXCLUDED .state
1152+ state = {jobs} .state
10791153 RETURNING id;
10801154 """
10811155 ).format (jobs = sql .Identifier (f"{ self .prefix } jobs" ))
@@ -1092,6 +1166,18 @@ def _get_job_sql(self):
10921166 """
10931167 ).format (jobs = sql .Identifier (f"{ self .prefix } jobs" ))
10941168
1169+ def _get_jobs_sql (self ):
1170+ return sql .SQL (
1171+ """
1172+ SELECT
1173+ *
1174+ FROM
1175+ {jobs}
1176+ WHERE
1177+ id = ANY(%s)
1178+ """
1179+ ).format (jobs = sql .Identifier (f"{ self .prefix } jobs" ))
1180+
10951181 def _declare_sql (self , upsert : bool ):
10961182 action = sql .SQL (
10971183 """
@@ -1111,7 +1197,8 @@ def _declare_sql(self, upsert: bool):
11111197 polling_interval = EXCLUDED.polling_interval,
11121198 rate_limit = EXCLUDED.rate_limit,
11131199 rate_limit_window = EXCLUDED.rate_limit_window,
1114- resume_at = EXCLUDED.resume_at
1200+ resume_at = EXCLUDED.resume_at,
1201+ eager_polling = EXCLUDED.eager_polling
11151202 """
11161203 )
11171204
@@ -1127,7 +1214,8 @@ def _declare_sql(self, upsert: bool):
11271214 polling_interval,
11281215 rate_limit,
11291216 rate_limit_window,
1130- resume_at
1217+ resume_at,
1218+ eager_polling
11311219 ) VALUES (
11321220 %(name)s,
11331221 %(state)s,
@@ -1138,7 +1226,8 @@ def _declare_sql(self, upsert: bool):
11381226 %(polling_interval)s,
11391227 %(rate_limit)s,
11401228 %(rate_limit_window)s,
1141- %(resume_at)s
1229+ %(resume_at)s,
1230+ %(eager_polling)s
11421231 )
11431232 ON CONFLICT (name) DO
11441233 {action}
@@ -1151,7 +1240,8 @@ def _declare_sql(self, upsert: bool):
11511240 executor_options,
11521241 rate_limit,
11531242 rate_limit_window,
1154- resume_at
1243+ resume_at,
1244+ eager_polling
11551245 """
11561246 ).format (
11571247 queues = sql .Identifier (f"{ self .prefix } queues" ),
0 commit comments