coredis 4.24.0__py3-none-any.whl → 5.0.0rc1__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.

Potentially problematic release.


This version of coredis might be problematic. Click here for more details.

Files changed (77) hide show
  1. coredis/__init__.py +1 -3
  2. coredis/_packer.py +10 -10
  3. coredis/_protocols.py +23 -32
  4. coredis/_py_311_typing.py +20 -0
  5. coredis/_py_312_typing.py +17 -0
  6. coredis/_utils.py +49 -51
  7. coredis/_version.py +3 -3
  8. coredis/cache.py +57 -82
  9. coredis/client/__init__.py +1 -2
  10. coredis/client/basic.py +129 -56
  11. coredis/client/cluster.py +147 -70
  12. coredis/commands/__init__.py +27 -7
  13. coredis/commands/_key_spec.py +11 -10
  14. coredis/commands/_utils.py +1 -1
  15. coredis/commands/_validators.py +30 -20
  16. coredis/commands/_wrappers.py +19 -99
  17. coredis/commands/bitfield.py +10 -2
  18. coredis/commands/constants.py +20 -3
  19. coredis/commands/core.py +1627 -1246
  20. coredis/commands/function.py +21 -19
  21. coredis/commands/monitor.py +0 -71
  22. coredis/commands/pubsub.py +7 -142
  23. coredis/commands/request.py +108 -0
  24. coredis/commands/script.py +9 -9
  25. coredis/commands/sentinel.py +60 -49
  26. coredis/connection.py +14 -15
  27. coredis/exceptions.py +2 -2
  28. coredis/experimental/__init__.py +0 -4
  29. coredis/globals.py +3 -0
  30. coredis/modules/autocomplete.py +28 -30
  31. coredis/modules/base.py +15 -31
  32. coredis/modules/filters.py +269 -245
  33. coredis/modules/graph.py +61 -62
  34. coredis/modules/json.py +172 -140
  35. coredis/modules/response/_callbacks/autocomplete.py +5 -4
  36. coredis/modules/response/_callbacks/graph.py +34 -29
  37. coredis/modules/response/_callbacks/json.py +5 -3
  38. coredis/modules/response/_callbacks/search.py +49 -53
  39. coredis/modules/response/_callbacks/timeseries.py +18 -30
  40. coredis/modules/response/types.py +1 -5
  41. coredis/modules/search.py +186 -169
  42. coredis/modules/timeseries.py +184 -164
  43. coredis/parser.py +6 -19
  44. coredis/pipeline.py +391 -422
  45. coredis/pool/basic.py +7 -7
  46. coredis/pool/cluster.py +3 -3
  47. coredis/pool/nodemanager.py +10 -3
  48. coredis/response/_callbacks/__init__.py +76 -57
  49. coredis/response/_callbacks/acl.py +0 -3
  50. coredis/response/_callbacks/cluster.py +25 -16
  51. coredis/response/_callbacks/command.py +8 -6
  52. coredis/response/_callbacks/connection.py +4 -3
  53. coredis/response/_callbacks/geo.py +17 -13
  54. coredis/response/_callbacks/hash.py +13 -11
  55. coredis/response/_callbacks/keys.py +9 -5
  56. coredis/response/_callbacks/module.py +2 -3
  57. coredis/response/_callbacks/script.py +6 -8
  58. coredis/response/_callbacks/sentinel.py +21 -17
  59. coredis/response/_callbacks/server.py +36 -14
  60. coredis/response/_callbacks/sets.py +3 -4
  61. coredis/response/_callbacks/sorted_set.py +27 -24
  62. coredis/response/_callbacks/streams.py +22 -13
  63. coredis/response/_callbacks/strings.py +7 -6
  64. coredis/response/_callbacks/vector_sets.py +126 -0
  65. coredis/response/types.py +13 -4
  66. coredis/sentinel.py +1 -1
  67. coredis/stream.py +4 -3
  68. coredis/tokens.py +343 -16
  69. coredis/typing.py +432 -79
  70. {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/METADATA +4 -5
  71. coredis-5.0.0rc1.dist-info/RECORD +95 -0
  72. coredis/client/keydb.py +0 -336
  73. coredis/pipeline.pyi +0 -2103
  74. coredis-4.24.0.dist-info/RECORD +0 -93
  75. {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/WHEEL +0 -0
  76. {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/licenses/LICENSE +0 -0
  77. {coredis-4.24.0.dist-info → coredis-5.0.0rc1.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,6 @@ from coredis.config import Config
8
8
  from coredis.exceptions import CommandSyntaxError
9
9
  from coredis.typing import (
10
10
  Callable,
11
- Coroutine,
12
11
  Iterable,
13
12
  ParamSpec,
14
13
  TypeVar,
@@ -18,6 +17,14 @@ R = TypeVar("R")
18
17
  P = ParamSpec("P")
19
18
 
20
19
 
20
+ class RequiredParameterError(CommandSyntaxError):
21
+ def __init__(self, arguments: set[str], details: str | None):
22
+ message = (
23
+ f"One of [{','.join(arguments)}] must be provided.{' ' + details if details else ''}"
24
+ )
25
+ super().__init__(arguments, message)
26
+
27
+
21
28
  class MutuallyExclusiveParametersError(CommandSyntaxError):
22
29
  def __init__(self, arguments: set[str], details: str | None):
23
30
  message = (
@@ -45,18 +52,20 @@ class MutuallyInclusiveParametersMissing(CommandSyntaxError):
45
52
 
46
53
 
47
54
  def mutually_exclusive_parameters(
48
- *exclusive_params: str | Iterable[str], details: str | None = None
49
- ) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]]:
55
+ *exclusive_params: str | Iterable[str],
56
+ details: str | None = None,
57
+ required: bool = False,
58
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]:
50
59
  primary = {k for k in exclusive_params if isinstance(k, str)}
51
60
  secondary = [k for k in set(exclusive_params) - primary]
52
61
 
53
62
  def wrapper(
54
- func: Callable[P, Coroutine[Any, Any, R]],
55
- ) -> Callable[P, Coroutine[Any, Any, R]]:
63
+ func: Callable[P, R],
64
+ ) -> Callable[P, R]:
56
65
  sig = inspect.signature(func)
57
66
 
58
67
  @functools.wraps(func)
59
- async def wrapped(*args: P.args, **kwargs: P.kwargs) -> R:
68
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> R:
60
69
  if not Config.optimized:
61
70
  call_args = sig.bind_partial(*args, **kwargs)
62
71
  params = {
@@ -77,8 +86,10 @@ def mutually_exclusive_parameters(
77
86
 
78
87
  if len(params) > 1:
79
88
  raise MutuallyExclusiveParametersError(params, details)
89
+ if len(params) == 0 and required:
90
+ raise RequiredParameterError(primary, details)
80
91
 
81
- return await func(*args, **kwargs)
92
+ return func(*args, **kwargs)
82
93
 
83
94
  return wrapped if not Config.optimized else func
84
95
 
@@ -89,17 +100,17 @@ def mutually_inclusive_parameters(
89
100
  *inclusive_params: str,
90
101
  leaders: Iterable[str] | None = None,
91
102
  details: str | None = None,
92
- ) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]]:
103
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]:
93
104
  _leaders = set(leaders or [])
94
105
  _inclusive_params = set(inclusive_params)
95
106
 
96
107
  def wrapper(
97
- func: Callable[P, Coroutine[Any, Any, R]],
98
- ) -> Callable[P, Coroutine[Any, Any, R]]:
108
+ func: Callable[P, R],
109
+ ) -> Callable[P, R]:
99
110
  sig = inspect.signature(func)
