1313
1414from taskiq .abc .broker import AckableMessage , AsyncBroker
1515from taskiq .abc .middleware import TaskiqMiddleware
16+ from taskiq .abc .result_backend import AsyncResultBackend
17+ from taskiq .acks import AcknowledgeType
1618from taskiq .brokers .inmemory_broker import InMemoryBroker
1719from taskiq .exceptions import NoResultError , TaskiqResultTimeoutError
1820from taskiq .message import TaskiqMessage
@@ -26,6 +28,7 @@ def get_receiver(
2628 no_parse : bool = False ,
2729 max_async_tasks : int | None = None ,
2830 max_async_tasks_jitter : int = 0 ,
31+ ack_type : AcknowledgeType | None = None ,
2932) -> Receiver :
3033 """
3134 Returns receiver with custom broker and args.
@@ -44,9 +47,50 @@ def get_receiver(
4447 validate_params = not no_parse ,
4548 max_async_tasks = max_async_tasks ,
4649 max_async_tasks_jitter = max_async_tasks_jitter ,
50+ ack_type = ack_type ,
4751 )
4852
4953
54+ class _EventResultBackend (AsyncResultBackend [Any ]):
55+ def __init__ (self , events : list [str ]) -> None :
56+ self .events = events
57+ self .results : dict [str , TaskiqResult [Any ]] = {}
58+
59+ async def set_result (self , task_id : str , result : TaskiqResult [Any ]) -> None :
60+ self .events .append ("save" )
61+ self .results [task_id ] = result
62+
63+ async def is_result_ready (self , task_id : str ) -> bool :
64+ return task_id in self .results
65+
66+ async def get_result (
67+ self ,
68+ task_id : str ,
69+ with_logs : bool = False ,
70+ ) -> TaskiqResult [Any ]:
71+ return self .results [task_id ]
72+
73+
74+ class _EventMiddleware (TaskiqMiddleware ):
75+ def __init__ (self , events : list [str ]) -> None :
76+ super ().__init__ ()
77+ self .events = events
78+
79+ def post_execute (
80+ self ,
81+ message : TaskiqMessage ,
82+ result : TaskiqResult [Any ],
83+ ) -> None :
84+ self .events .append ("post_execute" )
85+
86+ def post_save (
87+ self ,
88+ message : TaskiqMessage ,
89+ result : TaskiqResult [Any ],
90+ ) -> None :
91+ self .events .append ("post_save" )
92+
93+
5094async def test_run_task_successful_async () -> None :
5195 """Tests that run_task can run async tasks."""
5296
@@ -327,6 +371,192 @@ async def ack_callback() -> None:
327371 assert acked
328372
329373
374+ async def test_task_ack_type_when_received_overrides_worker_ack_type () -> None :
375+ """Task ack_type label overrides worker-level ack type."""
376+ events : list [str ] = []
377+ broker = (
378+ InMemoryBroker ()
379+ .with_result_backend (
380+ _EventResultBackend (events ),
381+ )
382+ .with_middlewares (_EventMiddleware (events ))
383+ )
384+
385+ @broker .task
386+ async def my_task () -> int :
387+ events .append ("task" )
388+ return 1
389+
390+ def ack_callback () -> None :
391+ events .append ("ack" )
392+
393+ receiver = get_receiver (broker , ack_type = AcknowledgeType .WHEN_SAVED )
394+ broker_message = broker .formatter .dumps (
395+ TaskiqMessage (
396+ task_id = "task_id" ,
397+ task_name = my_task .task_name ,
398+ labels = {"ack_type" : "when_received" },
399+ args = [],
400+ kwargs = {},
401+ ),
402+ )
403+
404+ await receiver .callback (
405+ AckableMessage (
406+ data = broker_message .message ,
407+ ack = ack_callback ,
408+ ),
409+ )
410+
411+ assert events == ["ack" , "task" , "post_execute" , "save" , "post_save" ]
412+
413+
414+ async def test_task_ack_type_when_executed_overrides_worker_ack_type () -> None :
415+ """Task ack_type label can delay a worker-level when_received ack."""
416+ events : list [str ] = []
417+ broker = (
418+ InMemoryBroker ()
419+ .with_result_backend (
420+ _EventResultBackend (events ),
421+ )
422+ .with_middlewares (_EventMiddleware (events ))
423+ )
424+
425+ @broker .task (ack_type = "when_executed" )
426+ async def my_task () -> int :
427+ events .append ("task" )
428+ return 1
429+
430+ def ack_callback () -> None :
431+ events .append ("ack" )
432+
433+ receiver = get_receiver (broker , ack_type = AcknowledgeType .WHEN_RECEIVED )
434+ broker_message = broker .formatter .dumps (my_task .kicker ()._prepare_message ())
435+
436+ await receiver .callback (
437+ AckableMessage (
438+ data = broker_message .message ,
439+ ack = ack_callback ,
440+ ),
441+ )
442+
443+ assert events == ["task" , "ack" , "post_execute" , "save" , "post_save" ]
444+
445+
446+ async def test_task_ack_type_when_saved_overrides_worker_ack_type () -> None :
447+ """Task ack_type label can use when_saved with an early-ack worker."""
448+ events : list [str ] = []
449+ broker = (
450+ InMemoryBroker ()
451+ .with_result_backend (
452+ _EventResultBackend (events ),
453+ )
454+ .with_middlewares (_EventMiddleware (events ))
455+ )
456+
457+ @broker .task
458+ async def my_task () -> int :
459+ events .append ("task" )
460+ return 1
461+
462+ def ack_callback () -> None :
463+ events .append ("ack" )
464+
465+ receiver = get_receiver (broker , ack_type = AcknowledgeType .WHEN_RECEIVED )
466+ broker_message = broker .formatter .dumps (
467+ TaskiqMessage (
468+ task_id = "task_id" ,
469+ task_name = my_task .task_name ,
470+ labels = {"ack_type" : "when_saved" },
471+ args = [],
472+ kwargs = {},
473+ ),
474+ )
475+
476+ await receiver .callback (
477+ AckableMessage (
478+ data = broker_message .message ,
479+ ack = ack_callback ,
480+ ),
481+ )
482+
483+ assert events == ["task" , "post_execute" , "save" , "post_save" , "ack" ]
484+
485+
486+ async def test_worker_ack_type_is_used_without_task_ack_type () -> None :
487+ """Worker-level ack_type is used when the task has no ack_type label."""
488+ events : list [str ] = []
489+ broker = (
490+ InMemoryBroker ()
491+ .with_result_backend (
492+ _EventResultBackend (events ),
493+ )
494+ .with_middlewares (_EventMiddleware (events ))
495+ )
496+
497+ @broker .task
498+ async def my_task () -> int :
499+ events .append ("task" )
500+ return 1
501+
502+ def ack_callback () -> None :
503+ events .append ("ack" )
504+
505+ receiver = get_receiver (broker , ack_type = AcknowledgeType .WHEN_RECEIVED )
506+ broker_message = broker .formatter .dumps (
507+ TaskiqMessage (
508+ task_id = "task_id" ,
509+ task_name = my_task .task_name ,
510+ labels = {},
511+ args = [],
512+ kwargs = {},
513+ ),
514+ )
515+
516+ await receiver .callback (
517+ AckableMessage (
518+ data = broker_message .message ,
519+ ack = ack_callback ,
520+ ),
521+ )
522+
523+ assert events == ["ack" , "task" , "post_execute" , "save" , "post_save" ]
524+
525+
526+ async def test_invalid_task_ack_type_raises_before_execution () -> None :
527+ """Invalid task ack_type fails before task execution and acknowledgement."""
528+ events : list [str ] = []
529+ broker = InMemoryBroker ()
530+
531+ @broker .task
532+ async def my_task () -> None :
533+ events .append ("task" )
534+
535+ def ack_callback () -> None :
536+ events .append ("ack" )
537+
538+ receiver = get_receiver (broker , ack_type = AcknowledgeType .WHEN_RECEIVED )
539+ broker_message = broker .formatter .dumps (
540+ TaskiqMessage (
541+ task_id = "task_id" ,
542+ task_name = my_task .task_name ,
543+ labels = {"ack_type" : "when_save" },
544+ args = [],
545+ kwargs = {},
546+ ),
547+ )
548+
549+ with pytest .raises (ValueError , match = "Invalid ack_type label" ):
550+ await receiver .callback (
551+ AckableMessage (
552+ data = broker_message .message ,
553+ ack = ack_callback ,
554+ ),
555+ )
556+
557+ assert events == []
558+
559+
330560async def test_callback_wrong_format () -> None :
331561 """Test that wrong format of a message won't throw an error."""
332562 receiver = get_receiver ()
0 commit comments