coredis 5.2.0__cp314-cp314-macosx_10_13_x86_64.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.
- 22fe76227e35f92ab5c3__mypyc.cpython-314-darwin.so +0 -0
- coredis/__init__.py +42 -0
- coredis/_enum.py +42 -0
- coredis/_json.py +11 -0
- coredis/_packer.cpython-314-darwin.so +0 -0
- coredis/_packer.py +71 -0
- coredis/_protocols.py +50 -0
- coredis/_py_311_typing.py +20 -0
- coredis/_py_312_typing.py +17 -0
- coredis/_sidecar.py +114 -0
- coredis/_utils.cpython-314-darwin.so +0 -0
- coredis/_utils.py +440 -0
- coredis/_version.py +34 -0
- coredis/_version.pyi +1 -0
- coredis/cache.py +801 -0
- coredis/client/__init__.py +6 -0
- coredis/client/basic.py +1238 -0
- coredis/client/cluster.py +1264 -0
- coredis/commands/__init__.py +64 -0
- coredis/commands/_key_spec.py +517 -0
- coredis/commands/_utils.py +108 -0
- coredis/commands/_validators.py +159 -0
- coredis/commands/_wrappers.py +175 -0
- coredis/commands/bitfield.py +110 -0
- coredis/commands/constants.py +662 -0
- coredis/commands/core.py +8484 -0
- coredis/commands/function.py +408 -0
- coredis/commands/monitor.py +168 -0
- coredis/commands/pubsub.py +905 -0
- coredis/commands/request.py +108 -0
- coredis/commands/script.py +296 -0
- coredis/commands/sentinel.py +246 -0
- coredis/config.py +50 -0
- coredis/connection.py +906 -0
- coredis/constants.cpython-314-darwin.so +0 -0
- coredis/constants.py +37 -0
- coredis/credentials.py +45 -0
- coredis/exceptions.py +360 -0
- coredis/experimental/__init__.py +1 -0
- coredis/globals.py +23 -0
- coredis/modules/__init__.py +117 -0
- coredis/modules/autocomplete.py +138 -0
- coredis/modules/base.py +262 -0
- coredis/modules/filters.py +1319 -0
- coredis/modules/graph.py +362 -0
- coredis/modules/json.py +691 -0
- coredis/modules/response/__init__.py +0 -0
- coredis/modules/response/_callbacks/__init__.py +0 -0
- coredis/modules/response/_callbacks/autocomplete.py +42 -0
- coredis/modules/response/_callbacks/graph.py +237 -0
- coredis/modules/response/_callbacks/json.py +21 -0
- coredis/modules/response/_callbacks/search.py +221 -0
- coredis/modules/response/_callbacks/timeseries.py +158 -0
- coredis/modules/response/types.py +179 -0
- coredis/modules/search.py +1089 -0
- coredis/modules/timeseries.py +1139 -0
- coredis/parser.cpython-314-darwin.so +0 -0
- coredis/parser.py +344 -0
- coredis/pipeline.py +1225 -0
- coredis/pool/__init__.py +11 -0
- coredis/pool/basic.py +453 -0
- coredis/pool/cluster.py +517 -0
- coredis/pool/nodemanager.py +340 -0
- coredis/py.typed +0 -0
- coredis/recipes/__init__.py +0 -0
- coredis/recipes/credentials/__init__.py +5 -0
- coredis/recipes/credentials/iam_provider.py +63 -0
- coredis/recipes/locks/__init__.py +5 -0
- coredis/recipes/locks/extend.lua +17 -0
- coredis/recipes/locks/lua_lock.py +281 -0
- coredis/recipes/locks/release.lua +10 -0
- coredis/response/__init__.py +5 -0
- coredis/response/_callbacks/__init__.py +538 -0
- coredis/response/_callbacks/acl.py +32 -0
- coredis/response/_callbacks/cluster.py +183 -0
- coredis/response/_callbacks/command.py +86 -0
- coredis/response/_callbacks/connection.py +31 -0
- coredis/response/_callbacks/geo.py +58 -0
- coredis/response/_callbacks/hash.py +85 -0
- coredis/response/_callbacks/keys.py +59 -0
- coredis/response/_callbacks/module.py +33 -0
- coredis/response/_callbacks/script.py +85 -0
- coredis/response/_callbacks/sentinel.py +179 -0
- coredis/response/_callbacks/server.py +241 -0
- coredis/response/_callbacks/sets.py +44 -0
- coredis/response/_callbacks/sorted_set.py +204 -0
- coredis/response/_callbacks/streams.py +185 -0
- coredis/response/_callbacks/strings.py +70 -0
- coredis/response/_callbacks/vector_sets.py +159 -0
- coredis/response/_utils.py +33 -0
- coredis/response/types.py +416 -0
- coredis/retry.py +233 -0
- coredis/sentinel.py +477 -0
- coredis/stream.py +369 -0
- coredis/tokens.py +2286 -0
- coredis/typing.py +580 -0
- coredis-5.2.0.dist-info/METADATA +211 -0
- coredis-5.2.0.dist-info/RECORD +100 -0
- coredis-5.2.0.dist-info/WHEEL +6 -0
- coredis-5.2.0.dist-info/licenses/LICENSE +23 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import time
|
|
5
|
+
import warnings
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
from coredis.commands.constants import CommandName
|
|
9
|
+
from coredis.config import Config
|
|
10
|
+
from coredis.exceptions import CommandNotSupportedError, CommandSyntaxError
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
import coredis.client
|
|
14
|
+
from coredis.commands._wrappers import CommandDetails
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def normalized_seconds(value: int | datetime.timedelta) -> int:
|
|
18
|
+
if isinstance(value, datetime.timedelta):
|
|
19
|
+
value = value.seconds + value.days * 24 * 3600
|
|
20
|
+
|
|
21
|
+
return value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def normalized_milliseconds(value: int | datetime.timedelta) -> int:
|
|
25
|
+
if isinstance(value, datetime.timedelta):
|
|
26
|
+
ms = int(value.microseconds / 1000)
|
|
27
|
+
value = (value.seconds + value.days * 24 * 3600) * 1000 + ms
|
|
28
|
+
|
|
29
|
+
return value
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def normalized_time_seconds(value: int | datetime.datetime) -> int:
|
|
33
|
+
if isinstance(value, datetime.datetime):
|
|
34
|
+
s = int(value.microsecond / 1000000)
|
|
35
|
+
value = int(time.mktime(value.timetuple())) + s
|
|
36
|
+
|
|
37
|
+
return value
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def normalized_time_milliseconds(value: int | datetime.datetime) -> int:
|
|
41
|
+
if isinstance(value, datetime.datetime):
|
|
42
|
+
ms = int(value.microsecond / 1000)
|
|
43
|
+
value = int(time.mktime(value.timetuple())) * 1000 + ms
|
|
44
|
+
|
|
45
|
+
return value
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def check_version(
|
|
49
|
+
instance: coredis.client.Client[Any],
|
|
50
|
+
function_name: str,
|
|
51
|
+
command_details: CommandDetails,
|
|
52
|
+
deprecation_reason: str | None = None,
|
|
53
|
+
kwargs: dict[str, Any] = {},
|
|
54
|
+
) -> None:
|
|
55
|
+
if Config.optimized or not any(
|
|
56
|
+
[
|
|
57
|
+
command_details.version_introduced,
|
|
58
|
+
command_details.version_deprecated,
|
|
59
|
+
command_details.arguments,
|
|
60
|
+
]
|
|
61
|
+
):
|
|
62
|
+
return
|
|
63
|
+
if getattr(instance, "verify_version", False) and not getattr(instance, "noreply", False):
|
|
64
|
+
server_version = getattr(instance, "server_version", None)
|
|
65
|
+
if not server_version:
|
|
66
|
+
return
|
|
67
|
+
if (
|
|
68
|
+
command_details.version_introduced
|
|
69
|
+
and server_version < command_details.version_introduced
|
|
70
|
+
):
|
|
71
|
+
raise CommandNotSupportedError(
|
|
72
|
+
command_details.command.decode("latin-1"),
|
|
73
|
+
str(instance.server_version),
|
|
74
|
+
)
|
|
75
|
+
elif command_details.arguments and set(command_details.arguments.keys()).intersection(
|
|
76
|
+
kwargs.keys()
|
|
77
|
+
):
|
|
78
|
+
for argument, minimum_version in [
|
|
79
|
+
(arg, ver) for (arg, ver) in command_details.arguments.items() if arg in kwargs
|
|
80
|
+
]:
|
|
81
|
+
if minimum_version and server_version < minimum_version:
|
|
82
|
+
raise CommandSyntaxError(
|
|
83
|
+
{argument},
|
|
84
|
+
(
|
|
85
|
+
f"{command_details.command.decode('latin-1')} with `{argument}` "
|
|
86
|
+
f"is not supported in redis version {server_version}"
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
elif (
|
|
90
|
+
command_details.version_deprecated
|
|
91
|
+
and server_version >= command_details.version_deprecated
|
|
92
|
+
):
|
|
93
|
+
warnings.warn(
|
|
94
|
+
(
|
|
95
|
+
deprecation_reason.strip()
|
|
96
|
+
if deprecation_reason
|
|
97
|
+
else (
|
|
98
|
+
f"{function_name}() is deprecated since redis version "
|
|
99
|
+
"{command_details.version_deprecated}."
|
|
100
|
+
)
|
|
101
|
+
),
|
|
102
|
+
category=DeprecationWarning,
|
|
103
|
+
stacklevel=3,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def redis_command_link(command: CommandName) -> str:
|
|
108
|
+
return f"`{str(command)} <https://redis.io/commands/{str(command).lower().replace(' ', '-')}>`_"
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import inspect
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from coredis.config import Config
|
|
8
|
+
from coredis.exceptions import CommandSyntaxError
|
|
9
|
+
from coredis.typing import (
|
|
10
|
+
Callable,
|
|
11
|
+
Iterable,
|
|
12
|
+
ParamSpec,
|
|
13
|
+
TypeVar,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
R = TypeVar("R")
|
|
17
|
+
P = ParamSpec("P")
|
|
18
|
+
|
|
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
|
+
|
|
28
|
+
class MutuallyExclusiveParametersError(CommandSyntaxError):
|
|
29
|
+
def __init__(self, arguments: set[str], details: str | None):
|
|
30
|
+
message = (
|
|
31
|
+
f"The [{','.join(arguments)}] parameters are mutually exclusive."
|
|
32
|
+
f"{' ' + details if details else ''}"
|
|
33
|
+
)
|
|
34
|
+
super().__init__(arguments, message)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MutuallyInclusiveParametersMissing(CommandSyntaxError):
|
|
38
|
+
def __init__(self, arguments: set[str], leaders: set[str], details: str | None):
|
|
39
|
+
if leaders:
|
|
40
|
+
message = (
|
|
41
|
+
f"The [{','.join(arguments)}] parameters(s)"
|
|
42
|
+
" must be provided together with [{','.join(leaders)}]."
|
|
43
|
+
f"{' ' + details if details else ''}"
|
|
44
|
+
)
|
|
45
|
+
else:
|
|
46
|
+
message = (
|
|
47
|
+
f"The [{','.join(arguments)}] parameters are mutually"
|
|
48
|
+
" inclusive and must be provided together."
|
|
49
|
+
f"{' ' + details if details else ''}"
|
|
50
|
+
)
|
|
51
|
+
super().__init__(arguments, message)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def mutually_exclusive_parameters(
|
|
55
|
+
*exclusive_params: str | Iterable[str],
|
|
56
|
+
details: str | None = None,
|
|
57
|
+
required: bool = False,
|
|
58
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
59
|
+
primary = {k for k in exclusive_params if isinstance(k, str)}
|
|
60
|
+
secondary = [k for k in set(exclusive_params) - primary]
|
|
61
|
+
|
|
62
|
+
def wrapper(
|
|
63
|
+
func: Callable[P, R],
|
|
64
|
+
) -> Callable[P, R]:
|
|
65
|
+
sig = inspect.signature(func)
|
|
66
|
+
|
|
67
|
+
@functools.wraps(func)
|
|
68
|
+
def wrapped(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
69
|
+
if not Config.optimized:
|
|
70
|
+
call_args = sig.bind_partial(*args, **kwargs)
|
|
71
|
+
params = {
|
|
72
|
+
k
|
|
73
|
+
for k in primary
|
|
74
|
+
if not call_args.arguments.get(k) == getattr(sig.parameters.get(k), "default")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if params:
|
|
78
|
+
for group in secondary:
|
|
79
|
+
for k in group:
|
|
80
|
+
if not call_args.arguments.get(k) == getattr(
|
|
81
|
+
sig.parameters.get(k), "default"
|
|
82
|
+
):
|
|
83
|
+
params.add(k)
|
|
84
|
+
|
|
85
|
+
break
|
|
86
|
+
|
|
87
|
+
if len(params) > 1:
|
|
88
|
+
raise MutuallyExclusiveParametersError(params, details)
|
|
89
|
+
if len(params) == 0 and required:
|
|
90
|
+
raise RequiredParameterError(primary, details)
|
|
91
|
+
|
|
92
|
+
return func(*args, **kwargs)
|
|
93
|
+
|
|
94
|
+
return wrapped if not Config.optimized else func
|
|
95
|
+
|
|
96
|
+
return wrapper
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def mutually_inclusive_parameters(
|
|
100
|
+
*inclusive_params: str,
|
|
101
|
+
leaders: Iterable[str] | None = None,
|
|
102
|
+
details: str | None = None,
|
|
103
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
104
|
+
_leaders = set(leaders or [])
|
|
105
|
+
_inclusive_params = set(inclusive_params)
|
|
106
|
+
|
|
107
|
+
def wrapper(
|
|
108
|
+
func: Callable[P, R],
|
|
109
|
+
) -> Callable[P, R]:
|
|
110
|
+
sig = inspect.signature(func)
|
|
111
|
+
|
|
112
|
+
@functools.wraps(func)
|
|
113
|
+
def wrapped(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
114
|
+
if not Config.optimized:
|
|
115
|
+
call_args = sig.bind_partial(*args, **kwargs)
|
|
116
|
+
params = {
|
|
117
|
+
k
|
|
118
|
+
for k in _inclusive_params | _leaders
|
|
119
|
+
if not call_args.arguments.get(k) == getattr(sig.parameters.get(k), "default")
|
|
120
|
+
}
|
|
121
|
+
if _leaders and _leaders & params != _leaders and len(params) > 0:
|
|
122
|
+
raise MutuallyInclusiveParametersMissing(_inclusive_params, _leaders, details)
|
|
123
|
+
elif not _leaders and params and len(params) != len(_inclusive_params):
|
|
124
|
+
raise MutuallyInclusiveParametersMissing(_inclusive_params, _leaders, details)
|
|
125
|
+
return func(*args, **kwargs)
|
|
126
|
+
|
|
127
|
+
return wrapped if not Config.optimized else func
|
|
128
|
+
|
|
129
|
+
return wrapper
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def ensure_iterable_valid(
|
|
133
|
+
argument: str,
|
|
134
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
135
|
+
def iterable_valid(value: Any) -> bool:
|
|
136
|
+
return isinstance(value, Iterable) and not isinstance(value, (str, bytes))
|
|
137
|
+
|
|
138
|
+
def wrapper(
|
|
139
|
+
func: Callable[P, R],
|
|
140
|
+
) -> Callable[P, R]:
|
|
141
|
+
sig = inspect.signature(func)
|
|
142
|
+
expected_type = sig.parameters[argument].annotation
|
|
143
|
+
|
|
144
|
+
@functools.wraps(func)
|
|
145
|
+
def wrapped(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
146
|
+
if Config.optimized:
|
|
147
|
+
return func(*args, **kwargs)
|
|
148
|
+
bound = sig.bind_partial(*args, **kwargs)
|
|
149
|
+
value = bound.arguments.get(argument)
|
|
150
|
+
if not iterable_valid(value):
|
|
151
|
+
raise TypeError(
|
|
152
|
+
f"{func.__name__} parameter {argument}={value!r} violates expected iterable of type {expected_type}"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
return func(*args, **kwargs)
|
|
156
|
+
|
|
157
|
+
return wrapped if not Config.optimized else func
|
|
158
|
+
|
|
159
|
+
return wrapper
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import functools
|
|
5
|
+
import textwrap
|
|
6
|
+
import warnings
|
|
7
|
+
|
|
8
|
+
from packaging import version
|
|
9
|
+
|
|
10
|
+
from coredis.commands._utils import check_version, redis_command_link
|
|
11
|
+
from coredis.commands.constants import CommandFlag, CommandGroup, CommandName, NodeFlag
|
|
12
|
+
from coredis.commands.request import CommandRequest
|
|
13
|
+
from coredis.globals import CACHEABLE_COMMANDS, COMMAND_FLAGS, READONLY_COMMANDS
|
|
14
|
+
from coredis.response._callbacks import ClusterMultiNodeCallback
|
|
15
|
+
from coredis.typing import (
|
|
16
|
+
Callable,
|
|
17
|
+
NamedTuple,
|
|
18
|
+
P,
|
|
19
|
+
R,
|
|
20
|
+
add_runtime_checks,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RedirectUsage(NamedTuple):
|
|
25
|
+
reason: str
|
|
26
|
+
warn: bool
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclasses.dataclass
|
|
30
|
+
class CommandDetails:
|
|
31
|
+
command: bytes
|
|
32
|
+
group: CommandGroup | None
|
|
33
|
+
version_introduced: version.Version | None
|
|
34
|
+
version_deprecated: version.Version | None
|
|
35
|
+
_arguments: dict[str, dict[str, str]] | None
|
|
36
|
+
cluster: ClusterCommandConfig
|
|
37
|
+
flags: set[CommandFlag]
|
|
38
|
+
redirect_usage: RedirectUsage | None
|
|
39
|
+
arguments: dict[str, version.Version] = dataclasses.field(
|
|
40
|
+
init=False, default_factory=lambda: {}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def __post_init__(self) -> None:
|
|
44
|
+
self.arguments = {
|
|
45
|
+
k: version.Version(v["version_introduced"])
|
|
46
|
+
for k, v in (self._arguments or {}).items()
|
|
47
|
+
if v.get("version_introduced")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclasses.dataclass
|
|
52
|
+
class ClusterCommandConfig:
|
|
53
|
+
enabled: bool = True
|
|
54
|
+
combine: ClusterMultiNodeCallback | None = None # type: ignore
|
|
55
|
+
route: NodeFlag | None = None
|
|
56
|
+
split: NodeFlag | None = None
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def multi_node(self) -> bool:
|
|
60
|
+
return (self.route or self.split) in [
|
|
61
|
+
NodeFlag.ALL,
|
|
62
|
+
NodeFlag.PRIMARIES,
|
|
63
|
+
NodeFlag.REPLICAS,
|
|
64
|
+
NodeFlag.SLOT_ID,
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def redis_command(
|
|
69
|
+
command_name: CommandName,
|
|
70
|
+
*,
|
|
71
|
+
group: CommandGroup | None = None,
|
|
72
|
+
version_introduced: str | None = None,
|
|
73
|
+
version_deprecated: str | None = None,
|
|
74
|
+
deprecation_reason: str | None = None,
|
|
75
|
+
redirect_usage: RedirectUsage | None = None,
|
|
76
|
+
arguments: dict[str, dict[str, str]] | None = None,
|
|
77
|
+
flags: set[CommandFlag] | None = None,
|
|
78
|
+
cluster: ClusterCommandConfig = ClusterCommandConfig(),
|
|
79
|
+
cacheable: bool | None = None,
|
|
80
|
+
) -> Callable[[Callable[P, CommandRequest[R]]], Callable[P, CommandRequest[R]]]:
|
|
81
|
+
readonly = False
|
|
82
|
+
if flags and CommandFlag.READONLY in flags:
|
|
83
|
+
READONLY_COMMANDS.add(command_name)
|
|
84
|
+
readonly = True
|
|
85
|
+
|
|
86
|
+
if not readonly and cacheable: # noqa
|
|
87
|
+
raise RuntimeError(f"Can't decorate non readonly command {command_name} with cache config")
|
|
88
|
+
if cacheable:
|
|
89
|
+
CACHEABLE_COMMANDS.add(command_name)
|
|
90
|
+
|
|
91
|
+
COMMAND_FLAGS[command_name] = flags or set()
|
|
92
|
+
|
|
93
|
+
command_details = CommandDetails(
|
|
94
|
+
command_name,
|
|
95
|
+
group,
|
|
96
|
+
version.Version(version_introduced) if version_introduced else None,
|
|
97
|
+
version.Version(version_deprecated) if version_deprecated else None,
|
|
98
|
+
arguments,
|
|
99
|
+
cluster or ClusterCommandConfig(),
|
|
100
|
+
flags or set(),
|
|
101
|
+
redirect_usage,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def wrapper(
|
|
105
|
+
func: Callable[P, CommandRequest[R]],
|
|
106
|
+
) -> Callable[P, CommandRequest[R]]:
|
|
107
|
+
runtime_checkable = add_runtime_checks(func)
|
|
108
|
+
|
|
109
|
+
@functools.wraps(func)
|
|
110
|
+
def wrapped(*args: P.args, **kwargs: P.kwargs) -> CommandRequest[R]:
|
|
111
|
+
from coredis import Redis, RedisCluster
|
|
112
|
+
|
|
113
|
+
is_regular_client = isinstance(args[0], (Redis, RedisCluster))
|
|
114
|
+
if redirect_usage and is_regular_client:
|
|
115
|
+
if redirect_usage.warn:
|
|
116
|
+
warnings.warn(redirect_usage.reason, UserWarning, stacklevel=2)
|
|
117
|
+
else:
|
|
118
|
+
raise NotImplementedError(redirect_usage.reason)
|
|
119
|
+
runtime_checking = not getattr(args[0], "noreply", None) and is_regular_client
|
|
120
|
+
check_version(
|
|
121
|
+
args[0], # type: ignore
|
|
122
|
+
func.__name__,
|
|
123
|
+
command_details,
|
|
124
|
+
deprecation_reason,
|
|
125
|
+
kwargs,
|
|
126
|
+
)
|
|
127
|
+
return (func if not runtime_checking else runtime_checkable)(*args, **kwargs)
|
|
128
|
+
|
|
129
|
+
wrapped.__doc__ = textwrap.dedent(wrapped.__doc__ or "")
|
|
130
|
+
if group:
|
|
131
|
+
wrapped.__doc__ = f"""
|
|
132
|
+
{wrapped.__doc__}
|
|
133
|
+
|
|
134
|
+
Redis command documentation: {redis_command_link(command_name)}
|
|
135
|
+
"""
|
|
136
|
+
if version_introduced or command_details.arguments:
|
|
137
|
+
wrapped.__doc__ += """
|
|
138
|
+
Compatibility:
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
if version_introduced:
|
|
142
|
+
wrapped.__doc__ += f"""
|
|
143
|
+
- New in :redis-version:`{version_introduced}`
|
|
144
|
+
"""
|
|
145
|
+
if version_deprecated and deprecation_reason:
|
|
146
|
+
wrapped.__doc__ += f"""
|
|
147
|
+
- Deprecated in :redis-version:`{version_deprecated}`
|
|
148
|
+
{deprecation_reason.strip()}
|
|
149
|
+
"""
|
|
150
|
+
elif version_deprecated:
|
|
151
|
+
wrapped.__doc__ += f"""
|
|
152
|
+
- Deprecated in :redis-version:`{version_deprecated}`
|
|
153
|
+
"""
|
|
154
|
+
if command_details.arguments:
|
|
155
|
+
for argument, min_version in command_details.arguments.items():
|
|
156
|
+
wrapped.__doc__ += f"""
|
|
157
|
+
- :paramref:`{argument}`: New in :redis-version:`{min_version}`
|
|
158
|
+
"""
|
|
159
|
+
if cacheable:
|
|
160
|
+
wrapped.__doc__ += """
|
|
161
|
+
.. hint:: Supports client side caching
|
|
162
|
+
"""
|
|
163
|
+
if redirect_usage:
|
|
164
|
+
if redirect_usage.warn:
|
|
165
|
+
preamble = f".. warning:: Using ``{func.__name__}`` directly is not recommended."
|
|
166
|
+
else:
|
|
167
|
+
preamble = f".. danger:: Using ``{func.__name__}`` directly is not supported."
|
|
168
|
+
wrapped.__doc__ += f"""
|
|
169
|
+
{preamble} {redirect_usage.reason}
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
setattr(wrapped, "__coredis_command", command_details)
|
|
173
|
+
return wrapped
|
|
174
|
+
|
|
175
|
+
return wrapper
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import enum
|
|
4
|
+
|
|
5
|
+
from coredis._protocols import AbstractExecutor
|
|
6
|
+
from coredis.commands.constants import CommandName
|
|
7
|
+
from coredis.commands.request import CommandRequest
|
|
8
|
+
from coredis.exceptions import ReadOnlyError
|
|
9
|
+
from coredis.response._callbacks import NoopCallback
|
|
10
|
+
from coredis.tokens import PrefixToken, PureToken
|
|
11
|
+
from coredis.typing import (
|
|
12
|
+
AnyStr,
|
|
13
|
+
CommandArgList,
|
|
14
|
+
Generic,
|
|
15
|
+
KeyT,
|
|
16
|
+
Literal,
|
|
17
|
+
ResponseType,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BitFieldSubCommand(bytes, enum.Enum):
|
|
22
|
+
SET = PrefixToken.SET
|
|
23
|
+
GET = PrefixToken.GET
|
|
24
|
+
INCRBY = PrefixToken.INCRBY
|
|
25
|
+
OVERFLOW = PrefixToken.OVERFLOW
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BitFieldOperation(Generic[AnyStr]):
|
|
29
|
+
"""
|
|
30
|
+
The command treats a Redis string as a array of bits,
|
|
31
|
+
and is capable of addressing specific integer fields
|
|
32
|
+
of varying bit widths and arbitrary non (necessary) aligned offset.
|
|
33
|
+
|
|
34
|
+
The supported types are up to 64 bits for signed integers,
|
|
35
|
+
and up to 63 bits for unsigned integers.
|
|
36
|
+
|
|
37
|
+
Offset can be num prefixed with `#` character or num directly.
|
|
38
|
+
|
|
39
|
+
Redis command documentation: `BITFIELD <https://redios.io/commands/bitfield>`__
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, redis_client: AbstractExecutor, key: KeyT, readonly: bool = False) -> None:
|
|
43
|
+
self._command = CommandName.BITFIELD if not readonly else CommandName.BITFIELD_RO
|
|
44
|
+
self._command_stack: CommandArgList = [key]
|
|
45
|
+
self.redis = redis_client
|
|
46
|
+
self.readonly = readonly
|
|
47
|
+
|
|
48
|
+
def __del__(self) -> None:
|
|
49
|
+
self._command_stack.clear()
|
|
50
|
+
|
|
51
|
+
def set(self, encoding: str, offset: int | str, value: int) -> BitFieldOperation[AnyStr]:
|
|
52
|
+
"""
|
|
53
|
+
Set the specified bit field and returns its old value.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
if self.readonly:
|
|
57
|
+
raise ReadOnlyError()
|
|
58
|
+
|
|
59
|
+
self._command_stack.extend([BitFieldSubCommand.SET, encoding, offset, value])
|
|
60
|
+
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
def get(self, encoding: str, offset: int | str) -> BitFieldOperation[AnyStr]:
|
|
64
|
+
"""
|
|
65
|
+
Returns the specified bit field.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
self._command_stack.extend([BitFieldSubCommand.GET, encoding, offset])
|
|
69
|
+
|
|
70
|
+
return self
|
|
71
|
+
|
|
72
|
+
def incrby(self, encoding: str, offset: int | str, increment: int) -> BitFieldOperation[AnyStr]:
|
|
73
|
+
"""
|
|
74
|
+
Increments or decrements (if a negative increment is given)
|
|
75
|
+
the specified bit field and returns the new value.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
if self.readonly:
|
|
79
|
+
raise ReadOnlyError()
|
|
80
|
+
|
|
81
|
+
self._command_stack.extend([BitFieldSubCommand.INCRBY, encoding, offset, increment])
|
|
82
|
+
|
|
83
|
+
return self
|
|
84
|
+
|
|
85
|
+
def overflow(
|
|
86
|
+
self,
|
|
87
|
+
behavior: Literal[PureToken.SAT, PureToken.WRAP, PureToken.FAIL] = PureToken.SAT,
|
|
88
|
+
) -> BitFieldOperation[AnyStr]:
|
|
89
|
+
"""
|
|
90
|
+
fine-tune the behavior of the increment or decrement overflow,
|
|
91
|
+
have no effect unless used before :meth:`incrby`
|
|
92
|
+
three :paramref:`behavior` types are available: ``WRAP|SAT|FAIL``
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
if self.readonly:
|
|
96
|
+
raise ReadOnlyError()
|
|
97
|
+
self._command_stack.extend([BitFieldSubCommand.OVERFLOW, behavior])
|
|
98
|
+
|
|
99
|
+
return self
|
|
100
|
+
|
|
101
|
+
def exc(self) -> CommandRequest[ResponseType]:
|
|
102
|
+
"""execute commands in command stack"""
|
|
103
|
+
|
|
104
|
+
return CommandRequest(
|
|
105
|
+
self.redis,
|
|
106
|
+
self._command,
|
|
107
|
+
*self._command_stack,
|
|
108
|
+
callback=NoopCallback(),
|
|
109
|
+
execution_parameters={"decode": False},
|
|
110
|
+
)
|