100
111
 
101
112
  @functools.wraps(func)
102
- async def wrapped(*args: P.args, **kwargs: P.kwargs) -> R:
113
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> R:
103
114
  if not Config.optimized:
104
115
  call_args = sig.bind_partial(*args, **kwargs)
105
116
  params = {
@@ -111,7 +122,7 @@ def mutually_inclusive_parameters(
111
122
  raise MutuallyInclusiveParametersMissing(_inclusive_params, _leaders, details)
112
123
  elif not _leaders and params and len(params) != len(_inclusive_params):
113
124
  raise MutuallyInclusiveParametersMissing(_inclusive_params, _leaders, details)
114
- return await func(*args, **kwargs)
125
+ return func(*args, **kwargs)
115
126
 
116
127
  return wrapped if not Config.optimized else func
117
128
 
@@ -120,29 +131,28 @@ def mutually_inclusive_parameters(
120
131
 
121
132
  def ensure_iterable_valid(
122
133
  argument: str,
123
- ) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]]:
134
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]:
124
135
  def iterable_valid(value: Any) -> bool:
125
136
  return isinstance(value, Iterable) and not isinstance(value, (str, bytes))
126
137
 
127
138
  def wrapper(
128
- func: Callable[P, Coroutine[Any, Any, R]],
129
- ) -> Callable[P, Coroutine[Any, Any, R]]:
139
+ func: Callable[P, R],
140
+ ) -> Callable[P, R]:
130
141
  sig = inspect.signature(func)
