coredis 5.2.0__cp314-cp314-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.

Potentially problematic release.


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

Files changed (100) hide show
  1. 22fe76227e35f92ab5c3__mypyc.cpython-314-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-314-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-314-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 +1238 -0
  18. coredis/client/cluster.py +1264 -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-314-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 +117 -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-314-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 +580 -0
  97. coredis-5.2.0.dist-info/METADATA +211 -0
  98. coredis-5.2.0.dist-info/RECORD +100 -0
  99. coredis-5.2.0.dist-info/WHEEL +6 -0
  100. 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
+ )