coredis 5.5.0__cp313-cp313-macosx_11_0_arm64.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.
Files changed (100) hide show
  1. 22fe76227e35f92ab5c3__mypyc.cpython-313-darwin.so +0 -0
  2. coredis/__init__.py +42 -0
  3. coredis/_enum.py +42 -0
  4. coredis/_json.py +11 -0
  5. coredis/_packer.cpython-313-darwin.so +0 -0
  6. coredis/_packer.py +71 -0
  7. coredis/_protocols.py +50 -0
  8. coredis/_py_311_typing.py +20 -0
  9. coredis/_py_312_typing.py +17 -0
  10. coredis/_sidecar.py +114 -0
  11. coredis/_utils.cpython-313-darwin.so +0 -0
  12. coredis/_utils.py +440 -0
  13. coredis/_version.py +34 -0
  14. coredis/_version.pyi +1 -0
  15. coredis/cache.py +801 -0
  16. coredis/client/__init__.py +6 -0
  17. coredis/client/basic.py +1240 -0
  18. coredis/client/cluster.py +1265 -0
  19. coredis/commands/__init__.py +64 -0
  20. coredis/commands/_key_spec.py +517 -0
  21. coredis/commands/_utils.py +108 -0
  22. coredis/commands/_validators.py +159 -0
  23. coredis/commands/_wrappers.py +175 -0
  24. coredis/commands/bitfield.py +110 -0
  25. coredis/commands/constants.py +662 -0
  26. coredis/commands/core.py +8484 -0
  27. coredis/commands/function.py +408 -0
  28. coredis/commands/monitor.py +168 -0
  29. coredis/commands/pubsub.py +905 -0
  30. coredis/commands/request.py +108 -0
  31. coredis/commands/script.py +296 -0
  32. coredis/commands/sentinel.py +246 -0
  33. coredis/config.py +50 -0
  34. coredis/connection.py +906 -0
  35. coredis/constants.cpython-313-darwin.so +0 -0
  36. coredis/constants.py +37 -0
  37. coredis/credentials.py +45 -0
  38. coredis/exceptions.py +360 -0
  39. coredis/experimental/__init__.py +1 -0
  40. coredis/globals.py +23 -0
  41. coredis/modules/__init__.py +121 -0
  42. coredis/modules/autocomplete.py +138 -0
  43. coredis/modules/base.py +262 -0
  44. coredis/modules/filters.py +1319 -0
  45. coredis/modules/graph.py +362 -0
  46. coredis/modules/json.py +691 -0
  47. coredis/modules/response/__init__.py +0 -0
  48. coredis/modules/response/_callbacks/__init__.py +0 -0
  49. coredis/modules/response/_callbacks/autocomplete.py +42 -0
  50. coredis/modules/response/_callbacks/graph.py +237 -0
  51. coredis/modules/response/_callbacks/json.py +21 -0
  52. coredis/modules/response/_callbacks/search.py +221 -0
  53. coredis/modules/response/_callbacks/timeseries.py +158 -0
  54. coredis/modules/response/types.py +179 -0
  55. coredis/modules/search.py +1089 -0
  56. coredis/modules/timeseries.py +1139 -0
  57. coredis/parser.cpython-313-darwin.so +0 -0
  58. coredis/parser.py +344 -0
  59. coredis/pipeline.py +1225 -0
  60. coredis/pool/__init__.py +11 -0
  61. coredis/pool/basic.py +453 -0
  62. coredis/pool/cluster.py +517 -0
  63. coredis/pool/nodemanager.py +340 -0
  64. coredis/py.typed +0 -0
  65. coredis/recipes/__init__.py +0 -0
  66. coredis/recipes/credentials/__init__.py +5 -0
  67. coredis/recipes/credentials/iam_provider.py +63 -0
  68. coredis/recipes/locks/__init__.py +5 -0
  69. coredis/recipes/locks/extend.lua +17 -0
  70. coredis/recipes/locks/lua_lock.py +281 -0
  71. coredis/recipes/locks/release.lua +10 -0
  72. coredis/response/__init__.py +5 -0
  73. coredis/response/_callbacks/__init__.py +538 -0
  74. coredis/response/_callbacks/acl.py +32 -0
  75. coredis/response/_callbacks/cluster.py +183 -0
  76. coredis/response/_callbacks/command.py +86 -0
  77. coredis/response/_callbacks/connection.py +31 -0
  78. coredis/response/_callbacks/geo.py +58 -0
  79. coredis/response/_callbacks/hash.py +85 -0
  80. coredis/response/_callbacks/keys.py +59 -0
  81. coredis/response/_callbacks/module.py +33 -0
  82. coredis/response/_callbacks/script.py +85 -0
  83. coredis/response/_callbacks/sentinel.py +179 -0
  84. coredis/response/_callbacks/server.py +241 -0
  85. coredis/response/_callbacks/sets.py +44 -0
  86. coredis/response/_callbacks/sorted_set.py +204 -0
  87. coredis/response/_callbacks/streams.py +185 -0
  88. coredis/response/_callbacks/strings.py +70 -0
  89. coredis/response/_callbacks/vector_sets.py +159 -0
  90. coredis/response/_utils.py +33 -0
  91. coredis/response/types.py +416 -0
  92. coredis/retry.py +233 -0
  93. coredis/sentinel.py +477 -0
  94. coredis/stream.py +369 -0
  95. coredis/tokens.py +2286 -0
  96. coredis/typing.py +593 -0
  97. coredis-5.5.0.dist-info/METADATA +211 -0
  98. coredis-5.5.0.dist-info/RECORD +100 -0
  99. coredis-5.5.0.dist-info/WHEEL +6 -0
  100. coredis-5.5.0.dist-info/licenses/LICENSE +23 -0