131
142
  expected_type = sig.parameters[argument].annotation
132
143
 
133
144
  @functools.wraps(func)
134
- async def wrapped(*args: P.args, **kwargs: P.kwargs) -> R:
145
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> R:
135
146
  if Config.optimized:
136
- return await func(*args, **kwargs)
147
+ return func(*args, **kwargs)
137
148
  bound = sig.bind_partial(*args, **kwargs)
138
149
  value = bound.arguments.get(argument)
139
150
  if not iterable_valid(value):
140
151
  raise TypeError(
141
- f"{func.__name__} parameter {argument}={value!r} "
142
- f"violates expected iterable of type {expected_type}"
152
+ f"{func.__name__} parameter {argument}={value!r} violates expected iterable of type {expected_type}"
143
153
  )
144
154
 
145
- return await func(*args, **kwargs)
155
+ return func(*args, **kwargs)
146
156
 
147
157
  return wrapped if not Config.optimized else func
148
158
 
@@ -1,39 +1,25 @@
1
1
  from __future__ import annotations
2
2
 
3
- import contextlib
4
3
  import dataclasses
5
4
  import functools
6
- import random
7
5
  import textwrap
8
6
  import warnings
9
- from typing import TYPE_CHECKING, Any, cast
10
7
 
11
8
  from packaging import version
12
9
 
13
- from coredis.cache import AbstractCache, SupportsSampling
14
10
  from coredis.commands._utils import check_version, redis_command_link
15
11
  from coredis.commands.constants import CommandFlag, CommandGroup, CommandName, NodeFlag
16
- from coredis.globals import COMMAND_FLAGS, READONLY_COMMANDS
12
+ from coredis.commands.request import CommandRequest
13
+ from coredis.globals import CACHEABLE_COMMANDS, COMMAND_FLAGS, READONLY_COMMANDS
17
14
  from coredis.response._callbacks import ClusterMultiNodeCallback
18
15
  from coredis.typing import (
19
- AsyncIterator,
20
16
  Callable,
21
- Coroutine,
22
17
  NamedTuple,
23
18
  P,
24
19
  R,
25
- ResponseType,
26
20
  add_runtime_checks,
27
21
  )
28
22
 
29
- if TYPE_CHECKING:
30
- pass
31
-
32
-
33
- @dataclasses.dataclass
34
- class CacheConfig:
35
- key_func: Callable[..., bytes]
36
-
37
23
 
38
24
  class RedirectUsage(NamedTuple):
39
25
  reason: str
@@ -48,7 +34,6 @@ class CommandDetails:
48
34
  version_deprecated: version.Version | None
49
35
  _arguments: dict[str, dict[str, str]] | None
50
36
  cluster: ClusterCommandConfig
51
- cache_config: CacheConfig | None
52
37
  flags: set[CommandFlag]
53
38
  redirect_usage: RedirectUsage | None
