valkey-glide 2.2.1rc1__cp313-cp313-macosx_10_7_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 valkey-glide might be problematic. Click here for more details.
- glide/__init__.py +388 -0
- glide/async_commands/__init__.py +5 -0
- glide/async_commands/cluster_commands.py +1476 -0
- glide/async_commands/core.py +7818 -0
- glide/async_commands/ft.py +465 -0
- glide/async_commands/glide_json.py +1269 -0
- glide/async_commands/standalone_commands.py +1001 -0
- glide/glide.cpython-313-darwin.so +0 -0
- glide/glide.pyi +61 -0
- glide/glide_client.py +821 -0
- glide/logger.py +97 -0
- glide/opentelemetry.py +185 -0
- glide/py.typed +0 -0
- glide_shared/__init__.py +330 -0
- glide_shared/commands/__init__.py +0 -0
- glide_shared/commands/batch.py +5997 -0
- glide_shared/commands/batch_options.py +261 -0
- glide_shared/commands/bitmap.py +320 -0
- glide_shared/commands/command_args.py +103 -0
- glide_shared/commands/core_options.py +407 -0
- glide_shared/commands/server_modules/ft_options/ft_aggregate_options.py +300 -0
- glide_shared/commands/server_modules/ft_options/ft_constants.py +84 -0
- glide_shared/commands/server_modules/ft_options/ft_create_options.py +423 -0
- glide_shared/commands/server_modules/ft_options/ft_profile_options.py +113 -0
- glide_shared/commands/server_modules/ft_options/ft_search_options.py +139 -0
- glide_shared/commands/server_modules/json_batch.py +820 -0
- glide_shared/commands/server_modules/json_options.py +93 -0
- glide_shared/commands/sorted_set.py +412 -0
- glide_shared/commands/stream.py +449 -0
- glide_shared/config.py +975 -0
- glide_shared/constants.py +124 -0
- glide_shared/exceptions.py +88 -0
- glide_shared/protobuf/command_request_pb2.py +56 -0
- glide_shared/protobuf/connection_request_pb2.py +56 -0
- glide_shared/protobuf/response_pb2.py +32 -0
- glide_shared/protobuf_codec.py +110 -0
- glide_shared/routes.py +161 -0
- valkey_glide-2.2.1rc1.dist-info/METADATA +210 -0
- valkey_glide-2.2.1rc1.dist-info/RECORD +40 -0
- valkey_glide-2.2.1rc1.dist-info/WHEEL +4 -0
glide_shared/config.py
ADDED
|
@@ -0,0 +1,975 @@
|
|
|
1
|
+
# Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from enum import Enum, IntEnum
|
|
7
|
+
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
|
|
8
|
+
|
|
9
|
+
from glide_shared.commands.core_options import PubSubMsg
|
|
10
|
+
from glide_shared.exceptions import ConfigurationError
|
|
11
|
+
from glide_shared.protobuf.connection_request_pb2 import (
|
|
12
|
+
ConnectionRequest,
|
|
13
|
+
)
|
|
14
|
+
from glide_shared.protobuf.connection_request_pb2 import (
|
|
15
|
+
ProtocolVersion as SentProtocolVersion,
|
|
16
|
+
)
|
|
17
|
+
from glide_shared.protobuf.connection_request_pb2 import ReadFrom as ProtobufReadFrom
|
|
18
|
+
from glide_shared.protobuf.connection_request_pb2 import (
|
|
19
|
+
TlsMode,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class NodeAddress:
|
|
24
|
+
"""
|
|
25
|
+
Represents the address and port of a node in the cluster.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
host (str, optional): The server host. Defaults to "localhost".
|
|
29
|
+
port (int, optional): The server port. Defaults to 6379.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, host: str = "localhost", port: int = 6379):
|
|
33
|
+
self.host = host
|
|
34
|
+
self.port = port
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ReadFrom(Enum):
|
|
38
|
+
"""
|
|
39
|
+
Represents the client's read from strategy.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
PRIMARY = ProtobufReadFrom.Primary
|
|
43
|
+
"""
|
|
44
|
+
Always get from primary, in order to get the freshest data.
|
|
45
|
+
"""
|
|
46
|
+
PREFER_REPLICA = ProtobufReadFrom.PreferReplica
|
|
47
|
+
"""
|
|
48
|
+
Spread the requests between all replicas in a round robin manner.
|
|
49
|
+
If no replica is available, route the requests to the primary.
|
|
50
|
+
"""
|
|
51
|
+
AZ_AFFINITY = ProtobufReadFrom.AZAffinity
|
|
52
|
+
"""
|
|
53
|
+
Spread the read requests between replicas in the same client's AZ (Aviliablity zone) in a round robin manner,
|
|
54
|
+
falling back to other replicas or the primary if needed
|
|
55
|
+
"""
|
|
56
|
+
AZ_AFFINITY_REPLICAS_AND_PRIMARY = ProtobufReadFrom.AZAffinityReplicasAndPrimary
|
|
57
|
+
"""
|
|
58
|
+
Spread the read requests among nodes within the client's Availability Zone (AZ) in a round robin manner,
|
|
59
|
+
prioritizing local replicas, then the local primary, and falling back to any replica or the primary if needed.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ProtocolVersion(Enum):
|
|
64
|
+
"""
|
|
65
|
+
Represents the communication protocol with the server.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
RESP2 = SentProtocolVersion.RESP2
|
|
69
|
+
"""
|
|
70
|
+
Communicate using RESP2.
|
|
71
|
+
"""
|
|
72
|
+
RESP3 = SentProtocolVersion.RESP3
|
|
73
|
+
"""
|
|
74
|
+
Communicate using RESP3.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class BackoffStrategy:
|
|
79
|
+
"""
|
|
80
|
+
Represents the strategy used to determine how and when to reconnect, in case of connection failures.
|
|
81
|
+
The time between attempts grows exponentially, to the formula rand(0 .. factor * (exponentBase ^ N)), where N
|
|
82
|
+
is the number of failed attempts, and rand(...) applies a jitter of up to jitter_percent% to introduce randomness and reduce retry storms.
|
|
83
|
+
Once the maximum value is reached, that will remain the time between retry attempts until a reconnect attempt is
|
|
84
|
+
successful.
|
|
85
|
+
The client will attempt to reconnect indefinitely.
|
|
86
|
+
|
|
87
|
+
Attributes:
|
|
88
|
+
num_of_retries (int): Number of retry attempts that the client should perform when disconnected from the server,
|
|
89
|
+
where the time between retries increases. Once the retries have reached the maximum value, the time between
|
|
90
|
+
retries will remain constant until a reconnect attempt is succesful.
|
|
91
|
+
factor (int): The multiplier that will be applied to the waiting time between each retry.
|
|
92
|
+
This value is specified in milliseconds.
|
|
93
|
+
exponent_base (int): The exponent base configured for the strategy.
|
|
94
|
+
jitter_percent (Optional[int]): The Jitter percent on the calculated duration. If not set, a default value will be used.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(
|
|
98
|
+
self,
|
|
99
|
+
num_of_retries: int,
|
|
100
|
+
factor: int,
|
|
101
|
+
exponent_base: int,
|
|
102
|
+
jitter_percent: Optional[int] = None,
|
|
103
|
+
):
|
|
104
|
+
self.num_of_retries = num_of_retries
|
|
105
|
+
self.factor = factor
|
|
106
|
+
self.exponent_base = exponent_base
|
|
107
|
+
self.jitter_percent = jitter_percent
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ServiceType(Enum):
|
|
111
|
+
"""
|
|
112
|
+
Represents the types of AWS services that can be used for IAM authentication.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
ELASTICACHE = 0
|
|
116
|
+
"""Amazon ElastiCache service."""
|
|
117
|
+
MEMORYDB = 1
|
|
118
|
+
"""Amazon MemoryDB service."""
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class IamAuthConfig:
|
|
122
|
+
"""
|
|
123
|
+
Configuration settings for IAM authentication.
|
|
124
|
+
|
|
125
|
+
Attributes:
|
|
126
|
+
cluster_name (str): The name of the ElastiCache/MemoryDB cluster.
|
|
127
|
+
service (ServiceType): The type of service being used (ElastiCache or MemoryDB).
|
|
128
|
+
region (str): The AWS region where the ElastiCache/MemoryDB cluster is located.
|
|
129
|
+
refresh_interval_seconds (Optional[int]): Optional refresh interval in seconds for renewing IAM authentication tokens.
|
|
130
|
+
If not provided, the core will use a default value of 300 seconds (5 min).
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
def __init__(
|
|
134
|
+
self,
|
|
135
|
+
cluster_name: str,
|
|
136
|
+
service: ServiceType,
|
|
137
|
+
region: str,
|
|
138
|
+
refresh_interval_seconds: Optional[int] = None,
|
|
139
|
+
):
|
|
140
|
+
self.cluster_name = cluster_name
|
|
141
|
+
self.service = service
|
|
142
|
+
self.region = region
|
|
143
|
+
self.refresh_interval_seconds = refresh_interval_seconds
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class ServerCredentials:
|
|
147
|
+
"""
|
|
148
|
+
Represents the credentials for connecting to a server.
|
|
149
|
+
|
|
150
|
+
Exactly one of the following authentication modes must be provided:
|
|
151
|
+
- Password-based authentication: Use password (and optionally username)
|
|
152
|
+
- IAM authentication: Use username (required) and iam_config
|
|
153
|
+
|
|
154
|
+
These modes are mutually exclusive - you cannot use both simultaneously.
|
|
155
|
+
|
|
156
|
+
Attributes:
|
|
157
|
+
password (Optional[str]): The password that will be used for authenticating connections to the servers.
|
|
158
|
+
Mutually exclusive with iam_config. Either password or iam_config must be provided.
|
|
159
|
+
username (Optional[str]): The username that will be used for authenticating connections to the servers.
|
|
160
|
+
If not supplied for password-based authentication, "default" will be used.
|
|
161
|
+
Required for IAM authentication.
|
|
162
|
+
iam_config (Optional[IamAuthConfig]): IAM authentication configuration. Mutually exclusive with password.
|
|
163
|
+
Either password or iam_config must be provided.
|
|
164
|
+
The client will automatically generate and refresh the authentication token based on the provided configuration.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
def __init__(
|
|
168
|
+
self,
|
|
169
|
+
password: Optional[str] = None,
|
|
170
|
+
username: Optional[str] = None,
|
|
171
|
+
iam_config: Optional[IamAuthConfig] = None,
|
|
172
|
+
):
|
|
173
|
+
# Validate mutual exclusivity
|
|
174
|
+
if password is not None and iam_config is not None:
|
|
175
|
+
raise ConfigurationError(
|
|
176
|
+
"password and iam_config are mutually exclusive. Use either password-based or IAM authentication, not both."
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Validate IAM requires username
|
|
180
|
+
if iam_config is not None and not username:
|
|
181
|
+
raise ConfigurationError("username is required for IAM authentication.")
|
|
182
|
+
|
|
183
|
+
# At least one authentication method must be provided
|
|
184
|
+
if password is None and iam_config is None:
|
|
185
|
+
raise ConfigurationError(
|
|
186
|
+
"Either password or iam_config must be provided for authentication."
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
self.password = password
|
|
190
|
+
self.username = username
|
|
191
|
+
self.iam_config = iam_config
|
|
192
|
+
|
|
193
|
+
def is_iam_auth(self) -> bool:
|
|
194
|
+
"""Returns True if this credential is configured for IAM authentication."""
|
|
195
|
+
return self.iam_config is not None
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class PeriodicChecksManualInterval:
|
|
199
|
+
"""
|
|
200
|
+
Represents a manually configured interval for periodic checks.
|
|
201
|
+
|
|
202
|
+
Attributes:
|
|
203
|
+
duration_in_sec (int): The duration in seconds for the interval between periodic checks.
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
def __init__(self, duration_in_sec: int) -> None:
|
|
207
|
+
self.duration_in_sec = duration_in_sec
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class PeriodicChecksStatus(Enum):
|
|
211
|
+
"""
|
|
212
|
+
Represents the cluster's periodic checks status.
|
|
213
|
+
To configure specific interval, see PeriodicChecksManualInterval.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
ENABLED_DEFAULT_CONFIGS = 0
|
|
217
|
+
"""
|
|
218
|
+
Enables the periodic checks with the default configurations.
|
|
219
|
+
"""
|
|
220
|
+
DISABLED = 1
|
|
221
|
+
"""
|
|
222
|
+
Disables the periodic checks.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class TlsAdvancedConfiguration:
|
|
227
|
+
"""
|
|
228
|
+
Represents advanced TLS configuration settings.
|
|
229
|
+
|
|
230
|
+
Attributes:
|
|
231
|
+
use_insecure_tls (Optional[bool]): Whether to bypass TLS certificate verification.
|
|
232
|
+
|
|
233
|
+
- When set to True, the client skips certificate validation.
|
|
234
|
+
This is useful when connecting to servers or clusters using self-signed certificates,
|
|
235
|
+
or when DNS entries (e.g., CNAMEs) don't match certificate hostnames.
|
|
236
|
+
|
|
237
|
+
- This setting is typically used in development or testing environments. **It is
|
|
238
|
+
strongly discouraged in production**, as it introduces security risks such as man-in-the-middle attacks.
|
|
239
|
+
|
|
240
|
+
- Only valid if TLS is already enabled in the base client configuration.
|
|
241
|
+
Enabling it without TLS will result in a `ConfigurationError`.
|
|
242
|
+
|
|
243
|
+
- Default: False (verification is enforced).
|
|
244
|
+
|
|
245
|
+
root_pem_cacerts (Optional[bytes]): Custom root certificate data for TLS connections in PEM format.
|
|
246
|
+
|
|
247
|
+
- When provided, these certificates will be used instead of the system's default trust store.
|
|
248
|
+
This is useful for connecting to servers with self-signed certificates or corporate
|
|
249
|
+
certificate authorities.
|
|
250
|
+
|
|
251
|
+
- If set to an empty bytes object (non-None but length 0), a `ConfigurationError` will be raised.
|
|
252
|
+
|
|
253
|
+
- If None (default), the system's default certificate trust store will be used (platform verifier).
|
|
254
|
+
|
|
255
|
+
- The certificate data should be in PEM format as a bytes object.
|
|
256
|
+
|
|
257
|
+
- Multiple certificates can be provided by concatenating them in PEM format.
|
|
258
|
+
|
|
259
|
+
Example usage::
|
|
260
|
+
|
|
261
|
+
# Load from file
|
|
262
|
+
with open('/path/to/ca-cert.pem', 'rb') as f:
|
|
263
|
+
cert_data = f.read()
|
|
264
|
+
tls_config = TlsAdvancedConfiguration(root_pem_cacerts=cert_data)
|
|
265
|
+
|
|
266
|
+
# Or provide directly
|
|
267
|
+
cert_data = b"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----"
|
|
268
|
+
tls_config = TlsAdvancedConfiguration(root_pem_cacerts=cert_data)
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
def __init__(
|
|
272
|
+
self,
|
|
273
|
+
use_insecure_tls: Optional[bool] = None,
|
|
274
|
+
root_pem_cacerts: Optional[bytes] = None,
|
|
275
|
+
):
|
|
276
|
+
self.use_insecure_tls = use_insecure_tls
|
|
277
|
+
self.root_pem_cacerts = root_pem_cacerts
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class AdvancedBaseClientConfiguration:
|
|
281
|
+
"""
|
|
282
|
+
Represents the advanced configuration settings for a base Glide client.
|
|
283
|
+
|
|
284
|
+
Attributes:
|
|
285
|
+
connection_timeout (Optional[int]): The duration in milliseconds to wait for a TCP/TLS connection to complete.
|
|
286
|
+
This applies both during initial client creation and any reconnection that may occur during request processing.
|
|
287
|
+
**Note**: A high connection timeout may lead to prolonged blocking of the entire command pipeline.
|
|
288
|
+
If not explicitly set, a default value of 2000 milliseconds will be used.
|
|
289
|
+
tls_config (Optional[TlsAdvancedConfiguration]): The advanced TLS configuration settings.
|
|
290
|
+
This allows for more granular control of TLS behavior, such as enabling an insecure mode
|
|
291
|
+
that bypasses certificate validation.
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
def __init__(
|
|
295
|
+
self,
|
|
296
|
+
connection_timeout: Optional[int] = None,
|
|
297
|
+
tls_config: Optional[TlsAdvancedConfiguration] = None,
|
|
298
|
+
):
|
|
299
|
+
self.connection_timeout = connection_timeout
|
|
300
|
+
self.tls_config = tls_config
|
|
301
|
+
|
|
302
|
+
def _create_a_protobuf_conn_request(
|
|
303
|
+
self, request: ConnectionRequest
|
|
304
|
+
) -> ConnectionRequest:
|
|
305
|
+
if self.connection_timeout:
|
|
306
|
+
request.connection_timeout = self.connection_timeout
|
|
307
|
+
|
|
308
|
+
if self.tls_config:
|
|
309
|
+
if self.tls_config.use_insecure_tls:
|
|
310
|
+
# Validate that TLS is enabled before allowing insecure mode
|
|
311
|
+
if request.tls_mode == TlsMode.NoTls:
|
|
312
|
+
raise ConfigurationError(
|
|
313
|
+
"use_insecure_tls cannot be enabled when use_tls is disabled."
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# Override the default SecureTls mode to InsecureTls when user explicitly requests it
|
|
317
|
+
request.tls_mode = TlsMode.InsecureTls
|
|
318
|
+
|
|
319
|
+
# Handle root certificates
|
|
320
|
+
if self.tls_config.root_pem_cacerts is not None:
|
|
321
|
+
if len(self.tls_config.root_pem_cacerts) == 0:
|
|
322
|
+
raise ConfigurationError(
|
|
323
|
+
"root_pem_cacerts cannot be an empty bytes object; use None to use platform verifier"
|
|
324
|
+
)
|
|
325
|
+
request.root_certs.append(self.tls_config.root_pem_cacerts)
|
|
326
|
+
|
|
327
|
+
return request
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class BaseClientConfiguration:
|
|
331
|
+
"""
|
|
332
|
+
Represents the configuration settings for a Glide client.
|
|
333
|
+
|
|
334
|
+
Attributes:
|
|
335
|
+
addresses (List[NodeAddress]): DNS Addresses and ports of known nodes in the cluster.
|
|
336
|
+
If the server is in cluster mode the list can be partial, as the client will attempt to map out
|
|
337
|
+
the cluster and find all nodes.
|
|
338
|
+
If the server is in standalone mode, only nodes whose addresses were provided will be used by the
|
|
339
|
+
client.
|
|
340
|
+
For example::
|
|
341
|
+
|
|
342
|
+
[
|
|
343
|
+
NodeAddress("sample-address-0001.use1.cache.amazonaws.com", 6379),
|
|
344
|
+
NodeAddress("sample-address-0002.use1.cache.amazonaws.com", 6379)
|
|
345
|
+
]
|
|
346
|
+
|
|
347
|
+
use_tls (bool): True if communication with the cluster should use Transport Level Security.
|
|
348
|
+
Should match the TLS configuration of the server/cluster, otherwise the connection attempt will fail.
|
|
349
|
+
For advanced tls configuration, please use `AdvancedBaseClientConfiguration`.
|
|
350
|
+
credentials (ServerCredentials): Credentials for authentication process.
|
|
351
|
+
If none are set, the client will not authenticate itself with the server.
|
|
352
|
+
read_from (ReadFrom): If not set, `PRIMARY` will be used.
|
|
353
|
+
request_timeout (Optional[int]): The duration in milliseconds that the client should wait for a request to
|
|
354
|
+
complete.
|
|
355
|
+
This duration encompasses sending the request, awaiting for a response from the server, and any required
|
|
356
|
+
reconnection or retries.
|
|
357
|
+
If the specified timeout is exceeded for a pending request, it will result in a timeout error. If not
|
|
358
|
+
explicitly set, a default value of 250 milliseconds will be used.
|
|
359
|
+
reconnect_strategy (Optional[BackoffStrategy]): Strategy used to determine how and when to reconnect, in case of
|
|
360
|
+
connection failures.
|
|
361
|
+
If not set, a default backoff strategy will be used.
|
|
362
|
+
database_id (Optional[int]): Index of the logical database to connect to.
|
|
363
|
+
Must be a non-negative integer.If not set, the client will connect to database 0.
|
|
364
|
+
client_name (Optional[str]): Client name to be used for the client. Will be used with CLIENT SETNAME command
|
|
365
|
+
during connection establishment.
|
|
366
|
+
protocol (ProtocolVersion): Serialization protocol to be used. If not set, `RESP3` will be used.
|
|
367
|
+
inflight_requests_limit (Optional[int]): The maximum number of concurrent requests allowed to be in-flight
|
|
368
|
+
(sent but not yet completed).
|
|
369
|
+
This limit is used to control the memory usage and prevent the client from overwhelming the server or getting
|
|
370
|
+
stuck in case of a queue backlog.
|
|
371
|
+
If not set, a default value will be used.
|
|
372
|
+
client_az (Optional[str]): Availability Zone of the client.
|
|
373
|
+
If ReadFrom strategy is AZAffinity, this setting ensures that readonly commands are directed to replicas
|
|
374
|
+
within the specified AZ if exits.
|
|
375
|
+
If ReadFrom strategy is AZAffinityReplicasAndPrimary, this setting ensures that readonly commands are directed
|
|
376
|
+
to nodes (first replicas then primary) within the specified AZ if they exist.
|
|
377
|
+
advanced_config (Optional[AdvancedBaseClientConfiguration]): Advanced configuration settings for the client.
|
|
378
|
+
|
|
379
|
+
lazy_connect (Optional[bool]): Enables lazy connection mode, where physical connections to the server(s)
|
|
380
|
+
are deferred until the first command is sent. This can reduce startup latency and allow for client
|
|
381
|
+
creation in disconnected environments.
|
|
382
|
+
|
|
383
|
+
When set to `True`, the client will not attempt to connect to the specified nodes during
|
|
384
|
+
initialization. Instead, connections will be established only when a command is actually executed.
|
|
385
|
+
|
|
386
|
+
Note that the first command executed with lazy connections may experience additional latency
|
|
387
|
+
as it needs to establish the connection first. During this initial connection, the standard
|
|
388
|
+
request timeout does not apply yet - instead, the connection establishment is governed by
|
|
389
|
+
`AdvancedBaseClientConfiguration.connection_timeout`. The request timeout (`request_timeout`)
|
|
390
|
+
only begins counting after the connection has been successfully established. This behavior
|
|
391
|
+
can effectively increase the total time needed for the first command to complete.
|
|
392
|
+
|
|
393
|
+
This setting applies to both standalone and cluster modes. Note that if an operation is
|
|
394
|
+
attempted and connection fails (e.g., unreachable nodes), errors will surface at that point.
|
|
395
|
+
|
|
396
|
+
If not set, connections are established immediately during client creation (equivalent to `False`).
|
|
397
|
+
"""
|
|
398
|
+
|
|
399
|
+
def __init__(
|
|
400
|
+
self,
|
|
401
|
+
addresses: List[NodeAddress],
|
|
402
|
+
use_tls: bool = False,
|
|
403
|
+
credentials: Optional[ServerCredentials] = None,
|
|
404
|
+
read_from: ReadFrom = ReadFrom.PRIMARY,
|
|
405
|
+
request_timeout: Optional[int] = None,
|
|
406
|
+
reconnect_strategy: Optional[BackoffStrategy] = None,
|
|
407
|
+
database_id: Optional[int] = None,
|
|
408
|
+
client_name: Optional[str] = None,
|
|
409
|
+
protocol: ProtocolVersion = ProtocolVersion.RESP3,
|
|
410
|
+
inflight_requests_limit: Optional[int] = None,
|
|
411
|
+
client_az: Optional[str] = None,
|
|
412
|
+
advanced_config: Optional[AdvancedBaseClientConfiguration] = None,
|
|
413
|
+
lazy_connect: Optional[bool] = None,
|
|
414
|
+
):
|
|
415
|
+
self.addresses = addresses
|
|
416
|
+
self.use_tls = use_tls
|
|
417
|
+
self.credentials = credentials
|
|
418
|
+
self.read_from = read_from
|
|
419
|
+
self.request_timeout = request_timeout
|
|
420
|
+
self.reconnect_strategy = reconnect_strategy
|
|
421
|
+
self.database_id = database_id
|
|
422
|
+
self.client_name = client_name
|
|
423
|
+
self.protocol = protocol
|
|
424
|
+
self.inflight_requests_limit = inflight_requests_limit
|
|
425
|
+
self.client_az = client_az
|
|
426
|
+
self.advanced_config = advanced_config
|
|
427
|
+
self.lazy_connect = lazy_connect
|
|
428
|
+
|
|
429
|
+
if read_from == ReadFrom.AZ_AFFINITY and not client_az:
|
|
430
|
+
raise ValueError(
|
|
431
|
+
"client_az must be set when read_from is set to AZ_AFFINITY"
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
if read_from == ReadFrom.AZ_AFFINITY_REPLICAS_AND_PRIMARY and not client_az:
|
|
435
|
+
raise ValueError(
|
|
436
|
+
"client_az must be set when read_from is set to AZ_AFFINITY_REPLICAS_AND_PRIMARY"
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
def _set_addresses_in_request(self, request: ConnectionRequest) -> None:
|
|
440
|
+
"""Set addresses in the protobuf request."""
|
|
441
|
+
for address in self.addresses:
|
|
442
|
+
address_info = request.addresses.add()
|
|
443
|
+
address_info.host = address.host
|
|
444
|
+
address_info.port = address.port
|
|
445
|
+
|
|
446
|
+
def _set_reconnect_strategy_in_request(self, request: ConnectionRequest) -> None:
|
|
447
|
+
"""Set reconnect strategy in the protobuf request."""
|
|
448
|
+
if not self.reconnect_strategy:
|
|
449
|
+
return
|
|
450
|
+
|
|
451
|
+
request.connection_retry_strategy.number_of_retries = (
|
|
452
|
+
self.reconnect_strategy.num_of_retries
|
|
453
|
+
)
|
|
454
|
+
request.connection_retry_strategy.factor = self.reconnect_strategy.factor
|
|
455
|
+
request.connection_retry_strategy.exponent_base = (
|
|
456
|
+
self.reconnect_strategy.exponent_base
|
|
457
|
+
)
|
|
458
|
+
if self.reconnect_strategy.jitter_percent is not None:
|
|
459
|
+
request.connection_retry_strategy.jitter_percent = (
|
|
460
|
+
self.reconnect_strategy.jitter_percent
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
def _set_credentials_in_request(self, request: ConnectionRequest) -> None:
|
|
464
|
+
"""Set credentials in the protobuf request."""
|
|
465
|
+
if not self.credentials:
|
|
466
|
+
return
|
|
467
|
+
|
|
468
|
+
if self.credentials.username:
|
|
469
|
+
request.authentication_info.username = self.credentials.username
|
|
470
|
+
|
|
471
|
+
if self.credentials.password:
|
|
472
|
+
request.authentication_info.password = self.credentials.password
|
|
473
|
+
|
|
474
|
+
# Set IAM credentials if present
|
|
475
|
+
if self.credentials.iam_config:
|
|
476
|
+
iam_config = self.credentials.iam_config
|
|
477
|
+
request.authentication_info.iam_credentials.cluster_name = (
|
|
478
|
+
iam_config.cluster_name
|
|
479
|
+
)
|
|
480
|
+
request.authentication_info.iam_credentials.region = iam_config.region
|
|
481
|
+
|
|
482
|
+
# Map ServiceType enum to protobuf ServiceType
|
|
483
|
+
if iam_config.service == ServiceType.ELASTICACHE:
|
|
484
|
+
from glide_shared.protobuf.connection_request_pb2 import (
|
|
485
|
+
ServiceType as ProtobufServiceType,
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
request.authentication_info.iam_credentials.service_type = (
|
|
489
|
+
ProtobufServiceType.ELASTICACHE
|
|
490
|
+
)
|
|
491
|
+
elif iam_config.service == ServiceType.MEMORYDB:
|
|
492
|
+
from glide_shared.protobuf.connection_request_pb2 import (
|
|
493
|
+
ServiceType as ProtobufServiceType,
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
request.authentication_info.iam_credentials.service_type = (
|
|
497
|
+
ProtobufServiceType.MEMORYDB
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
# Set optional refresh interval
|
|
501
|
+
if iam_config.refresh_interval_seconds is not None:
|
|
502
|
+
request.authentication_info.iam_credentials.refresh_interval_seconds = (
|
|
503
|
+
iam_config.refresh_interval_seconds
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
def _create_a_protobuf_conn_request(
|
|
507
|
+
self, cluster_mode: bool = False
|
|
508
|
+
) -> ConnectionRequest:
|
|
509
|
+
"""
|
|
510
|
+
Generates a Protobuf ConnectionRequest using the values from this ClientConfiguration.
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
cluster_mode (bool, optional): The cluster mode of the client. Defaults to False.
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
ConnectionRequest: Protobuf ConnectionRequest.
|
|
517
|
+
"""
|
|
518
|
+
request = ConnectionRequest()
|
|
519
|
+
|
|
520
|
+
# Set basic configuration
|
|
521
|
+
self._set_addresses_in_request(request)
|
|
522
|
+
request.tls_mode = TlsMode.SecureTls if self.use_tls else TlsMode.NoTls
|
|
523
|
+
request.read_from = self.read_from.value
|
|
524
|
+
request.cluster_mode_enabled = cluster_mode
|
|
525
|
+
request.protocol = self.protocol.value
|
|
526
|
+
|
|
527
|
+
# Set optional configuration
|
|
528
|
+
if self.request_timeout:
|
|
529
|
+
request.request_timeout = self.request_timeout
|
|
530
|
+
|
|
531
|
+
self._set_reconnect_strategy_in_request(request)
|
|
532
|
+
self._set_credentials_in_request(request)
|
|
533
|
+
|
|
534
|
+
if self.client_name:
|
|
535
|
+
request.client_name = self.client_name
|
|
536
|
+
if self.inflight_requests_limit:
|
|
537
|
+
request.inflight_requests_limit = self.inflight_requests_limit
|
|
538
|
+
if self.client_az:
|
|
539
|
+
request.client_az = self.client_az
|
|
540
|
+
if self.database_id is not None:
|
|
541
|
+
request.database_id = self.database_id
|
|
542
|
+
if self.advanced_config:
|
|
543
|
+
self.advanced_config._create_a_protobuf_conn_request(request)
|
|
544
|
+
if self.lazy_connect is not None:
|
|
545
|
+
request.lazy_connect = self.lazy_connect
|
|
546
|
+
|
|
547
|
+
return request
|
|
548
|
+
|
|
549
|
+
def _is_pubsub_configured(self) -> bool:
|
|
550
|
+
return False
|
|
551
|
+
|
|
552
|
+
def _get_pubsub_callback_and_context(
|
|
553
|
+
self,
|
|
554
|
+
) -> Tuple[Optional[Callable[[PubSubMsg, Any], None]], Any]:
|
|
555
|
+
return None, None
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
class AdvancedGlideClientConfiguration(AdvancedBaseClientConfiguration):
|
|
559
|
+
"""
|
|
560
|
+
Represents the advanced configuration settings for a Standalone Glide client.
|
|
561
|
+
"""
|
|
562
|
+
|
|
563
|
+
def __init__(
|
|
564
|
+
self,
|
|
565
|
+
connection_timeout: Optional[int] = None,
|
|
566
|
+
tls_config: Optional[TlsAdvancedConfiguration] = None,
|
|
567
|
+
):
|
|
568
|
+
|
|
569
|
+
super().__init__(connection_timeout, tls_config)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
class GlideClientConfiguration(BaseClientConfiguration):
|
|
573
|
+
"""
|
|
574
|
+
Represents the configuration settings for a Standalone Glide client.
|
|
575
|
+
|
|
576
|
+
Attributes:
|
|
577
|
+
addresses (List[NodeAddress]): DNS Addresses and ports of known nodes in the cluster.
|
|
578
|
+
Only nodes whose addresses were provided will be used by the client.
|
|
579
|
+
For example::
|
|
580
|
+
|
|
581
|
+
[
|
|
582
|
+
NodeAddress("sample-address-0001.use1.cache.amazonaws.com", 6379),
|
|
583
|
+
NodeAddress("sample-address-0002.use1.cache.amazonaws.com", 6379)
|
|
584
|
+
]
|
|
585
|
+
|
|
586
|
+
use_tls (bool): True if communication with the cluster should use Transport Level Security.
|
|
587
|
+
Please use `AdvancedGlideClusterClientConfiguration`.
|
|
588
|
+
credentials (ServerCredentials): Credentials for authentication process.
|
|
589
|
+
If none are set, the client will not authenticate itself with the server.
|
|
590
|
+
read_from (ReadFrom): If not set, `PRIMARY` will be used.
|
|
591
|
+
request_timeout (Optional[int]): The duration in milliseconds that the client should wait for a request to complete.
|
|
592
|
+
This duration encompasses sending the request, awaiting for a response from the server, and any required
|
|
593
|
+
reconnection or retries.
|
|
594
|
+
If the specified timeout is exceeded for a pending request, it will result in a timeout error.
|
|
595
|
+
If not explicitly set, a default value of 250 milliseconds will be used.
|
|
596
|
+
reconnect_strategy (Optional[BackoffStrategy]): Strategy used to determine how and when to reconnect, in case of
|
|
597
|
+
connection failures.
|
|
598
|
+
If not set, a default backoff strategy will be used.
|
|
599
|
+
database_id (Optional[int]): Index of the logical database to connect to.
|
|
600
|
+
client_name (Optional[str]): Client name to be used for the client. Will be used with CLIENT SETNAME command during
|
|
601
|
+
connection establishment.
|
|
602
|
+
protocol (ProtocolVersion): The version of the RESP protocol to communicate with the server.
|
|
603
|
+
pubsub_subscriptions (Optional[GlideClientConfiguration.PubSubSubscriptions]): Pubsub subscriptions to be used for the
|
|
604
|
+
client.
|
|
605
|
+
Will be applied via SUBSCRIBE/PSUBSCRIBE commands during connection establishment.
|
|
606
|
+
inflight_requests_limit (Optional[int]): The maximum number of concurrent requests allowed to be in-flight
|
|
607
|
+
(sent but not yet completed).
|
|
608
|
+
This limit is used to control the memory usage and prevent the client from overwhelming the server or getting
|
|
609
|
+
stuck in case of a queue backlog.
|
|
610
|
+
If not set, a default value will be used.
|
|
611
|
+
client_az (Optional[str]): Availability Zone of the client.
|
|
612
|
+
If ReadFrom strategy is AZAffinity, this setting ensures that readonly commands are directed to replicas within
|
|
613
|
+
the specified AZ if exits.
|
|
614
|
+
If ReadFrom strategy is AZAffinityReplicasAndPrimary, this setting ensures that readonly commands are directed to
|
|
615
|
+
nodes (first replicas then primary) within the specified AZ if they exist.
|
|
616
|
+
advanced_config (Optional[AdvancedGlideClientConfiguration]): Advanced configuration settings for the client,
|
|
617
|
+
see `AdvancedGlideClientConfiguration`.
|
|
618
|
+
"""
|
|
619
|
+
|
|
620
|
+
class PubSubChannelModes(IntEnum):
|
|
621
|
+
"""
|
|
622
|
+
Describes pubsub subsciption modes.
|
|
623
|
+
See [valkey.io](https://valkey.io/docs/topics/pubsub/) for more details
|
|
624
|
+
"""
|
|
625
|
+
|
|
626
|
+
Exact = 0
|
|
627
|
+
""" Use exact channel names """
|
|
628
|
+
Pattern = 1
|
|
629
|
+
""" Use channel name patterns """
|
|
630
|
+
|
|
631
|
+
@dataclass
|
|
632
|
+
class PubSubSubscriptions:
|
|
633
|
+
"""Describes pubsub configuration for standalone mode client.
|
|
634
|
+
|
|
635
|
+
Attributes:
|
|
636
|
+
channels_and_patterns (Dict[GlideClientConfiguration.PubSubChannelModes, Set[str]]):
|
|
637
|
+
Channels and patterns by modes.
|
|
638
|
+
callback (Optional[Callable[[PubSubMsg, Any], None]]):
|
|
639
|
+
Optional callback to accept the incomming messages.
|
|
640
|
+
context (Any):
|
|
641
|
+
Arbitrary context to pass to the callback.
|
|
642
|
+
"""
|
|
643
|
+
|
|
644
|
+
channels_and_patterns: Dict[
|
|
645
|
+
GlideClientConfiguration.PubSubChannelModes, Set[str]
|
|
646
|
+
]
|
|
647
|
+
callback: Optional[Callable[[PubSubMsg, Any], None]]
|
|
648
|
+
context: Any
|
|
649
|
+
|
|
650
|
+
def __init__(
|
|
651
|
+
self,
|
|
652
|
+
addresses: List[NodeAddress],
|
|
653
|
+
use_tls: bool = False,
|
|
654
|
+
credentials: Optional[ServerCredentials] = None,
|
|
655
|
+
read_from: ReadFrom = ReadFrom.PRIMARY,
|
|
656
|
+
request_timeout: Optional[int] = None,
|
|
657
|
+
reconnect_strategy: Optional[BackoffStrategy] = None,
|
|
658
|
+
database_id: Optional[int] = None,
|
|
659
|
+
client_name: Optional[str] = None,
|
|
660
|
+
protocol: ProtocolVersion = ProtocolVersion.RESP3,
|
|
661
|
+
pubsub_subscriptions: Optional[PubSubSubscriptions] = None,
|
|
662
|
+
inflight_requests_limit: Optional[int] = None,
|
|
663
|
+
client_az: Optional[str] = None,
|
|
664
|
+
advanced_config: Optional[AdvancedGlideClientConfiguration] = None,
|
|
665
|
+
lazy_connect: Optional[bool] = None,
|
|
666
|
+
):
|
|
667
|
+
super().__init__(
|
|
668
|
+
addresses=addresses,
|
|
669
|
+
use_tls=use_tls,
|
|
670
|
+
credentials=credentials,
|
|
671
|
+
read_from=read_from,
|
|
672
|
+
request_timeout=request_timeout,
|
|
673
|
+
reconnect_strategy=reconnect_strategy,
|
|
674
|
+
database_id=database_id,
|
|
675
|
+
client_name=client_name,
|
|
676
|
+
protocol=protocol,
|
|
677
|
+
inflight_requests_limit=inflight_requests_limit,
|
|
678
|
+
client_az=client_az,
|
|
679
|
+
advanced_config=advanced_config,
|
|
680
|
+
lazy_connect=lazy_connect,
|
|
681
|
+
)
|
|
682
|
+
self.pubsub_subscriptions = pubsub_subscriptions
|
|
683
|
+
|
|
684
|
+
def _create_a_protobuf_conn_request(
|
|
685
|
+
self, cluster_mode: bool = False
|
|
686
|
+
) -> ConnectionRequest:
|
|
687
|
+
assert cluster_mode is False
|
|
688
|
+
request = super()._create_a_protobuf_conn_request(cluster_mode)
|
|
689
|
+
|
|
690
|
+
if self.pubsub_subscriptions:
|
|
691
|
+
if self.protocol == ProtocolVersion.RESP2:
|
|
692
|
+
raise ConfigurationError(
|
|
693
|
+
"PubSub subscriptions require RESP3 protocol, but RESP2 was configured."
|
|
694
|
+
)
|
|
695
|
+
if (
|
|
696
|
+
self.pubsub_subscriptions.context is not None
|
|
697
|
+
and not self.pubsub_subscriptions.callback
|
|
698
|
+
):
|
|
699
|
+
raise ConfigurationError(
|
|
700
|
+
"PubSub subscriptions with a context require a callback function to be configured."
|
|
701
|
+
)
|
|
702
|
+
for (
|
|
703
|
+
channel_type,
|
|
704
|
+
channels_patterns,
|
|
705
|
+
) in self.pubsub_subscriptions.channels_and_patterns.items():
|
|
706
|
+
entry = request.pubsub_subscriptions.channels_or_patterns_by_type[
|
|
707
|
+
int(channel_type)
|
|
708
|
+
]
|
|
709
|
+
for channel_pattern in channels_patterns:
|
|
710
|
+
entry.channels_or_patterns.append(str.encode(channel_pattern))
|
|
711
|
+
|
|
712
|
+
return request
|
|
713
|
+
|
|
714
|
+
def _is_pubsub_configured(self) -> bool:
|
|
715
|
+
return self.pubsub_subscriptions is not None
|
|
716
|
+
|
|
717
|
+
def _get_pubsub_callback_and_context(
|
|
718
|
+
self,
|
|
719
|
+
) -> Tuple[Optional[Callable[[PubSubMsg, Any], None]], Any]:
|
|
720
|
+
if self.pubsub_subscriptions:
|
|
721
|
+
return self.pubsub_subscriptions.callback, self.pubsub_subscriptions.context
|
|
722
|
+
return None, None
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
class AdvancedGlideClusterClientConfiguration(AdvancedBaseClientConfiguration):
|
|
726
|
+
"""
|
|
727
|
+
Represents the advanced configuration settings for a Glide Cluster client.
|
|
728
|
+
|
|
729
|
+
Attributes:
|
|
730
|
+
connection_timeout (Optional[int]): The duration in milliseconds to wait for a TCP/TLS connection to complete.
|
|
731
|
+
This applies both during initial client creation and any reconnection that may occur during request processing.
|
|
732
|
+
**Note**: A high connection timeout may lead to prolonged blocking of the entire command pipeline.
|
|
733
|
+
If not explicitly set, a default value of 2000 milliseconds will be used.
|
|
734
|
+
tls_config (Optional[TlsAdvancedConfiguration]): The advanced TLS configuration settings.
|
|
735
|
+
This allows for more granular control of TLS behavior, such as enabling an insecure mode
|
|
736
|
+
that bypasses certificate validation.
|
|
737
|
+
refresh_topology_from_initial_nodes (bool): Enables refreshing the cluster topology using only the initial nodes.
|
|
738
|
+
When this option is enabled, all topology updates (both the periodic checks and on-demand refreshes
|
|
739
|
+
triggered by topology changes) will query only the initial nodes provided when creating the client, rather than using the internal cluster view.
|
|
740
|
+
"""
|
|
741
|
+
|
|
742
|
+
def __init__(
|
|
743
|
+
self,
|
|
744
|
+
connection_timeout: Optional[int] = None,
|
|
745
|
+
tls_config: Optional[TlsAdvancedConfiguration] = None,
|
|
746
|
+
refresh_topology_from_initial_nodes: bool = False,
|
|
747
|
+
):
|
|
748
|
+
super().__init__(connection_timeout, tls_config)
|
|
749
|
+
self.refresh_topology_from_initial_nodes = refresh_topology_from_initial_nodes
|
|
750
|
+
|
|
751
|
+
def _create_a_protobuf_conn_request(
|
|
752
|
+
self, request: ConnectionRequest
|
|
753
|
+
) -> ConnectionRequest:
|
|
754
|
+
super()._create_a_protobuf_conn_request(request)
|
|
755
|
+
|
|
756
|
+
request.refresh_topology_from_initial_nodes = (
|
|
757
|
+
self.refresh_topology_from_initial_nodes
|
|
758
|
+
)
|
|
759
|
+
return request
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
class GlideClusterClientConfiguration(BaseClientConfiguration):
|
|
763
|
+
"""
|
|
764
|
+
Represents the configuration settings for a Cluster Glide client.
|
|
765
|
+
|
|
766
|
+
Attributes:
|
|
767
|
+
addresses (List[NodeAddress]): DNS Addresses and ports of known nodes in the cluster.
|
|
768
|
+
The list can be partial, as the client will attempt to map out the cluster and find all nodes.
|
|
769
|
+
For example::
|
|
770
|
+
|
|
771
|
+
[
|
|
772
|
+
NodeAddress("sample-address-0001.use1.cache.amazonaws.com", 6379),
|
|
773
|
+
]
|
|
774
|
+
|
|
775
|
+
use_tls (bool): True if communication with the cluster should use Transport Level Security.
|
|
776
|
+
For advanced tls configuration, please use `AdvancedGlideClusterClientConfiguration`.
|
|
777
|
+
credentials (ServerCredentials): Credentials for authentication process.
|
|
778
|
+
If none are set, the client will not authenticate itself with the server.
|
|
779
|
+
read_from (ReadFrom): If not set, `PRIMARY` will be used.
|
|
780
|
+
request_timeout (Optional[int]): The duration in milliseconds that the client should wait for a request to complete.
|
|
781
|
+
This duration encompasses sending the request, awaiting for a response from the server, and any required
|
|
782
|
+
reconnection or retries.
|
|
783
|
+
If the specified timeout is exceeded for a pending request, it will result in a timeout error. If not explicitly
|
|
784
|
+
set, a default value of 250 milliseconds will be used.
|
|
785
|
+
reconnect_strategy (Optional[BackoffStrategy]): Strategy used to determine how and when to reconnect, in case of
|
|
786
|
+
connection failures.
|
|
787
|
+
If not set, a default backoff strategy will be used.
|
|
788
|
+
database_id (Optional[int]): Index of the logical database to connect to.
|
|
789
|
+
client_name (Optional[str]): Client name to be used for the client. Will be used with CLIENT SETNAME command during
|
|
790
|
+
connection establishment.
|
|
791
|
+
protocol (ProtocolVersion): The version of the RESP protocol to communicate with the server.
|
|
792
|
+
periodic_checks (Union[PeriodicChecksStatus, PeriodicChecksManualInterval]): Configure the periodic topology checks.
|
|
793
|
+
These checks evaluate changes in the cluster's topology, triggering a slot refresh when detected.
|
|
794
|
+
Periodic checks ensure a quick and efficient process by querying a limited number of nodes.
|
|
795
|
+
Defaults to PeriodicChecksStatus.ENABLED_DEFAULT_CONFIGS.
|
|
796
|
+
pubsub_subscriptions (Optional[GlideClusterClientConfiguration.PubSubSubscriptions]): Pubsub subscriptions to be used
|
|
797
|
+
for the client.
|
|
798
|
+
Will be applied via SUBSCRIBE/PSUBSCRIBE/SSUBSCRIBE commands during connection establishment.
|
|
799
|
+
inflight_requests_limit (Optional[int]): The maximum number of concurrent requests allowed to be in-flight
|
|
800
|
+
(sent but not yet completed).
|
|
801
|
+
This limit is used to control the memory usage and prevent the client from overwhelming the server or getting
|
|
802
|
+
stuck in case of a queue backlog.
|
|
803
|
+
If not set, a default value will be used.
|
|
804
|
+
client_az (Optional[str]): Availability Zone of the client.
|
|
805
|
+
If ReadFrom strategy is AZAffinity, this setting ensures that readonly commands are directed to replicas within
|
|
806
|
+
the specified AZ if exits.
|
|
807
|
+
If ReadFrom strategy is AZAffinityReplicasAndPrimary, this setting ensures that readonly commands are directed to
|
|
808
|
+
nodes (first replicas then primary) within the specified AZ if they exist.
|
|
809
|
+
advanced_config (Optional[AdvancedGlideClusterClientConfiguration]) : Advanced configuration settings for the client,
|
|
810
|
+
see `AdvancedGlideClusterClientConfiguration`.
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
Note:
|
|
814
|
+
Currently, the reconnection strategy in cluster mode is not configurable, and exponential backoff
|
|
815
|
+
with fixed values is used.
|
|
816
|
+
"""
|
|
817
|
+
|
|
818
|
+
class PubSubChannelModes(IntEnum):
|
|
819
|
+
"""
|
|
820
|
+
Describes pubsub subsciption modes.
|
|
821
|
+
See [valkey.io](https://valkey.io/docs/topics/pubsub/) for more details
|
|
822
|
+
"""
|
|
823
|
+
|
|
824
|
+
Exact = 0
|
|
825
|
+
""" Use exact channel names """
|
|
826
|
+
Pattern = 1
|
|
827
|
+
""" Use channel name patterns """
|
|
828
|
+
Sharded = 2
|
|
829
|
+
""" Use sharded pubsub. Available since Valkey version 7.0. """
|
|
830
|
+
|
|
831
|
+
@dataclass
|
|
832
|
+
class PubSubSubscriptions:
|
|
833
|
+
"""Describes pubsub configuration for cluster mode client.
|
|
834
|
+
|
|
835
|
+
Attributes:
|
|
836
|
+
channels_and_patterns (Dict[GlideClusterClientConfiguration.PubSubChannelModes, Set[str]]):
|
|
837
|
+
Channels and patterns by modes.
|
|
838
|
+
callback (Optional[Callable[[PubSubMsg, Any], None]]):
|
|
839
|
+
Optional callback to accept the incoming messages.
|
|
840
|
+
context (Any):
|
|
841
|
+
Arbitrary context to pass to the callback.
|
|
842
|
+
"""
|
|
843
|
+
|
|
844
|
+
channels_and_patterns: Dict[
|
|
845
|
+
GlideClusterClientConfiguration.PubSubChannelModes, Set[str]
|
|
846
|
+
]
|
|
847
|
+
callback: Optional[Callable[[PubSubMsg, Any], None]]
|
|
848
|
+
context: Any
|
|
849
|
+
|
|
850
|
+
def __init__(
|
|
851
|
+
self,
|
|
852
|
+
addresses: List[NodeAddress],
|
|
853
|
+
use_tls: bool = False,
|
|
854
|
+
credentials: Optional[ServerCredentials] = None,
|
|
855
|
+
read_from: ReadFrom = ReadFrom.PRIMARY,
|
|
856
|
+
request_timeout: Optional[int] = None,
|
|
857
|
+
reconnect_strategy: Optional[BackoffStrategy] = None,
|
|
858
|
+
database_id: Optional[int] = None,
|
|
859
|
+
client_name: Optional[str] = None,
|
|
860
|
+
protocol: ProtocolVersion = ProtocolVersion.RESP3,
|
|
861
|
+
periodic_checks: Union[
|
|
862
|
+
PeriodicChecksStatus, PeriodicChecksManualInterval
|
|
863
|
+
] = PeriodicChecksStatus.ENABLED_DEFAULT_CONFIGS,
|
|
864
|
+
pubsub_subscriptions: Optional[PubSubSubscriptions] = None,
|
|
865
|
+
inflight_requests_limit: Optional[int] = None,
|
|
866
|
+
client_az: Optional[str] = None,
|
|
867
|
+
advanced_config: Optional[AdvancedGlideClusterClientConfiguration] = None,
|
|
868
|
+
lazy_connect: Optional[bool] = None,
|
|
869
|
+
):
|
|
870
|
+
super().__init__(
|
|
871
|
+
addresses=addresses,
|
|
872
|
+
use_tls=use_tls,
|
|
873
|
+
credentials=credentials,
|
|
874
|
+
read_from=read_from,
|
|
875
|
+
request_timeout=request_timeout,
|
|
876
|
+
reconnect_strategy=reconnect_strategy,
|
|
877
|
+
database_id=database_id,
|
|
878
|
+
client_name=client_name,
|
|
879
|
+
protocol=protocol,
|
|
880
|
+
inflight_requests_limit=inflight_requests_limit,
|
|
881
|
+
client_az=client_az,
|
|
882
|
+
advanced_config=advanced_config,
|
|
883
|
+
lazy_connect=lazy_connect,
|
|
884
|
+
)
|
|
885
|
+
self.periodic_checks = periodic_checks
|
|
886
|
+
self.pubsub_subscriptions = pubsub_subscriptions
|
|
887
|
+
|
|
888
|
+
def _create_a_protobuf_conn_request(
|
|
889
|
+
self, cluster_mode: bool = False
|
|
890
|
+
) -> ConnectionRequest:
|
|
891
|
+
assert cluster_mode is True
|
|
892
|
+
request = super()._create_a_protobuf_conn_request(cluster_mode)
|
|
893
|
+
if type(self.periodic_checks) is PeriodicChecksManualInterval:
|
|
894
|
+
request.periodic_checks_manual_interval.duration_in_sec = (
|
|
895
|
+
self.periodic_checks.duration_in_sec
|
|
896
|
+
)
|
|
897
|
+
elif self.periodic_checks == PeriodicChecksStatus.DISABLED:
|
|
898
|
+
request.periodic_checks_disabled.SetInParent()
|
|
899
|
+
|
|
900
|
+
if self.pubsub_subscriptions:
|
|
901
|
+
if self.protocol == ProtocolVersion.RESP2:
|
|
902
|
+
raise ConfigurationError(
|
|
903
|
+
"PubSub subscriptions require RESP3 protocol, but RESP2 was configured."
|
|
904
|
+
)
|
|
905
|
+
if (
|
|
906
|
+
self.pubsub_subscriptions.context is not None
|
|
907
|
+
and not self.pubsub_subscriptions.callback
|
|
908
|
+
):
|
|
909
|
+
raise ConfigurationError(
|
|
910
|
+
"PubSub subscriptions with a context require a callback function to be configured."
|
|
911
|
+
)
|
|
912
|
+
for (
|
|
913
|
+
channel_type,
|
|
914
|
+
channels_patterns,
|
|
915
|
+
) in self.pubsub_subscriptions.channels_and_patterns.items():
|
|
916
|
+
entry = request.pubsub_subscriptions.channels_or_patterns_by_type[
|
|
917
|
+
int(channel_type)
|
|
918
|
+
]
|
|
919
|
+
for channel_pattern in channels_patterns:
|
|
920
|
+
entry.channels_or_patterns.append(str.encode(channel_pattern))
|
|
921
|
+
|
|
922
|
+
if self.lazy_connect is not None:
|
|
923
|
+
request.lazy_connect = self.lazy_connect
|
|
924
|
+
return request
|
|
925
|
+
|
|
926
|
+
def _is_pubsub_configured(self) -> bool:
|
|
927
|
+
return self.pubsub_subscriptions is not None
|
|
928
|
+
|
|
929
|
+
def _get_pubsub_callback_and_context(
|
|
930
|
+
self,
|
|
931
|
+
) -> Tuple[Optional[Callable[[PubSubMsg, Any], None]], Any]:
|
|
932
|
+
if self.pubsub_subscriptions:
|
|
933
|
+
return self.pubsub_subscriptions.callback, self.pubsub_subscriptions.context
|
|
934
|
+
return None, None
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
def load_root_certificates_from_file(path: str) -> bytes:
|
|
938
|
+
"""
|
|
939
|
+
Load PEM-encoded root certificates from a file.
|
|
940
|
+
|
|
941
|
+
This is a convenience function for loading custom root certificates from disk
|
|
942
|
+
to be used with TlsAdvancedConfiguration.
|
|
943
|
+
|
|
944
|
+
Args:
|
|
945
|
+
path (str): The file path to the PEM-encoded certificate file.
|
|
946
|
+
|
|
947
|
+
Returns:
|
|
948
|
+
bytes: The certificate data in PEM format.
|
|
949
|
+
|
|
950
|
+
Raises:
|
|
951
|
+
FileNotFoundError: If the certificate file does not exist.
|
|
952
|
+
ConfigurationError: If the certificate file is empty.
|
|
953
|
+
|
|
954
|
+
Example usage::
|
|
955
|
+
|
|
956
|
+
from glide_shared.config import load_root_certificates_from_file, TlsAdvancedConfiguration
|
|
957
|
+
|
|
958
|
+
# Load certificates from file
|
|
959
|
+
certs = load_root_certificates_from_file('/path/to/ca-cert.pem')
|
|
960
|
+
|
|
961
|
+
# Use in TLS configuration
|
|
962
|
+
tls_config = TlsAdvancedConfiguration(root_pem_cacerts=certs)
|
|
963
|
+
"""
|
|
964
|
+
try:
|
|
965
|
+
with open(path, "rb") as f:
|
|
966
|
+
data = f.read()
|
|
967
|
+
except FileNotFoundError:
|
|
968
|
+
raise FileNotFoundError(f"Certificate file not found: {path}")
|
|
969
|
+
except Exception as e:
|
|
970
|
+
raise ConfigurationError(f"Failed to read certificate file: {e}")
|
|
971
|
+
|
|
972
|
+
if len(data) == 0:
|
|
973
|
+
raise ConfigurationError(f"Certificate file is empty: {path}")
|
|
974
|
+
|
|
975
|
+
return data
|