@@ -0,0 +1,408 @@
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+ import inspect
5
+ import itertools
6
+ import weakref
7
+ from typing import Any, ClassVar, cast
8
+
9
+ from deprecated.sphinx import versionadded
10
+
11
+ from coredis._utils import EncodingInsensitiveDict, nativestr
12
+ from coredis.commands.request import CommandRequest
13
+ from coredis.exceptions import FunctionError
14
+ from coredis.typing import (
15
+ TYPE_CHECKING,
16
+ AnyStr,
17
+ Awaitable,
18
+ Callable,
19
+ Generator,
20
+ Generic,
21
+ KeyT,
22
+ MutableMapping,
23
+ P,
24
+ Parameters,
25
+ R,
26
+ ResponseType,
27
+ StringT,
28
+ TypeVar,
29
+ ValueT,
30
+ add_runtime_checks,
31
+ safe_beartype,
32
+ )
33
+
34
+ if TYPE_CHECKING:
35
+ import coredis.client
36
+
37
+ LibraryT = TypeVar("LibraryT", bound="Library[Any]")
38
+ LibraryStringT = TypeVar("LibraryStringT", bound="Library[str]")
39
+ LibraryBytesT = TypeVar("LibraryBytesT", bound="Library[bytes]")
40
+
41
+
42
+ class Library(Generic[AnyStr]):
43
+ #: Class variable equivalent of the :paramref:`Library.name` argument.
44
+ NAME: ClassVar[StringT | None] = None
45
+ #: Class variable equivalent of the :paramref:`Library.code` argument.
46
+ CODE: ClassVar[StringT | None] = None
47
+
48
+ def __init__(
49
+ self,
50
+ client: coredis.client.Client[AnyStr],
51
+ name: StringT | None = None,
52
+ code: StringT | None = None,
53
+ replace: bool = False,
54
+ ) -> None:
55
+ """
56
+ Abstraction over a library of redis functions
57
+
58
+ Example::
59
+
60
+ library_code = \"\"\"
61
+ #!lua name=coredis
62
+ redis.register_function('myfunc', function(k, a) return a[1] end)
63
+ \"\"\"
64
+ lib = await Library(client, "mylib", library_code)
65
+ assert "1" == await lib["myfunc"]([], [1])
66
+
67
+ When used as a base class the class variables :data:`NAME` and :data:`CODE`
68
+ can be set on the sub class to avoid having to implement a constructor. Constructor
69
+ parameters will take precedence over the class variables.
70
+
71
+ :param client: The coredis client instance to use when calling the functions
72
+ exposed by the library.
73
+ :param name: The name of the library (should match the name in the Shebang
74
+ in the library source).
75
+ :param code: The lua code representing the library
76
+ :param replace: Whether to replace the library when intializing. If ``False``
77
+ an exception will be raised if the library was already loaded in the target
78
+ redis instance.
79
+ """
80
+ self._client: weakref.ReferenceType[coredis.client.Client[AnyStr]] = weakref.ref(client)
81
+ self.name = nativestr(name or self.NAME)
82
+ self.code = (code or self.CODE or "").lstrip()
83
+ self._functions: EncodingInsensitiveDict = EncodingInsensitiveDict()
84
+ self.replace = replace
85
+ if self.replace and not self.code:
86
+ raise RuntimeError("library code must be provided when the ``replace`` option is used")
87
+
88
+ @property
89
+ def client(self) -> coredis.client.Client[AnyStr]:
90
+ c = self._client()
91
+ assert c
92
+ return c
93
+
94
+ @property
95
+ def functions(self) -> MutableMapping[str, Function[AnyStr]]:
96
+ """
97
+ mapping of function names to :class:`~coredis.commands.function.Function`
98
+ instances that can be directly called.
99
+ """
100
+ return self._functions
101
+
102
+ async def update(self, new_code: StringT) -> bool:
103
+ """
104
+ Update the code of a library with :paramref:`new_code`
105
+ """
106
+ self.code = new_code
107
+ if await self.initialize(replace=True):
108
+ return True
109
+ return False
110
+
111
+ async def initialize(self: LibraryT, replace: bool = False) -> LibraryT:
112
+ from coredis.pipeline import ClusterPipeline, Pipeline
113
+
114
+ self._functions.clear()
115
+ if isinstance(self.client, (Pipeline, ClusterPipeline)):
116
+ redis_client = self.client.client
117
+ else:
118
+ redis_client = self.client
119
+ library = (await redis_client.function_list(self.name)).get(self.name)
120
+ if (not library and self.code) or (replace or self.replace):
121
+ await redis_client.function_load(self.code, replace=replace or self.replace)
122
+ library = (await redis_client.function_list(self.name)).get(self.name)
123
+
124
+ if not library:
125
+ raise FunctionError(f"No library found for {self.name}")
126
+
127
+ for name, details in library["functions"].items():
128
+ self._functions[name] = Function[AnyStr](
129
+ self.client,
130
+ self.name,
131
+ name,
132
+ bool({b"no-writes", "no-writes"} & details["flags"]),
133
+ )
134
+ return self
135
+
136
+ def __await__(self: LibraryT) -> Generator[Any, None, LibraryT]:
137
+ return self.initialize().__await__()
138
+
139
+ def __getitem__(self, function: str) -> Function[AnyStr] | None:
140
+ return cast(Function[AnyStr] | None, self._functions.get(function))
141
+
142
+ @classmethod
143
+ @versionadded(version="3.5.0")
144
+ def wraps(
145
+ cls,
146
+ function_name: str,
147
+ key_spec: list[KeyT] | None = None,
148
+ param_is_key: Callable[[inspect.Parameter], bool] = lambda p: (
149
+ p.annotation in {"KeyT", KeyT}
150
+ ),
151
+ runtime_checks: bool = False,
152
+ readonly: bool | None = None,
153
+ ) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, CommandRequest[R]]]:
154
+ """
155
+ Decorator for wrapping methods of subclasses of :class:`Library`
156
+ as entry points to the functions contained in the library. This allows
157
+ exposing a strict signature instead of that which :meth:`Function.__call__`
158
+ provides. The callable being decorated should **not** have an implementation as
159
+ it will never be called.
160
+
161
+ The main objective of the decorator is to allow you to represent a lua library of
162
+ functions as a python class having strict (and type safe) methods as entry points.
163
+ Internally the decorator separates ``keys`` from ``args`` before calling
164
+ :meth:`coredis.Redis.fcall`.
165
+
166
+ Mapping the decorated method's arguments to key providers is done either by
167
+ using :paramref:`key_spec` or :paramref:`param_is_key`. All other parameters of the
168
+ decorated method are assumed to be ``args`` consumed by the lua function.
169
+
170
+
171
+ The following example demonstrates most of the functionality provided by the
172
+ decorator::
173
+
174
+ import coredis
175
+ from coredis.commands import Library
176
+ from coredis.typing import KeyT, RedisValueT
177
+ from typing import List
178
+
179
+ class MyAwesomeLibrary(Library):
180
+ NAME = "mylib"
181
+ CODE = \"\"\"
182
+ #!lua name=mylib
183
+
184
+ redis.register_function('echo', function(k, a)
185
+ return a[1]
186
+ end)
187
+ redis.register_function('ping', function()
188
+ return "PONG"
189
+ end)
190
+ redis.register_function('get', function(k, a)
191
+ return redis.call("GET", k[1])
192
+ end)
193
+ redis.register_function('hmget', function(k, a)
194
+ local values = {}
195
+ local fields = {}
196
+ local response = {}
197
+ local i = 1
198
+ local j = 1
199
+
200
+ while a[i] do
201
+ fields[j] = a[i]
202
+ i = i + 2
203
+ j = j + 1
204
+ end
205
+
206
+ for idx, key in ipairs(k) do
207
+ values = redis.call("HMGET", key, unpack(fields))
208
+ for idx, value in ipairs(values) do
209
+ if not response[idx] and value then
210
+ response[idx] = value
211
+ end
212
+ end
213
+ end
214
+ for idx, value in ipairs(fields) do
215
+ if not response[idx] then
216
+ response[idx] = a[idx*2]
217
+ end
218
+ end
219
+ return response
220
+ end)
221
+ \"\"\"
222
+
223
+ @Library.wraps("echo")
224
+ def echo(self, value: ValueT) -> CommandRequest[RedisValueT]: ...
225
+
226
+ @Library.wraps("ping"print(c)
227
+ )
228
+ def ping(self) -> CommandRequest[str]: ...
229
+
230
+ @Library.wraps("get")
231
+ def get(self, key: KeyT) -> CommandRequest[ValueT]: ...
232
+
233
+ @Library.wraps("hmmget")
234
+ def hmmget(self, *keys: KeyT, **fields_with_values: RedisValueT):
235
+ \"\"\"
236
+ Return values of ``fields_with_values`` on a first come first serve
237
+ basis from the hashes at ``keys``. Since ``fields_with_values`` is a mapping
238
+ the keys are mapped to hash fields and the values are used
239
+ as defaults if they are not found in any of the hashes at ``keys``
240
+ \"\"\"
241
+ ...
242
+
243
+ client = coredis.Redis()
244
+ lib = await MyAwesomeLibrary(client, replace=True)
245
+ await client.set("hello", "world")
246
+ # True
247
+ await lib.echo("hello world")
248
+ # b"hello world"
249
+ await lib.ping()
250
+ # b"pong"
251
+ await lib.get("hello")
252
+ # b"hello"
253
+ await client.hset("k1", {"c": 3, "d": 4})
254
+ await client.hset("k2", {"a": 1, "b": 2})
255
+ await lib.hmmget("k1", "k2", a=-1, b=-2, c=-3, d=-4, e=-5)
256
+ # [b"1", b"2", b"3", b"4", b"-5"]
257
+
258
+ :param key_spec: list of parameters of the decorated method that will
259
+ be passed as the :paramref:`keys` argument to :meth:`__call__`. If provided
260
+ this parameter takes precedence over using :paramref:`param_is_key` to
261
+ determine if a parameter is a key provider.
262
+ :param param_is_key: a callable that accepts a single argument of type
263
+ :class:`inspect.Parameter` and returns ``True`` if the parameter points to a key
264
+ that should be appended to the :paramref:`__call__.keys` argument of
265
+ :meth:`__call__`. The default implementation marks a parameter as a key
266
+ provider if it is of type :data:`coredis.typing.KeyT` and is only used
267
+ if :paramref:`key_spec` is ``None``.
268
+ :param runtime_checks: Whether to enable runtime type checking of input arguments
269
+ and return values. (requires :pypi:`beartype`). If :data:`False` the function will
270
+ still get runtime type checking if the environment configuration ``COREDIS_RUNTIME_CHECKS``
271
+ is set - for details see :ref:`handbook/typing:runtime type checking`.
272
+ :param readonly: If ``True`` forces this function to use :meth:`coredis.Redis.fcall_ro`
273
+
274
+ :return: A function that has a signature mirroring the decorated function.
275
+ """
276
+
277
+ def wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, CommandRequest[R]]:
278
+ sig = inspect.signature(func)
279
+ first_arg: str = list(sig.parameters.keys())[0]
280
+ runtime_check_wrapper = add_runtime_checks if not runtime_checks else safe_beartype
281
+ key_params = (
282
+ key_spec if key_spec else [n for n, p in sig.parameters.items() if param_is_key(p)]
283
+ )
284
+ arg_fetch: dict[str, Callable[..., Parameters[Any]]] = {
285
+ n: (
286
+ (lambda v: [v])
287
+ if p.kind
288
+ in {
289
+ inspect.Parameter.POSITIONAL_ONLY,
290
+ inspect.Parameter.KEYWORD_ONLY,
291
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
292
+ }
293
+ else (
294
+ (lambda v: list(itertools.chain.from_iterable(v.items())))
295
+ if p.kind == inspect.Parameter.VAR_KEYWORD
296
+ else lambda v: list(v)
297
+ )
298
+ )
299
+ for n, p in sig.parameters.items()
300
+ }
301
+
302
+ def split_args(
303
+ *a: P.args, **k: P.kwargs
304
+ ) -> tuple[Library[AnyStr], Parameters[KeyT], Parameters[ValueT]]:
305
+ bound_arguments = sig.bind(*a, **k)
306
+ bound_arguments.apply_defaults()
307
+ arguments: dict[str, Any] = bound_arguments.arguments
308
+ instance: Library[AnyStr] = arguments.pop(first_arg)
309
+ if not isinstance(instance, Library):
310
+ raise RuntimeError(
311
+ f"{instance.__class__.__name__} is not a subclass of"
312
+ " coredis.commands.function.Library therefore it's methods cannot be bound "
313
+ " to a redis library using ``Library.wrap``."
314
+ " Please refer to the documentation at https://coredis.readthedocs.org/"
315
+ " for instructions on how to bind a class to a redis library."
316
+ )
317
+ keys: list[KeyT] = []
318
+ args: list[ValueT] = []
319
+ for name in sig.parameters:
320
+ if name == first_arg:
321
+ continue
322
+ values = arg_fetch[name](arguments[name])
323
+ if name in key_params:
324
+ keys.extend(values)
325
+ else:
326
+ args.extend(values)
327
+ return instance, keys, args
328
+
329
+ @runtime_check_wrapper
330
+ @functools.wraps(func)
331
+ def _inner(*args: P.args, **kwargs: P.kwargs) -> CommandRequest[R]:
332
+ instance, keys, arguments = split_args(*args, **kwargs)
333
+ if (func := instance.functions.get(function_name, None)) is None:
334
+ raise AttributeError(
335
+ f"Library {instance.name} has no registered function {function_name}"
336
+ )
337
+ return cast(CommandRequest[R], func(keys, arguments, readonly=readonly))
338
+
339
+ return _inner
340
+
341
+ return wrapper
342
+
343
+
344
+ class Function(Generic[AnyStr]):
345
+ def __init__(
346
+ self,
347
+ client: coredis.client.Client[AnyStr],
348
+ library_name: StringT,
349
+ name: StringT,
350
+ readonly: bool = False,
351
+ ):
352
+ """
353
+ Wrapper to call a redis function that has already been loaded
354
+
355
+ :param library_name: Name of the library under which the function is registered
356
+ :param name: Name of the function this instance represents
357
+ :param readonly: If ``True`` the function will be called with
358
+ :meth:`coredis.Redis.fcall_ro` instead of :meth:`coredis.Redis.fcall`
359
+
360
+ Example::
361
+
362
+ func = await Function(client, "mylib", "myfunc")
363
+ response = await func(keys=["a"], args=[1])
364
+ """
365
+ self._client: weakref.ReferenceType[coredis.client.Client[AnyStr]] = weakref.ref(client)
366
+ self.library: Library[AnyStr] = Library[AnyStr](client, library_name)
367
+ self.name = name
368
+ self.readonly = readonly
369
+
370
+ @property
371
+ def client(self) -> coredis.client.Client[AnyStr]:
372
+ c = self._client()
373
+ assert c
374
+ return c
375
+
376
+ async def initialize(self) -> Function[AnyStr]:
377
+ await self.library
378
+ return self
379
+
380
+ def __await__(self) -> Generator[Any, None, Function[AnyStr]]:
381
+ return self.initialize().__await__()
382
+
383
+ def __call__(
384
+ self,
385
+ keys: Parameters[KeyT] | None = None,
386
+ args: Parameters[ValueT] | None = None,
387
+ *,
388
+ client: coredis.client.Client[AnyStr] | None = None,
389
+ readonly: bool | None = None,
390
+ ) -> CommandRequest[ResponseType]:
391
+ """
392
+ Wrapper to call :meth:`~coredis.Redis.fcall` with the
393
+ function named :paramref:`Function.name` registered under
394
+ the library at :paramref:`Function.library`
395
+
396
+ :param keys: The keys this function will reference
397
+ :param args: The arguments expected by the function
398
+ :param readonly: If ``True`` forces the function to use :meth:`coredis.Redis.fcall_ro`
399
+ """
400
+ if client is None:
401
+ client = self.client
402
+ if readonly is None:
403
+ readonly = self.readonly
404
+
405
+ if readonly:
406
+ return client.fcall_ro(self.name, keys or [], args or [])
407
+ else:
408
+ return client.fcall(self.name, keys or [], args or [])
@@ -0,0 +1,168 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from types import TracebackType
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from deprecated.sphinx import deprecated
8
+
9
+ from coredis.commands.constants import CommandName
10
+ from coredis.exceptions import ConnectionError, RedisError
11
+ from coredis.response.types import MonitorResult
12
+ from coredis.typing import AnyStr, Callable, Generator, Generic, Self, TypeVar
13
+
14
+ if TYPE_CHECKING:
15
+ import coredis.client
16
+ import coredis.connection
17
+
18
+ MonitorT = TypeVar("MonitorT", bound="Monitor[Any]")
19
+
20
+
21
+ @deprecated("The implementation of a monitor will be removed in 6.0", version="5.2.0")
22
+ class Monitor(Generic[AnyStr]):
23
+ """
24
+ Monitor is useful for handling the ``MONITOR`` command to the redis server.
25
+
26
+ It can be used as an infinite async iterator::
27
+
28
+ async with client.monitor() as monitor:
29
+ async for command in monitor:
30
+ print(command.time, command.client_type, command.command, command.args)
31
+
32
+ Alternatively, each command can be fetched explicitly::
33
+
34
+ monitor = client.monitor()
35
+ command1 = await monitor.get_command()
36
+ command2 = await monitor.get_command()
37
+ await monitor.aclose()
38
+
39
+ If you are only interested in triggering callbacks when a command is received
40
+ by the monitor::
41
+ def monitor_handler(result: MonitorResult) -> None:
42
+ ....
43
+
44
+ monitor = await client.monitor(response_handler=monitor_handler)
45
+ # when done
46
+ await monitor.aclose()
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ client: coredis.client.Client[AnyStr],
52
+ response_handler: Callable[[MonitorResult], None] | None = None,
53
+ ):
54
+ """
55
+ :param client: a Redis client
56
+ :param response_handler: optional callback to call whenever a
57
+ command is received by the monitor
58
+ """
59
+ self.client: coredis.client.Client[AnyStr] = client
60
+ self.encoding = client.encoding
61
+ self.connection: coredis.connection.Connection | None = None
62
+ self.monitoring = False
63
+ self._monitor_results: asyncio.Queue[MonitorResult] = asyncio.Queue()
64
+ self._monitor_task: asyncio.Task[None] | None = None
65
+ self._response_handler = response_handler
66
+
67
+ def __aiter__(self) -> Monitor[AnyStr]:
68
+ return self
69
+
70
+ async def __anext__(self) -> MonitorResult:
71
+ """
72
+ Infinite iterator that streams back the next command processed by the
73
+ monitored server.
74
+ """
75
+ return await self.get_command()
76
+
77
+ def __await__(self: MonitorT) -> Generator[Any, None, MonitorT]:
78
+ return self.__start_monitor().__await__()
79
+
80
+ async def __aenter__(self) -> Self:
81
+ await self.__start_monitor()
82
+ return self
83
+
84
+ async def __aexit__(
85
+ self,
86
+ exc_type: type[BaseException] | None,
87
+ exc_value: BaseException | None,
88
+ traceback: TracebackType | None,
89
+ ) -> None:
90
+ await self.aclose()
91
+
92
+ async def get_command(self) -> MonitorResult:
93
+ """
94
+ Wait for the next command issued and return the details
95
+ """
96
+ await self.__start_monitor()
97
+ return await self._monitor_results.get()
98
+
99
+ async def aclose(self) -> None:
100
+ """
101
+ Stop monitoring by issuing a ``RESET`` command
102
+ and release the connection.
103
+ """
104
+ return await self.__stop_monitoring()
105
+
106
+ @deprecated("Use :meth:`aclose` instead", version="4.21.0")
107
+ async def stop(self) -> None:
108
+ """
109
+ Stop monitoring by issuing a ``RESET`` command
110
+ and release the connection.
111
+ """
112
+ return await self.aclose()
113
+
114
+ async def __connect(self) -> None:
115
+ if self.connection is None:
116
+ self.connection = await self.client.connection_pool.get_connection()
117
+
118
+ async def __start_monitor(self: MonitorT) -> MonitorT:
119
+ if self.monitoring:
120
+ return self
121
+ await self.__connect()
122
+ assert self.connection
123
+ request = await self.connection.create_request(CommandName.MONITOR, decode=False)
124
+ response = await request
125
+ if not response == b"OK": # noqa
126
+ raise RedisError(f"Failed to start MONITOR {response!r}")
127
+ if not self._monitor_task or self._monitor_task.done():
128
+ self._monitor_task = asyncio.create_task(self._monitor())
129
+ self.monitoring = True
130
+ return self
131
+
132
+ async def __stop_monitoring(self) -> None:
133
+ if self.connection:
134
+ request = await self.connection.create_request(CommandName.RESET, decode=False)
135
+ response = await request
136
+ if not response == CommandName.RESET: # noqa
137
+ raise RedisError("Failed to reset connection")
138
+ self.__reset()
139
+
140
+ def __reset(self) -> None:
141
+ if self.connection:
142
+ self.connection.disconnect()
143
+ self.client.connection_pool.release(self.connection)
144
+ if self._monitor_task and not self._monitor_task.done():
145
+ try:
146
+ self._monitor_task.cancel()
147
+ except RuntimeError: # noqa
148
+ pass
149
+ self.monitoring = False
150
+ self.connection = None
151
+
152
+ async def _monitor(self) -> None:
153
+ while self.connection:
154
+ try:
155
+ response = await self.connection.fetch_push_message(block=True)
156
+ if isinstance(response, bytes):
157
+ response = response.decode(self.encoding)
158
+ assert isinstance(response, str)
159
+ result = MonitorResult.parse_response_string(response)
160
+ if self._response_handler:
161
+ self._response_handler(result)
162
+ else:
163
+ self._monitor_results.put_nowait(result)
164
+ except asyncio.CancelledError:
165
+ break
166
+ except ConnectionError:
167
+ break
168
+ self.__reset()