54
39
  arguments: dict[str, version.Version] = dataclasses.field(
@@ -76,75 +61,13 @@ class ClusterCommandConfig:
76
61
  NodeFlag.ALL,
77
62
  NodeFlag.PRIMARIES,
78
63
  NodeFlag.REPLICAS,
64
+ NodeFlag.SLOT_ID,
79
65
  ]
80
66
 
81
67
 
82
- @dataclasses.dataclass
83
- class CommandCache:
84
- command: bytes
85
- cache_config: CacheConfig | None
86
-
87
- @contextlib.asynccontextmanager
88
- async def __call__(
89
- self,
90
- func: Callable[P, Coroutine[Any, Any, R]],
91
- *args: P.args,
92
- **kwargs: P.kwargs,
93
- ) -> AsyncIterator[R]:
94
- from coredis.modules.base import ModuleGroup
95
-
96
- client = args[0]
97
- if isinstance(args[0], ModuleGroup):
98
- client = args[0].client
99
-
100
- cache = getattr(client, "cache")
101
- noreply = getattr(client, "noreply")
102
- if not (self.cache_config and cache) or noreply:
103
- yield await func(*args, **kwargs)
104
- else:
105
- assert isinstance(cache, AbstractCache)
106
- if not cache.healthy:
107
- yield await func(*args, **kwargs)
108
- else:
109
- key = self.cache_config.key_func(*args[1:], **kwargs)
110
- try:
111
- cached = cast(
112
- R,
113
- cache.get(
114
- self.command,
115
- key,
116
- *args[1:], # type: ignore
117
- *kwargs.items(), # type: ignore
118
- ),
119
- )
120
- if isinstance(cache, SupportsSampling) and not random.random() * 100.0 < min(
121
- 100.0, cache.confidence
122
- ):
123
- actual = await func(*args, **kwargs)
124
- cache.feedback(
125
- self.command,
126
- key,
127
- *args[1:], # type: ignore
128
- *kwargs.items(), # type: ignore
129
- match=(actual == cached),
130
- )
131
- yield actual
132
- else:
133
- yield cached
134
- except KeyError:
135
- response = await func(*args, **kwargs)
136
- cache.put(
137
- self.command,
138
- key,
139
- *args[1:], # type: ignore
140
- *kwargs.items(), # type: ignore
141
- value=cast(ResponseType, response),
142
- )
143
- yield response
144
-
145
-
146
68
  def redis_command(
147
69
  command_name: CommandName,
70
+ *,
148
71
  group: CommandGroup | None = None,
149
72
  version_introduced: str | None = None,
150
73
  version_deprecated: str | None = None,
@@ -153,15 +76,17 @@ def redis_command(
153
76
  arguments: dict[str, dict[str, str]] | None = None,
154
77
  flags: set[CommandFlag] | None = None,
155
78
  cluster: ClusterCommandConfig = ClusterCommandConfig(),
156
- cache_config: CacheConfig | None = None,
157
- ) -> Callable[[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]]:
79
+ cacheable: bool | None = None,
80
+ ) -> Callable[[Callable[P, CommandRequest[R]]], Callable[P, CommandRequest[R]]]:
158
81
  readonly = False
159
82
  if flags and CommandFlag.READONLY in flags:
160
83
  READONLY_COMMANDS.add(command_name)
161
84
  readonly = True
162
85
 
163
- if not readonly and cache_config: # noqa
86
+ if not readonly and cacheable: # noqa
164
87
  raise RuntimeError(f"Can't decorate non readonly command {command_name} with cache config")
88
+ if cacheable:
89
+ CACHEABLE_COMMANDS.add(command_name)
165
90
 
166
91
  COMMAND_FLAGS[command_name] = flags or set()
167
92
 
@@ -172,39 +97,34 @@ def redis_command(
172
97
  version.Version(version_deprecated) if version_deprecated else None,
173
98
  arguments,
174
99
  cluster or ClusterCommandConfig(),
175
- cache_config,
176
100
  flags or set(),
177
101
  redirect_usage,
178
102
  )
