11import os
2- from typing import Any , List , Literal , Optional , Tuple , Union
2+ from typing import Any , Callable , List , Literal , Optional , Tuple , Union
33
44from redis import Redis
5- from redis .client import Pipeline
65from redis .commands .core import Script
76
7+ from ._internal import ACTIVE , ERROR , QUEUED , SCHEDULED
8+
9+ LOCAL_FUNC_TEMPLATE = """
10+ local function {func_name}(KEYS, ARGV)
11+ {func_body}
12+ end
13+
14+ """
15+
816# ARGV = { score, member }
917ZADD_NOUPDATE_TEMPLATE = """
1018 if {condition} redis.call('zscore', {key}, {member}) then
@@ -313,8 +321,14 @@ def __init__(self, redis: Redis) -> None:
313321
314322 self ._get_expired_tasks = redis .register_script (GET_EXPIRED_TASKS )
315323
316- self ._execute_pipeline = self .register_script_from_file (
317- "lua/execute_pipeline.lua"
324+ self ._move_task = self .register_script_from_file (
325+ "lua/move_task.lua" ,
326+ include_functions = {
327+ "zadd_noupdate" : ZADD_NOUPDATE ,
328+ "zadd_update_min" : ZADD_UPDATE_MIN ,
329+ "srem_if_not_exists" : SREM_IF_NOT_EXISTS ,
330+ "delete_if_not_in_zsets" : DELETE_IF_NOT_IN_ZSETS ,
331+ },
318332 )
319333
320334 @property
@@ -330,11 +344,25 @@ def can_replicate_commands(self) -> bool:
330344 self ._can_replicate_commands = result
331345 return self ._can_replicate_commands
332346
333- def register_script_from_file (self , filename : str ) -> Script :
347+ def register_script_from_file (
348+ self , filename : str , include_functions : Optional [dict ] = None
349+ ) -> Script :
334350 with open (
335351 os .path .join (os .path .dirname (os .path .realpath (__file__ )), filename )
336352 ) as f :
337- return self .redis .register_script (f .read ())
353+ script = f .read ()
354+ if include_functions :
355+ function_definitions = []
356+ for func_name in sorted (include_functions .keys ()):
357+ function_definitions .append (
358+ LOCAL_FUNC_TEMPLATE .format (
359+ func_name = func_name ,
360+ func_body = include_functions [func_name ],
361+ )
362+ )
363+ script = "\n " .join (function_definitions + [script ])
364+
365+ return self .redis .register_script (script )
338366
339367 def zadd (
340368 self ,
@@ -538,77 +566,62 @@ def get_expired_tasks(
538566 # [queue1, task1, queue2, task2] -> [(queue1, task1), (queue2, task2)]
539567 return list (zip (result [::2 ], result [1 ::2 ]))
540568
541- def execute_pipeline (
542- self , pipeline : Pipeline , client : Optional [Redis ] = None
543- ) -> List [Any ]:
569+ def move_task (
570+ self ,
571+ id : str ,
572+ queue : str ,
573+ from_state : str ,
574+ to_state : Optional [str ],
575+ unique : bool ,
576+ when : float ,
577+ mode : Optional [str ],
578+ key_func : Callable [..., str ],
579+ publish_queued_tasks : bool ,
580+ client : Optional [Redis ] = None ,
581+ ) -> Any :
544582 """
545- Executes the given Redis pipeline as a Lua script. When an error
546- occurs, the transaction stops executing, and an exception is raised.
547- This differs from Redis transactions, where execution continues after an
548- error. On success, a list of results is returned. The pipeline is
549- cleared after execution and can no longer be reused.
550-
551- Example:
552-
553- p = conn.pipeline()
554- p.lrange('x', 0, -1)
555- p.set('success', 1)
556-
557- # If "x" is empty or a list, an array [[...], True] is returned.
558- # Otherwise, ResponseError is raised and "success" is not set.
559- results = redis_scripts.execute_pipeline(p)
583+ Refer to task._move internal helper documentation.
560584 """
561585
562- client = client or self .redis
563-
564- executing_pipeline = None
565- try :
566-
567- # Prepare args
568- stack = pipeline .command_stack
569- script_args = [int (self .can_replicate_commands ), len (stack )]
570- for args , options in stack :
571- script_args += [len (args ) - 1 ] + list (args )
572-
573- # Run the pipeline
574- if self .can_replicate_commands : # Redis 3.2 or higher
575- # Make sure scripts exist
576- if pipeline .scripts :
577- pipeline .load_scripts ()
578-
579- raw_results = self ._execute_pipeline (
580- args = script_args , client = client
581- )
582- else :
583- executing_pipeline = client .pipeline ()
584-
585- # Always load scripts to avoid issues when Redis loads data
586- # from AOF file / when replicating.
587- for s in pipeline .scripts :
588- executing_pipeline .script_load (s .script )
589-
590- # Run actual pipeline lua script
591- self ._execute_pipeline (
592- args = script_args , client = executing_pipeline
593- )
594-
595- # Always load all scripts and run actual pipeline lua script
596- raw_results = executing_pipeline .execute ()[- 1 ]
597-
598- # Run response callbacks on results.
599- results = []
600- response_callbacks = pipeline .response_callbacks
601- for ((args , options ), result ) in zip (stack , raw_results ):
602- command_name = args [0 ]
603- if command_name in response_callbacks :
604- result = response_callbacks [command_name ](
605- result , ** options
606- )
607- results .append (result )
608-
609- return results
610-
611- finally :
612- if executing_pipeline :
613- executing_pipeline .reset ()
614- pipeline .reset ()
586+ def _bool_to_str (v : bool ) -> str :
587+ return "true" if v else "false"
588+
589+ def _none_to_empty_str (v : Optional [str ]) -> str :
590+ return v or ""
591+
592+ key_task_id = key_func ("task" , id )
593+ key_task_id_executions = key_func ("task" , id , "executions" )
594+ key_task_id_executions_count = key_func ("task" , id , "executions_count" )
595+ key_from_state = key_func (from_state )
596+ key_to_state = key_func (to_state ) if to_state else ""
597+ key_active_queue = key_func (ACTIVE , queue )
598+ key_queued_queue = key_func (QUEUED , queue )
599+ key_error_queue = key_func (ERROR , queue )
600+ key_scheduled_queue = key_func (SCHEDULED , queue )
601+ key_activity = key_func ("activity" )
602+
603+ return self ._move_task (
604+ keys = [
605+ key_task_id ,
606+ key_task_id_executions ,
607+ key_task_id_executions_count ,
608+ key_from_state ,
609+ key_to_state ,
610+ key_active_queue ,
611+ key_queued_queue ,
612+ key_error_queue ,
613+ key_scheduled_queue ,
614+ key_activity ,
615+ ],
616+ args = [
617+ id ,
618+ queue ,
619+ from_state ,
620+ _none_to_empty_str (to_state ),
621+ _bool_to_str (unique ),
622+ when ,
623+ _none_to_empty_str (mode ),
624+ _bool_to_str (publish_queued_tasks ),
625+ ],
626+ client = client ,
627+ )
0 commit comments