3
3
import random
4
4
import socket
5
5
import threading
6
+ import time
6
7
import warnings
7
8
from abc import ABC , abstractmethod
8
9
from copy import copy
19
20
Tuple ,
20
21
Type ,
21
22
TypeVar ,
22
- Union , Set ,
23
+ Union ,
24
+ Set ,
25
+ Coroutine ,
23
26
)
24
27
25
28
from redis ._parsers import AsyncCommandsParser , Encoder
65
68
ResponseError ,
66
69
SlotNotCoveredError ,
67
70
TimeoutError ,
68
- TryAgainError , CrossSlotTransactionError , WatchError , ExecAbortError , InvalidPipelineStack ,
71
+ TryAgainError ,
72
+ CrossSlotTransactionError ,
73
+ WatchError ,
74
+ ExecAbortError ,
75
+ InvalidPipelineStack ,
69
76
)
70
77
from redis .typing import AnyKeyT , EncodableT , KeyT
71
78
from redis .utils import (
@@ -947,6 +954,30 @@ def lock(
947
954
raise_on_release_error = raise_on_release_error ,
948
955
)
949
956
957
+ async def transaction (
958
+ self , func : Coroutine [None , "ClusterPipeline" , Any ], * watches , ** kwargs
959
+ ):
960
+ """
961
+ Convenience method for executing the callable `func` as a transaction
962
+ while watching all keys specified in `watches`. The 'func' callable
963
+ should expect a single argument which is a Pipeline object.
964
+ """
965
+ shard_hint = kwargs .pop ("shard_hint" , None )
966
+ value_from_callable = kwargs .pop ("value_from_callable" , False )
967
+ watch_delay = kwargs .pop ("watch_delay" , None )
968
+ async with self .pipeline (True , shard_hint ) as pipe :
969
+ while True :
970
+ try :
971
+ if watches :
972
+ await pipe .watch (* watches )
973
+ func_value = await func (pipe )
974
+ exec_value = await pipe .execute ()
975
+ return func_value if value_from_callable else exec_value
976
+ except WatchError :
977
+ if watch_delay is not None and watch_delay > 0 :
978
+ time .sleep (watch_delay )
979
+ continue
980
+
950
981
951
982
class ClusterNode :
952
983
"""
@@ -1508,17 +1539,17 @@ class ClusterPipeline(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterComm
1508
1539
| Existing :class:`~.RedisCluster` client
1509
1540
"""
1510
1541
1511
- __slots__ = ("_command_stack" , " cluster_client" )
1542
+ __slots__ = ("cluster_client" , )
1512
1543
1513
1544
def __init__ (
1514
- self ,
1515
- client : RedisCluster ,
1516
- transaction : Optional [bool ] = None
1545
+ self , client : RedisCluster , transaction : Optional [bool ] = None
1517
1546
) -> None :
1518
1547
self .cluster_client = client
1519
1548
self ._transaction = transaction
1520
1549
self ._execution_strategy : ExecutionStrategy = (
1521
- PipelineStrategy (self ) if not self ._transaction else TransactionStrategy (self )
1550
+ PipelineStrategy (self )
1551
+ if not self ._transaction
1552
+ else TransactionStrategy (self )
1522
1553
)
1523
1554
1524
1555
async def initialize (self ) -> "ClusterPipeline" :
@@ -1585,7 +1616,9 @@ async def execute(
1585
1616
can't be mapped to a slot
1586
1617
"""
1587
1618
try :
1588
- return await self ._execution_strategy .execute (raise_on_error , allow_redirections )
1619
+ return await self ._execution_strategy .execute (
1620
+ raise_on_error , allow_redirections
1621
+ )
1589
1622
finally :
1590
1623
await self .reset ()
1591
1624
@@ -1628,7 +1661,6 @@ async def unlink(self, *names):
1628
1661
def mset_nonatomic (
1629
1662
self , mapping : Mapping [AnyKeyT , EncodableT ]
1630
1663
) -> "ClusterPipeline" :
1631
-
1632
1664
return self ._execution_strategy .mset_nonatomic (mapping )
1633
1665
1634
1666
@@ -1663,7 +1695,7 @@ async def initialize(self) -> "ClusterPipeline":
1663
1695
1664
1696
@abstractmethod
1665
1697
def execute_command (
1666
- self , * args : Union [KeyT , EncodableT ], ** kwargs : Any
1698
+ self , * args : Union [KeyT , EncodableT ], ** kwargs : Any
1667
1699
) -> "ClusterPipeline" :
1668
1700
"""
1669
1701
Append a raw command to the pipeline.
@@ -1748,7 +1780,6 @@ async def unlink(self, *names):
1748
1780
1749
1781
1750
1782
class AbstractStrategy (ExecutionStrategy ):
1751
-
1752
1783
def __init__ (self , pipe : ClusterPipeline ) -> None :
1753
1784
self ._pipe : ClusterPipeline = pipe
1754
1785
self ._command_queue : List ["PipelineCommand" ] = []
@@ -1779,7 +1810,9 @@ async def initialize(self) -> "ClusterPipeline":
1779
1810
self ._command_queue = []
1780
1811
return self ._pipe
1781
1812
1782
- def execute_command (self , * args : Union [KeyT , EncodableT ], ** kwargs : Any ) -> "ClusterPipeline" :
1813
+ def execute_command (
1814
+ self , * args : Union [KeyT , EncodableT ], ** kwargs : Any
1815
+ ) -> "ClusterPipeline" :
1783
1816
self ._command_queue .append (
1784
1817
PipelineCommand (len (self ._command_queue ), * args , ** kwargs )
1785
1818
)
@@ -1797,11 +1830,15 @@ def _annotate_exception(self, exception, number, command):
1797
1830
exception .args = (msg ,) + exception .args [1 :]
1798
1831
1799
1832
@abstractmethod
1800
- def mset_nonatomic (self , mapping : Mapping [AnyKeyT , EncodableT ]) -> "ClusterPipeline" :
1833
+ def mset_nonatomic (
1834
+ self , mapping : Mapping [AnyKeyT , EncodableT ]
1835
+ ) -> "ClusterPipeline" :
1801
1836
pass
1802
1837
1803
1838
@abstractmethod
1804
- async def execute (self , raise_on_error : bool = True , allow_redirections : bool = True ) -> List [Any ]:
1839
+ async def execute (
1840
+ self , raise_on_error : bool = True , allow_redirections : bool = True
1841
+ ) -> List [Any ]:
1805
1842
pass
1806
1843
1807
1844
@abstractmethod
@@ -1828,11 +1865,14 @@ async def discard(self):
1828
1865
async def unlink (self , * names ):
1829
1866
pass
1830
1867
1868
+
1831
1869
class PipelineStrategy (AbstractStrategy ):
1832
1870
def __init__ (self , pipe : ClusterPipeline ) -> None :
1833
1871
super ().__init__ (pipe )
1834
1872
1835
- def mset_nonatomic (self , mapping : Mapping [AnyKeyT , EncodableT ]) -> "ClusterPipeline" :
1873
+ def mset_nonatomic (
1874
+ self , mapping : Mapping [AnyKeyT , EncodableT ]
1875
+ ) -> "ClusterPipeline" :
1836
1876
encoder = self ._pipe .cluster_client .encoder
1837
1877
1838
1878
slots_pairs = {}
@@ -1845,7 +1885,9 @@ def mset_nonatomic(self, mapping: Mapping[AnyKeyT, EncodableT]) -> "ClusterPipel
1845
1885
1846
1886
return self ._pipe
1847
1887
1848
- async def execute (self , raise_on_error : bool = True , allow_redirections : bool = True ) -> List [Any ]:
1888
+ async def execute (
1889
+ self , raise_on_error : bool = True , allow_redirections : bool = True
1890
+ ) -> List [Any ]:
1849
1891
if not self ._command_queue :
1850
1892
return []
1851
1893
@@ -1993,6 +2035,7 @@ async def unlink(self, *names):
1993
2035
1994
2036
return self .execute_command ("UNLINK" , names [0 ])
1995
2037
2038
+
1996
2039
class TransactionStrategy (AbstractStrategy ):
1997
2040
NO_SLOTS_COMMANDS = {"UNWATCH" }
1998
2041
IMMEDIATE_EXECUTE_COMMANDS = {"WATCH" , "UNWATCH" }
@@ -2018,7 +2061,9 @@ def __init__(self, pipe: ClusterPipeline) -> None:
2018
2061
RedisCluster .ERRORS_ALLOW_RETRY + self .SLOT_REDIRECT_ERRORS
2019
2062
)
2020
2063
2021
- def _get_client_and_connection_for_transaction (self ) -> Tuple [ClusterNode , Connection ]:
2064
+ def _get_client_and_connection_for_transaction (
2065
+ self ,
2066
+ ) -> Tuple [ClusterNode , Connection ]:
2022
2067
"""
2023
2068
Find a connection for a pipeline transaction.
2024
2069
@@ -2065,7 +2110,9 @@ def runner():
2065
2110
2066
2111
return response
2067
2112
2068
- async def _execute_command (self , * args : Union [KeyT , EncodableT ], ** kwargs : Any ) -> Any :
2113
+ async def _execute_command (
2114
+ self , * args : Union [KeyT , EncodableT ], ** kwargs : Any
2115
+ ) -> Any :
2069
2116
if self ._pipe .cluster_client ._initialize :
2070
2117
await self ._pipe .cluster_client .initialize ()
2071
2118
@@ -2074,7 +2121,7 @@ async def _execute_command(self, *args: Union[KeyT, EncodableT], **kwargs: Any)
2074
2121
slot_number = await self ._pipe .cluster_client ._determine_slot (* args )
2075
2122
2076
2123
if (
2077
- self ._watching or args [0 ] in self .IMMEDIATE_EXECUTE_COMMANDS
2124
+ self ._watching or args [0 ] in self .IMMEDIATE_EXECUTE_COMMANDS
2078
2125
) and not self ._explicit_transaction :
2079
2126
if args [0 ] == "WATCH" :
2080
2127
self ._validate_watch ()
@@ -2118,7 +2165,12 @@ async def _get_connection_and_send_command(self, *args, **options):
2118
2165
)
2119
2166
2120
2167
async def _send_command_parse_response (
2121
- self , connection : Connection , redis_node : ClusterNode , command_name , * args , ** options
2168
+ self ,
2169
+ connection : Connection ,
2170
+ redis_node : ClusterNode ,
2171
+ command_name ,
2172
+ * args ,
2173
+ ** options ,
2122
2174
):
2123
2175
"""
2124
2176
Send a command and parse the response
@@ -2145,8 +2197,10 @@ async def _reinitialize_on_error(self, error):
2145
2197
2146
2198
self ._pipe .cluster_client .reinitialize_counter += 1
2147
2199
if (
2148
- self ._pipe .cluster_client .reinitialize_steps
2149
- and self ._pipe .cluster_client .reinitialize_counter % self ._pipe .cluster_client .reinitialize_steps == 0
2200
+ self ._pipe .cluster_client .reinitialize_steps
2201
+ and self ._pipe .cluster_client .reinitialize_counter
2202
+ % self ._pipe .cluster_client .reinitialize_steps
2203
+ == 0
2150
2204
):
2151
2205
await self ._pipe .cluster_client .nodes_manager .initialize ()
2152
2206
self .reinitialize_counter = 0
@@ -2164,10 +2218,14 @@ def _raise_first_error(self, responses, stack):
2164
2218
self ._annotate_exception (r , cmd .position + 1 , cmd .args )
2165
2219
raise r
2166
2220
2167
- def mset_nonatomic (self , mapping : Mapping [AnyKeyT , EncodableT ]) -> "ClusterPipeline" :
2168
- raise NotImplementedError ('Method is not supported in transactional context.' )
2221
+ def mset_nonatomic (
2222
+ self , mapping : Mapping [AnyKeyT , EncodableT ]
2223
+ ) -> "ClusterPipeline" :
2224
+ raise NotImplementedError ("Method is not supported in transactional context." )
2169
2225
2170
- async def execute (self , raise_on_error : bool = True , allow_redirections : bool = True ) -> List [Any ]:
2226
+ async def execute (
2227
+ self , raise_on_error : bool = True , allow_redirections : bool = True
2228
+ ) -> List [Any ]:
2171
2229
stack = self ._command_queue
2172
2230
if not stack and (not self ._watching or not self ._pipeline_slots ):
2173
2231
return []
@@ -2197,7 +2255,7 @@ async def _execute_transaction(
2197
2255
stack = chain (
2198
2256
[PipelineCommand (0 , "MULTI" )],
2199
2257
stack ,
2200
- [PipelineCommand (0 , ' EXEC' )],
2258
+ [PipelineCommand (0 , " EXEC" )],
2201
2259
)
2202
2260
commands = [c .args for c in stack if EMPTY_RESPONSE not in c .kwargs ]
2203
2261
packed_commands = connection .pack_commands (commands )
@@ -2334,4 +2392,4 @@ async def discard(self):
2334
2392
await self .reset ()
2335
2393
2336
2394
async def unlink (self , * names ):
2337
- return self .execute_command ("UNLINK" , * names )
2395
+ return self .execute_command ("UNLINK" , * names )
0 commit comments