179
103
 
180
104
  def wrapper(
181
- func: Callable[P, Coroutine[Any, Any, R]],
182
- ) -> Callable[P, Coroutine[Any, Any, R]]:
183
- command_cache = CommandCache(command_name, cache_config)
105
+ func: Callable[P, CommandRequest[R]],
106
+ ) -> Callable[P, CommandRequest[R]]:
184
107
  runtime_checkable = add_runtime_checks(func)
185
108
 
186
109
  @functools.wraps(func)
187
- async def wrapped(*args: P.args, **kwargs: P.kwargs) -> R:
188
- from coredis.client import Redis, RedisCluster
110
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> CommandRequest[R]:
111
+ from coredis import Redis, RedisCluster
189
112
 
190
- client = args[0]
191
- is_regular_client = isinstance(client, (Redis, RedisCluster))
192
- runtime_checking = not getattr(client, "noreply", None) and is_regular_client
113
+ is_regular_client = isinstance(args[0], (Redis, RedisCluster))
193
114
  if redirect_usage and is_regular_client:
194
115
  if redirect_usage.warn:
195
116
  warnings.warn(redirect_usage.reason, UserWarning, stacklevel=2)
196
117
  else:
197
118
  raise NotImplementedError(redirect_usage.reason)
198
- callable = runtime_checkable if runtime_checking else func
199
- await check_version(
200
- client, # type: ignore
119
+ runtime_checking = not getattr(args[0], "noreply", None) and is_regular_client
120
+ check_version(
121
+ args[0], # type: ignore
201
122
  func.__name__,
202
123
  command_details,
203
124
  deprecation_reason,
204
125
  kwargs,
205
126
  )
206
- async with command_cache(callable, *args, **kwargs) as response:
207
- return response
127
+ return (func if not runtime_checking else runtime_checkable)(*args, **kwargs)
208
128
 
209
129
  wrapped.__doc__ = textwrap.dedent(wrapped.__doc__ or "")
210
130
  if group:
@@ -236,7 +156,7 @@ Compatibility:
236
156
  wrapped.__doc__ += f"""
237
157
  - :paramref:`{argument}`: New in :redis-version:`{min_version}`
238
158
  """
239
- if cache_config:
159
+ if cacheable:
240
160
  wrapped.__doc__ += """
241
161
  .. hint:: Supports client side caching
242
162
  """
@@ -4,7 +4,9 @@ import enum
4
4
 
5
5
  from coredis._protocols import AbstractExecutor
6
6
  from coredis.commands.constants import CommandName
7
+ from coredis.commands.request import CommandRequest
7
8
  from coredis.exceptions import ReadOnlyError
9
+ from coredis.response._callbacks import NoopCallback
8
10
  from coredis.tokens import PrefixToken, PureToken
9
11
  from coredis.typing import (
10
12
  AnyStr,
@@ -96,7 +98,13 @@ class BitFieldOperation(Generic[AnyStr]):
96
98
 
97
99
  return self
98
100
 
99
- async def exc(self) -> ResponseType:
101
+ def exc(self) -> CommandRequest[ResponseType]:
100
102
  """execute commands in command stack"""
101
103
 
102
- return await self.redis.execute_command(self._command, *self._command_stack, decode=False)
104
+ return CommandRequest(
105
+ self.redis,
106
+ self._command,
107
+ *self._command_stack,
108
+ callback=NoopCallback(),
109
+ execution_parameters={"decode": False},
110
+ )
@@ -351,6 +351,9 @@ class CommandName(CaseAndEncodingInsensitiveEnum):
351
351
  HPEXPIRETIME = b"HPEXPIRETIME" # Since redis: 7.4.0
352
352
  HPTTL = b"HPTTL" # Since redis: 7.4.0
353
353
  HTTL = b"HTTL" # Since redis: 7.4.0
354
+ HGETDEL = b"HGETDEL" # Since redis: 8.0.0
355
+ HGETEX = b"HGETEX" # Since redis: 8.0.0
356
+ HSETEX = b"HSETEX" # Since redis: 8.0.0
354
357
  HMSET = b"HMSET" # Deprecated in redis: 4.0.0
355
358
 
356
359
  #: Commands for hyperloglog
@@ -420,6 +423,19 @@ class CommandName(CaseAndEncodingInsensitiveEnum):
420
423
  XAUTOCLAIM = b"XAUTOCLAIM" # Since redis: 6.2.0
421
424
  XGROUP_CREATECONSUMER = b"XGROUP CREATECONSUMER" # Since redis: 6.2.0
422
425
 
426
+ #: Commands for vector_set
427
+ VADD = b"VADD" # Since redis: 8.0.0
428
+ VREM = b"VREM" # Since redis: 8.0.0
429
+ VSIM = b"VSIM" # Since redis: 8.0.0
430
+ VDIM = b"VDIM" # Since redis: 8.0.0
431
+ VCARD = b"VCARD" # Since redis: 8.0.0
432
+ VEMB = b"VEMB" # Since redis: 8.0.0
433
+ VLINKS = b"VLINKS" # Since redis: 8.0.0
434
+ VINFO = b"VINFO" # Since redis: 8.0.0
435
+ VSETATTR = b"VSETATTR" # Since redis: 8.0.0
436
+ VGETATTR = b"VGETATTR" # Since redis: 8.0.0
437
+ VRANDMEMBER = b"VRANDMEMBER" # Since redis: 8.0.0
438
+
423
439
  #: Commands for json
424
440
  JSON_DEL = b"JSON.DEL" # Since RedisJSON: 1.0.0
425
441
  JSON_FORGET = b"JSON.FORGET" # Since RedisJSON: 1.0.0
@@ -549,9 +565,6 @@ class CommandName(CaseAndEncodingInsensitiveEnum):
549
565
  FT_ALIASUPDATE = b"FT.ALIASUPDATE" # Since search: 1.0.0
550
566
  FT_ALIASDEL = b"FT.ALIASDEL" # Since search: 1.0.0
551
567
  FT_TAGVALS = b"FT.TAGVALS" # Since search: 1.0.0
552
- FT_CONFIG_SET = b"FT.CONFIG SET" # Since search: 1.0.0
553
- FT_CONFIG_GET = b"FT.CONFIG GET" # Since search: 1.0.0
554
- FT_CONFIG_HELP = b"FT.CONFIG HELP" # Since search: 1.0.0
555
568
  FT_SEARCH = b"FT.SEARCH" # Since search: 1.0.0
556
569
  FT_AGGREGATE = b"FT.AGGREGATE" # Since search: 1.1.0
557
570
  FT_CURSOR_READ = b"FT.CURSOR READ" # Since search: 1.1.0
@@ -565,6 +578,9 @@ class CommandName(CaseAndEncodingInsensitiveEnum):
565
578
  FT_DROPINDEX = b"FT.DROPINDEX" # Since search: 2.0.0
566
579
  FT__LIST = b"FT._LIST" # Since search: 2.0.0
567
580
  FT_PROFILE = b"FT.PROFILE" # Since search: 2.2.0
581
+ FT_CONFIG_SET = b"FT.CONFIG SET" # Deprecated in search: 8.0.0
582
+ FT_CONFIG_GET = b"FT.CONFIG GET" # Deprecated in search: 8.0.0
583
+ FT_CONFIG_HELP = b"FT.CONFIG HELP" # Deprecated in search: 8.0.0
568
584
 
569
585
  #: Commands for suggestion
570
586
  FT_SUGADD = b"FT.SUGADD" # Since search: 1.0.0
@@ -624,6 +640,7 @@ class CommandGroup(enum.Enum):
624
640
  TIMESERIES = "timeseries"
625
641
  TOPK = "topk"
626
642
  TRANSACTIONS = "transactions"
643
+ VECTOR_SET = "vector_set"
627
644
 
628
645
 
629
646
  class NodeFlag(enum.Enum):