redis 6.0.0b2__py3-none-any.whl → 6.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- redis/__init__.py +8 -1
- redis/_parsers/__init__.py +8 -1
- redis/_parsers/base.py +53 -1
- redis/_parsers/hiredis.py +72 -5
- redis/_parsers/resp3.py +12 -37
- redis/asyncio/client.py +76 -70
- redis/asyncio/cluster.py +796 -104
- redis/asyncio/connection.py +8 -10
- redis/asyncio/retry.py +12 -0
- redis/backoff.py +54 -0
- redis/client.py +101 -89
- redis/cluster.py +1088 -365
- redis/commands/core.py +104 -104
- redis/commands/helpers.py +19 -6
- redis/commands/json/__init__.py +1 -1
- redis/commands/json/commands.py +8 -8
- redis/commands/redismodules.py +20 -10
- redis/commands/search/commands.py +2 -2
- redis/commands/timeseries/__init__.py +1 -1
- redis/connection.py +19 -9
- redis/exceptions.py +18 -0
- redis/retry.py +25 -0
- redis/typing.py +0 -4
- redis/utils.py +5 -2
- {redis-6.0.0b2.dist-info → redis-6.2.0.dist-info}/METADATA +16 -12
- {redis-6.0.0b2.dist-info → redis-6.2.0.dist-info}/RECORD +28 -28
- {redis-6.0.0b2.dist-info → redis-6.2.0.dist-info}/WHEEL +0 -0
- {redis-6.0.0b2.dist-info → redis-6.2.0.dist-info}/licenses/LICENSE +0 -0
redis/__init__.py
CHANGED
|
@@ -16,11 +16,14 @@ from redis.exceptions import (
|
|
|
16
16
|
BusyLoadingError,
|
|
17
17
|
ChildDeadlockedError,
|
|
18
18
|
ConnectionError,
|
|
19
|
+
CrossSlotTransactionError,
|
|
19
20
|
DataError,
|
|
21
|
+
InvalidPipelineStack,
|
|
20
22
|
InvalidResponse,
|
|
21
23
|
OutOfMemoryError,
|
|
22
24
|
PubSubError,
|
|
23
25
|
ReadOnlyError,
|
|
26
|
+
RedisClusterException,
|
|
24
27
|
RedisError,
|
|
25
28
|
ResponseError,
|
|
26
29
|
TimeoutError,
|
|
@@ -42,7 +45,8 @@ def int_or_str(value):
|
|
|
42
45
|
return value
|
|
43
46
|
|
|
44
47
|
|
|
45
|
-
|
|
48
|
+
# This version is used when building the package for publishing
|
|
49
|
+
__version__ = "6.2.0"
|
|
46
50
|
VERSION = tuple(map(int_or_str, __version__.split(".")))
|
|
47
51
|
|
|
48
52
|
|
|
@@ -56,15 +60,18 @@ __all__ = [
|
|
|
56
60
|
"ConnectionError",
|
|
57
61
|
"ConnectionPool",
|
|
58
62
|
"CredentialProvider",
|
|
63
|
+
"CrossSlotTransactionError",
|
|
59
64
|
"DataError",
|
|
60
65
|
"from_url",
|
|
61
66
|
"default_backoff",
|
|
67
|
+
"InvalidPipelineStack",
|
|
62
68
|
"InvalidResponse",
|
|
63
69
|
"OutOfMemoryError",
|
|
64
70
|
"PubSubError",
|
|
65
71
|
"ReadOnlyError",
|
|
66
72
|
"Redis",
|
|
67
73
|
"RedisCluster",
|
|
74
|
+
"RedisClusterException",
|
|
68
75
|
"RedisError",
|
|
69
76
|
"ResponseError",
|
|
70
77
|
"Sentinel",
|
redis/_parsers/__init__.py
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
from .base import
|
|
1
|
+
from .base import (
|
|
2
|
+
AsyncPushNotificationsParser,
|
|
3
|
+
BaseParser,
|
|
4
|
+
PushNotificationsParser,
|
|
5
|
+
_AsyncRESPBase,
|
|
6
|
+
)
|
|
2
7
|
from .commands import AsyncCommandsParser, CommandsParser
|
|
3
8
|
from .encoders import Encoder
|
|
4
9
|
from .hiredis import _AsyncHiredisParser, _HiredisParser
|
|
@@ -11,10 +16,12 @@ __all__ = [
|
|
|
11
16
|
"_AsyncRESPBase",
|
|
12
17
|
"_AsyncRESP2Parser",
|
|
13
18
|
"_AsyncRESP3Parser",
|
|
19
|
+
"AsyncPushNotificationsParser",
|
|
14
20
|
"CommandsParser",
|
|
15
21
|
"Encoder",
|
|
16
22
|
"BaseParser",
|
|
17
23
|
"_HiredisParser",
|
|
18
24
|
"_RESP2Parser",
|
|
19
25
|
"_RESP3Parser",
|
|
26
|
+
"PushNotificationsParser",
|
|
20
27
|
]
|
redis/_parsers/base.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
from abc import ABC
|
|
3
3
|
from asyncio import IncompleteReadError, StreamReader, TimeoutError
|
|
4
|
-
from typing import List, Optional, Union
|
|
4
|
+
from typing import Callable, List, Optional, Protocol, Union
|
|
5
5
|
|
|
6
6
|
if sys.version_info.major >= 3 and sys.version_info.minor >= 11:
|
|
7
7
|
from asyncio import timeout as async_timeout
|
|
@@ -158,6 +158,58 @@ class AsyncBaseParser(BaseParser):
|
|
|
158
158
|
raise NotImplementedError()
|
|
159
159
|
|
|
160
160
|
|
|
161
|
+
_INVALIDATION_MESSAGE = [b"invalidate", "invalidate"]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class PushNotificationsParser(Protocol):
|
|
165
|
+
"""Protocol defining RESP3-specific parsing functionality"""
|
|
166
|
+
|
|
167
|
+
pubsub_push_handler_func: Callable
|
|
168
|
+
invalidation_push_handler_func: Optional[Callable] = None
|
|
169
|
+
|
|
170
|
+
def handle_pubsub_push_response(self, response):
|
|
171
|
+
"""Handle pubsub push responses"""
|
|
172
|
+
raise NotImplementedError()
|
|
173
|
+
|
|
174
|
+
def handle_push_response(self, response, **kwargs):
|
|
175
|
+
if response[0] not in _INVALIDATION_MESSAGE:
|
|
176
|
+
return self.pubsub_push_handler_func(response)
|
|
177
|
+
if self.invalidation_push_handler_func:
|
|
178
|
+
return self.invalidation_push_handler_func(response)
|
|
179
|
+
|
|
180
|
+
def set_pubsub_push_handler(self, pubsub_push_handler_func):
|
|
181
|
+
self.pubsub_push_handler_func = pubsub_push_handler_func
|
|
182
|
+
|
|
183
|
+
def set_invalidation_push_handler(self, invalidation_push_handler_func):
|
|
184
|
+
self.invalidation_push_handler_func = invalidation_push_handler_func
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class AsyncPushNotificationsParser(Protocol):
|
|
188
|
+
"""Protocol defining async RESP3-specific parsing functionality"""
|
|
189
|
+
|
|
190
|
+
pubsub_push_handler_func: Callable
|
|
191
|
+
invalidation_push_handler_func: Optional[Callable] = None
|
|
192
|
+
|
|
193
|
+
async def handle_pubsub_push_response(self, response):
|
|
194
|
+
"""Handle pubsub push responses asynchronously"""
|
|
195
|
+
raise NotImplementedError()
|
|
196
|
+
|
|
197
|
+
async def handle_push_response(self, response, **kwargs):
|
|
198
|
+
"""Handle push responses asynchronously"""
|
|
199
|
+
if response[0] not in _INVALIDATION_MESSAGE:
|
|
200
|
+
return await self.pubsub_push_handler_func(response)
|
|
201
|
+
if self.invalidation_push_handler_func:
|
|
202
|
+
return await self.invalidation_push_handler_func(response)
|
|
203
|
+
|
|
204
|
+
def set_pubsub_push_handler(self, pubsub_push_handler_func):
|
|
205
|
+
"""Set the pubsub push handler function"""
|
|
206
|
+
self.pubsub_push_handler_func = pubsub_push_handler_func
|
|
207
|
+
|
|
208
|
+
def set_invalidation_push_handler(self, invalidation_push_handler_func):
|
|
209
|
+
"""Set the invalidation push handler function"""
|
|
210
|
+
self.invalidation_push_handler_func = invalidation_push_handler_func
|
|
211
|
+
|
|
212
|
+
|
|
161
213
|
class _AsyncRESPBase(AsyncBaseParser):
|
|
162
214
|
"""Base class for async resp parsing"""
|
|
163
215
|
|
redis/_parsers/hiredis.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import socket
|
|
3
3
|
import sys
|
|
4
|
+
from logging import getLogger
|
|
4
5
|
from typing import Callable, List, Optional, TypedDict, Union
|
|
5
6
|
|
|
6
7
|
if sys.version_info.major >= 3 and sys.version_info.minor >= 11:
|
|
@@ -11,7 +12,12 @@ else:
|
|
|
11
12
|
from ..exceptions import ConnectionError, InvalidResponse, RedisError
|
|
12
13
|
from ..typing import EncodableT
|
|
13
14
|
from ..utils import HIREDIS_AVAILABLE
|
|
14
|
-
from .base import
|
|
15
|
+
from .base import (
|
|
16
|
+
AsyncBaseParser,
|
|
17
|
+
AsyncPushNotificationsParser,
|
|
18
|
+
BaseParser,
|
|
19
|
+
PushNotificationsParser,
|
|
20
|
+
)
|
|
15
21
|
from .socket import (
|
|
16
22
|
NONBLOCKING_EXCEPTION_ERROR_NUMBERS,
|
|
17
23
|
NONBLOCKING_EXCEPTIONS,
|
|
@@ -32,7 +38,7 @@ class _HiredisReaderArgs(TypedDict, total=False):
|
|
|
32
38
|
errors: Optional[str]
|
|
33
39
|
|
|
34
40
|
|
|
35
|
-
class _HiredisParser(BaseParser):
|
|
41
|
+
class _HiredisParser(BaseParser, PushNotificationsParser):
|
|
36
42
|
"Parser class for connections using Hiredis"
|
|
37
43
|
|
|
38
44
|
def __init__(self, socket_read_size):
|
|
@@ -40,6 +46,9 @@ class _HiredisParser(BaseParser):
|
|
|
40
46
|
raise RedisError("Hiredis is not installed")
|
|
41
47
|
self.socket_read_size = socket_read_size
|
|
42
48
|
self._buffer = bytearray(socket_read_size)
|
|
49
|
+
self.pubsub_push_handler_func = self.handle_pubsub_push_response
|
|
50
|
+
self.invalidation_push_handler_func = None
|
|
51
|
+
self._hiredis_PushNotificationType = None
|
|
43
52
|
|
|
44
53
|
def __del__(self):
|
|
45
54
|
try:
|
|
@@ -47,6 +56,11 @@ class _HiredisParser(BaseParser):
|
|
|
47
56
|
except Exception:
|
|
48
57
|
pass
|
|
49
58
|
|
|
59
|
+
def handle_pubsub_push_response(self, response):
|
|
60
|
+
logger = getLogger("push_response")
|
|
61
|
+
logger.debug("Push response: " + str(response))
|
|
62
|
+
return response
|
|
63
|
+
|
|
50
64
|
def on_connect(self, connection, **kwargs):
|
|
51
65
|
import hiredis
|
|
52
66
|
|
|
@@ -64,6 +78,12 @@ class _HiredisParser(BaseParser):
|
|
|
64
78
|
self._reader = hiredis.Reader(**kwargs)
|
|
65
79
|
self._next_response = NOT_ENOUGH_DATA
|
|
66
80
|
|
|
81
|
+
try:
|
|
82
|
+
self._hiredis_PushNotificationType = hiredis.PushNotification
|
|
83
|
+
except AttributeError:
|
|
84
|
+
# hiredis < 3.2
|
|
85
|
+
self._hiredis_PushNotificationType = None
|
|
86
|
+
|
|
67
87
|
def on_disconnect(self):
|
|
68
88
|
self._sock = None
|
|
69
89
|
self._reader = None
|
|
@@ -109,7 +129,7 @@ class _HiredisParser(BaseParser):
|
|
|
109
129
|
if custom_timeout:
|
|
110
130
|
sock.settimeout(self._socket_timeout)
|
|
111
131
|
|
|
112
|
-
def read_response(self, disable_decoding=False):
|
|
132
|
+
def read_response(self, disable_decoding=False, push_request=False):
|
|
113
133
|
if not self._reader:
|
|
114
134
|
raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)
|
|
115
135
|
|
|
@@ -117,6 +137,16 @@ class _HiredisParser(BaseParser):
|
|
|
117
137
|
if self._next_response is not NOT_ENOUGH_DATA:
|
|
118
138
|
response = self._next_response
|
|
119
139
|
self._next_response = NOT_ENOUGH_DATA
|
|
140
|
+
if self._hiredis_PushNotificationType is not None and isinstance(
|
|
141
|
+
response, self._hiredis_PushNotificationType
|
|
142
|
+
):
|
|
143
|
+
response = self.handle_push_response(response)
|
|
144
|
+
if not push_request:
|
|
145
|
+
return self.read_response(
|
|
146
|
+
disable_decoding=disable_decoding, push_request=push_request
|
|
147
|
+
)
|
|
148
|
+
else:
|
|
149
|
+
return response
|
|
120
150
|
return response
|
|
121
151
|
|
|
122
152
|
if disable_decoding:
|
|
@@ -135,6 +165,16 @@ class _HiredisParser(BaseParser):
|
|
|
135
165
|
# happened
|
|
136
166
|
if isinstance(response, ConnectionError):
|
|
137
167
|
raise response
|
|
168
|
+
elif self._hiredis_PushNotificationType is not None and isinstance(
|
|
169
|
+
response, self._hiredis_PushNotificationType
|
|
170
|
+
):
|
|
171
|
+
response = self.handle_push_response(response)
|
|
172
|
+
if not push_request:
|
|
173
|
+
return self.read_response(
|
|
174
|
+
disable_decoding=disable_decoding, push_request=push_request
|
|
175
|
+
)
|
|
176
|
+
else:
|
|
177
|
+
return response
|
|
138
178
|
elif (
|
|
139
179
|
isinstance(response, list)
|
|
140
180
|
and response
|
|
@@ -144,7 +184,7 @@ class _HiredisParser(BaseParser):
|
|
|
144
184
|
return response
|
|
145
185
|
|
|
146
186
|
|
|
147
|
-
class _AsyncHiredisParser(AsyncBaseParser):
|
|
187
|
+
class _AsyncHiredisParser(AsyncBaseParser, AsyncPushNotificationsParser):
|
|
148
188
|
"""Async implementation of parser class for connections using Hiredis"""
|
|
149
189
|
|
|
150
190
|
__slots__ = ("_reader",)
|
|
@@ -154,6 +194,14 @@ class _AsyncHiredisParser(AsyncBaseParser):
|
|
|
154
194
|
raise RedisError("Hiredis is not available.")
|
|
155
195
|
super().__init__(socket_read_size=socket_read_size)
|
|
156
196
|
self._reader = None
|
|
197
|
+
self.pubsub_push_handler_func = self.handle_pubsub_push_response
|
|
198
|
+
self.invalidation_push_handler_func = None
|
|
199
|
+
self._hiredis_PushNotificationType = None
|
|
200
|
+
|
|
201
|
+
async def handle_pubsub_push_response(self, response):
|
|
202
|
+
logger = getLogger("push_response")
|
|
203
|
+
logger.debug("Push response: " + str(response))
|
|
204
|
+
return response
|
|
157
205
|
|
|
158
206
|
def on_connect(self, connection):
|
|
159
207
|
import hiredis
|
|
@@ -171,6 +219,14 @@ class _AsyncHiredisParser(AsyncBaseParser):
|
|
|
171
219
|
self._reader = hiredis.Reader(**kwargs)
|
|
172
220
|
self._connected = True
|
|
173
221
|
|
|
222
|
+
try:
|
|
223
|
+
self._hiredis_PushNotificationType = getattr(
|
|
224
|
+
hiredis, "PushNotification", None
|
|
225
|
+
)
|
|
226
|
+
except AttributeError:
|
|
227
|
+
# hiredis < 3.2
|
|
228
|
+
self._hiredis_PushNotificationType = None
|
|
229
|
+
|
|
174
230
|
def on_disconnect(self):
|
|
175
231
|
self._connected = False
|
|
176
232
|
|
|
@@ -195,7 +251,7 @@ class _AsyncHiredisParser(AsyncBaseParser):
|
|
|
195
251
|
return True
|
|
196
252
|
|
|
197
253
|
async def read_response(
|
|
198
|
-
self, disable_decoding: bool = False
|
|
254
|
+
self, disable_decoding: bool = False, push_request: bool = False
|
|
199
255
|
) -> Union[EncodableT, List[EncodableT]]:
|
|
200
256
|
# If `on_disconnect()` has been called, prohibit any more reads
|
|
201
257
|
# even if they could happen because data might be present.
|
|
@@ -207,6 +263,7 @@ class _AsyncHiredisParser(AsyncBaseParser):
|
|
|
207
263
|
response = self._reader.gets(False)
|
|
208
264
|
else:
|
|
209
265
|
response = self._reader.gets()
|
|
266
|
+
|
|
210
267
|
while response is NOT_ENOUGH_DATA:
|
|
211
268
|
await self.read_from_socket()
|
|
212
269
|
if disable_decoding:
|
|
@@ -219,6 +276,16 @@ class _AsyncHiredisParser(AsyncBaseParser):
|
|
|
219
276
|
# happened
|
|
220
277
|
if isinstance(response, ConnectionError):
|
|
221
278
|
raise response
|
|
279
|
+
elif self._hiredis_PushNotificationType is not None and isinstance(
|
|
280
|
+
response, self._hiredis_PushNotificationType
|
|
281
|
+
):
|
|
282
|
+
response = await self.handle_push_response(response)
|
|
283
|
+
if not push_request:
|
|
284
|
+
return await self.read_response(
|
|
285
|
+
disable_decoding=disable_decoding, push_request=push_request
|
|
286
|
+
)
|
|
287
|
+
else:
|
|
288
|
+
return response
|
|
222
289
|
elif (
|
|
223
290
|
isinstance(response, list)
|
|
224
291
|
and response
|
redis/_parsers/resp3.py
CHANGED
|
@@ -3,13 +3,16 @@ from typing import Any, Union
|
|
|
3
3
|
|
|
4
4
|
from ..exceptions import ConnectionError, InvalidResponse, ResponseError
|
|
5
5
|
from ..typing import EncodableT
|
|
6
|
-
from .base import
|
|
6
|
+
from .base import (
|
|
7
|
+
AsyncPushNotificationsParser,
|
|
8
|
+
PushNotificationsParser,
|
|
9
|
+
_AsyncRESPBase,
|
|
10
|
+
_RESPBase,
|
|
11
|
+
)
|
|
7
12
|
from .socket import SERVER_CLOSED_CONNECTION_ERROR
|
|
8
13
|
|
|
9
|
-
_INVALIDATION_MESSAGE = [b"invalidate", "invalidate"]
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
class _RESP3Parser(_RESPBase):
|
|
15
|
+
class _RESP3Parser(_RESPBase, PushNotificationsParser):
|
|
13
16
|
"""RESP3 protocol implementation"""
|
|
14
17
|
|
|
15
18
|
def __init__(self, socket_read_size):
|
|
@@ -19,7 +22,7 @@ class _RESP3Parser(_RESPBase):
|
|
|
19
22
|
|
|
20
23
|
def handle_pubsub_push_response(self, response):
|
|
21
24
|
logger = getLogger("push_response")
|
|
22
|
-
logger.
|
|
25
|
+
logger.debug("Push response: " + str(response))
|
|
23
26
|
return response
|
|
24
27
|
|
|
25
28
|
def read_response(self, disable_decoding=False, push_request=False):
|
|
@@ -113,9 +116,7 @@ class _RESP3Parser(_RESPBase):
|
|
|
113
116
|
)
|
|
114
117
|
for _ in range(int(response))
|
|
115
118
|
]
|
|
116
|
-
response = self.handle_push_response(
|
|
117
|
-
response, disable_decoding, push_request
|
|
118
|
-
)
|
|
119
|
+
response = self.handle_push_response(response)
|
|
119
120
|
if not push_request:
|
|
120
121
|
return self._read_response(
|
|
121
122
|
disable_decoding=disable_decoding, push_request=push_request
|
|
@@ -129,20 +130,8 @@ class _RESP3Parser(_RESPBase):
|
|
|
129
130
|
response = self.encoder.decode(response)
|
|
130
131
|
return response
|
|
131
132
|
|
|
132
|
-
def handle_push_response(self, response, disable_decoding, push_request):
|
|
133
|
-
if response[0] not in _INVALIDATION_MESSAGE:
|
|
134
|
-
return self.pubsub_push_handler_func(response)
|
|
135
|
-
if self.invalidation_push_handler_func:
|
|
136
|
-
return self.invalidation_push_handler_func(response)
|
|
137
|
-
|
|
138
|
-
def set_pubsub_push_handler(self, pubsub_push_handler_func):
|
|
139
|
-
self.pubsub_push_handler_func = pubsub_push_handler_func
|
|
140
|
-
|
|
141
|
-
def set_invalidation_push_handler(self, invalidation_push_handler_func):
|
|
142
|
-
self.invalidation_push_handler_func = invalidation_push_handler_func
|
|
143
|
-
|
|
144
133
|
|
|
145
|
-
class _AsyncRESP3Parser(_AsyncRESPBase):
|
|
134
|
+
class _AsyncRESP3Parser(_AsyncRESPBase, AsyncPushNotificationsParser):
|
|
146
135
|
def __init__(self, socket_read_size):
|
|
147
136
|
super().__init__(socket_read_size)
|
|
148
137
|
self.pubsub_push_handler_func = self.handle_pubsub_push_response
|
|
@@ -150,7 +139,7 @@ class _AsyncRESP3Parser(_AsyncRESPBase):
|
|
|
150
139
|
|
|
151
140
|
async def handle_pubsub_push_response(self, response):
|
|
152
141
|
logger = getLogger("push_response")
|
|
153
|
-
logger.
|
|
142
|
+
logger.debug("Push response: " + str(response))
|
|
154
143
|
return response
|
|
155
144
|
|
|
156
145
|
async def read_response(
|
|
@@ -253,9 +242,7 @@ class _AsyncRESP3Parser(_AsyncRESPBase):
|
|
|
253
242
|
)
|
|
254
243
|
for _ in range(int(response))
|
|
255
244
|
]
|
|
256
|
-
response = await self.handle_push_response(
|
|
257
|
-
response, disable_decoding, push_request
|
|
258
|
-
)
|
|
245
|
+
response = await self.handle_push_response(response)
|
|
259
246
|
if not push_request:
|
|
260
247
|
return await self._read_response(
|
|
261
248
|
disable_decoding=disable_decoding, push_request=push_request
|
|
@@ -268,15 +255,3 @@ class _AsyncRESP3Parser(_AsyncRESPBase):
|
|
|
268
255
|
if isinstance(response, bytes) and disable_decoding is False:
|
|
269
256
|
response = self.encoder.decode(response)
|
|
270
257
|
return response
|
|
271
|
-
|
|
272
|
-
async def handle_push_response(self, response, disable_decoding, push_request):
|
|
273
|
-
if response[0] not in _INVALIDATION_MESSAGE:
|
|
274
|
-
return await self.pubsub_push_handler_func(response)
|
|
275
|
-
if self.invalidation_push_handler_func:
|
|
276
|
-
return await self.invalidation_push_handler_func(response)
|
|
277
|
-
|
|
278
|
-
def set_pubsub_push_handler(self, pubsub_push_handler_func):
|
|
279
|
-
self.pubsub_push_handler_func = pubsub_push_handler_func
|
|
280
|
-
|
|
281
|
-
def set_invalidation_push_handler(self, invalidation_push_handler_func):
|
|
282
|
-
self.invalidation_push_handler_func = invalidation_push_handler_func
|