valkey-glide 2.0.0rc3__cp310-cp310-macosx_11_0_arm64.whl → 2.2.1rc3__cp310-cp310-macosx_11_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. glide/__init__.py +160 -106
  2. glide/async_commands/cluster_commands.py +108 -105
  3. glide/async_commands/core.py +637 -444
  4. glide/async_commands/{server_modules/ft.py → ft.py} +8 -7
  5. glide/async_commands/{server_modules/glide_json.py → glide_json.py} +15 -92
  6. glide/async_commands/standalone_commands.py +27 -58
  7. glide/glide.cpython-310-darwin.so +0 -0
  8. glide/glide.pyi +26 -1
  9. glide/glide_client.py +269 -125
  10. glide/logger.py +33 -21
  11. glide/opentelemetry.py +185 -0
  12. glide_shared/__init__.py +330 -0
  13. glide_shared/commands/__init__.py +0 -0
  14. {glide/async_commands → glide_shared/commands}/batch.py +476 -64
  15. glide_shared/commands/batch_options.py +261 -0
  16. glide_shared/commands/core_options.py +407 -0
  17. {glide/async_commands → glide_shared/commands}/server_modules/ft_options/ft_aggregate_options.py +3 -3
  18. {glide/async_commands → glide_shared/commands}/server_modules/ft_options/ft_create_options.py +4 -2
  19. {glide/async_commands → glide_shared/commands}/server_modules/ft_options/ft_profile_options.py +4 -4
  20. {glide/async_commands → glide_shared/commands}/server_modules/ft_options/ft_search_options.py +4 -2
  21. {glide/async_commands → glide_shared/commands}/server_modules/json_batch.py +4 -4
  22. glide_shared/commands/server_modules/json_options.py +93 -0
  23. {glide/async_commands → glide_shared/commands}/sorted_set.py +2 -2
  24. {glide/async_commands → glide_shared/commands}/stream.py +1 -1
  25. {glide → glide_shared}/config.py +386 -61
  26. {glide → glide_shared}/constants.py +3 -3
  27. {glide → glide_shared}/exceptions.py +27 -1
  28. glide_shared/protobuf/command_request_pb2.py +56 -0
  29. glide_shared/protobuf/connection_request_pb2.py +56 -0
  30. {glide → glide_shared}/protobuf/response_pb2.py +6 -6
  31. {glide → glide_shared}/routes.py +54 -15
  32. valkey_glide-2.2.1rc3.dist-info/METADATA +210 -0
  33. valkey_glide-2.2.1rc3.dist-info/RECORD +40 -0
  34. glide/protobuf/command_request_pb2.py +0 -54
  35. glide/protobuf/command_request_pb2.pyi +0 -1187
  36. glide/protobuf/connection_request_pb2.py +0 -54
  37. glide/protobuf/connection_request_pb2.pyi +0 -320
  38. glide/protobuf/response_pb2.pyi +0 -100
  39. valkey_glide-2.0.0rc3.dist-info/METADATA +0 -127
  40. valkey_glide-2.0.0rc3.dist-info/RECORD +0 -37
  41. {glide/async_commands → glide_shared/commands}/bitmap.py +0 -0
  42. {glide/async_commands → glide_shared/commands}/command_args.py +0 -0
  43. {glide/async_commands → glide_shared/commands}/server_modules/ft_options/ft_constants.py +0 -0
  44. {glide → glide_shared}/protobuf_codec.py +0 -0
  45. {valkey_glide-2.0.0rc3.dist-info → valkey_glide-2.2.1rc3.dist-info}/WHEEL +0 -0
@@ -6,12 +6,18 @@ from dataclasses import dataclass
6
6
  from enum import Enum, IntEnum
7
7
  from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
8
8
 
9
- from glide.async_commands.core import CoreCommands
10
- from glide.exceptions import ConfigurationError
11
- from glide.protobuf.connection_request_pb2 import ConnectionRequest
12
- from glide.protobuf.connection_request_pb2 import ProtocolVersion as SentProtocolVersion
13
- from glide.protobuf.connection_request_pb2 import ReadFrom as ProtobufReadFrom
14
- from glide.protobuf.connection_request_pb2 import TlsMode
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
+ )
15
21
 
16
22
 
17
23
  class NodeAddress:
@@ -73,7 +79,7 @@ class BackoffStrategy:
73
79
  """
74
80
  Represents the strategy used to determine how and when to reconnect, in case of connection failures.
75
81
  The time between attempts grows exponentially, to the formula rand(0 .. factor * (exponentBase ^ N)), where N
76
- is the number of failed attempts.
82
+ is the number of failed attempts, and rand(...) applies a jitter of up to jitter_percent% to introduce randomness and reduce retry storms.
77
83
  Once the maximum value is reached, that will remain the time between retry attempts until a reconnect attempt is
78
84
  successful.
79
85
  The client will attempt to reconnect indefinitely.
@@ -83,6 +89,7 @@ class BackoffStrategy:
83
89
  where the time between retries increases. Once the retries have reached the maximum value, the time between
84
90
  retries will remain constant until a reconnect attempt is succesful.
85
91
  factor (int): The multiplier that will be applied to the waiting time between each retry.
92
+ This value is specified in milliseconds.
86
93
  exponent_base (int): The exponent base configured for the strategy.
87
94
  jitter_percent (Optional[int]): The Jitter percent on the calculated duration. If not set, a default value will be used.
88
95
  """
@@ -100,23 +107,92 @@ class BackoffStrategy:
100
107
  self.jitter_percent = jitter_percent
101
108
 
102
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
+
103
146
  class ServerCredentials:
104
147
  """
105
148
  Represents the credentials for connecting to a server.
106
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
+
107
156
  Attributes:
108
- password (str): The password that will be used for authenticating connections to the servers.
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.
109
159
  username (Optional[str]): The username that will be used for authenticating connections to the servers.
110
- If not supplied, "default" will be used.
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.
111
165
  """
112
166
 
113
167
  def __init__(
114
168
  self,
115
- password: str,
169
+ password: Optional[str] = None,
116
170
  username: Optional[str] = None,
171
+ iam_config: Optional[IamAuthConfig] = None,
117
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
+
118
189
  self.password = password
119
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
120
196
 
121
197
 
122
198
  class PeriodicChecksManualInterval:
@@ -147,25 +223,107 @@ class PeriodicChecksStatus(Enum):
147
223
  """
148
224
 
149
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
+
150
280
  class AdvancedBaseClientConfiguration:
151
281
  """
152
282
  Represents the advanced configuration settings for a base Glide client.
153
283
 
154
284
  Attributes:
155
285
  connection_timeout (Optional[int]): The duration in milliseconds to wait for a TCP/TLS connection to complete.
156
- This applies both during initial client creation and any reconnections that may occur during request processing.
286
+ This applies both during initial client creation and any reconnection that may occur during request processing.
157
287
  **Note**: A high connection timeout may lead to prolonged blocking of the entire command pipeline.
158
- If not explicitly set, a default value of 250 milliseconds will be used.
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.
159
292
  """
160
293
 
161
- def __init__(self, connection_timeout: Optional[int] = None):
294
+ def __init__(
295
+ self,
296
+ connection_timeout: Optional[int] = None,
297
+ tls_config: Optional[TlsAdvancedConfiguration] = None,
298
+ ):
162
299
  self.connection_timeout = connection_timeout
300
+ self.tls_config = tls_config
163
301
 
164
302
  def _create_a_protobuf_conn_request(
165
303
  self, request: ConnectionRequest
166
304
  ) -> ConnectionRequest:
167
305
  if self.connection_timeout:
168
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
+
169
327
  return request
170
328
 
171
329
 
@@ -182,24 +340,27 @@ class BaseClientConfiguration:
182
340
  For example::
183
341
 
184
342
  [
185
- {address:sample-address-0001.use1.cache.amazonaws.com, port:6379},
186
- {address: sample-address-0002.use2.cache.amazonaws.com, port:6379}
187
- ].
343
+ NodeAddress("sample-address-0001.use1.cache.amazonaws.com", 6379),
344
+ NodeAddress("sample-address-0002.use1.cache.amazonaws.com", 6379)
345
+ ]
188
346
 
189
347
  use_tls (bool): True if communication with the cluster should use Transport Level Security.
190
- Should match the TLS configuration of the server/cluster, otherwise the connection attempt will fail
348
+ Should match the TLS configuration of the server/cluster, otherwise the connection attempt will fail.
349
+ For advanced tls configuration, please use `AdvancedBaseClientConfiguration`.
191
350
  credentials (ServerCredentials): Credentials for authentication process.
192
351
  If none are set, the client will not authenticate itself with the server.
193
352
  read_from (ReadFrom): If not set, `PRIMARY` will be used.
194
353
  request_timeout (Optional[int]): The duration in milliseconds that the client should wait for a request to
195
354
  complete.
196
355
  This duration encompasses sending the request, awaiting for a response from the server, and any required
197
- reconnections or retries.
356
+ reconnection or retries.
198
357
  If the specified timeout is exceeded for a pending request, it will result in a timeout error. If not
199
358
  explicitly set, a default value of 250 milliseconds will be used.
200
359
  reconnect_strategy (Optional[BackoffStrategy]): Strategy used to determine how and when to reconnect, in case of
201
360
  connection failures.
202
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.
203
364
  client_name (Optional[str]): Client name to be used for the client. Will be used with CLIENT SETNAME command
204
365
  during connection establishment.
205
366
  protocol (ProtocolVersion): Serialization protocol to be used. If not set, `RESP3` will be used.
@@ -214,6 +375,25 @@ class BaseClientConfiguration:
214
375
  If ReadFrom strategy is AZAffinityReplicasAndPrimary, this setting ensures that readonly commands are directed
215
376
  to nodes (first replicas then primary) within the specified AZ if they exist.
216
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`).
217
397
  """
218
398
 
219
399
  def __init__(
@@ -224,11 +404,13 @@ class BaseClientConfiguration:
224
404
  read_from: ReadFrom = ReadFrom.PRIMARY,
225
405
  request_timeout: Optional[int] = None,
226
406
  reconnect_strategy: Optional[BackoffStrategy] = None,
407
+ database_id: Optional[int] = None,
227
408
  client_name: Optional[str] = None,
228
409
  protocol: ProtocolVersion = ProtocolVersion.RESP3,
229
410
  inflight_requests_limit: Optional[int] = None,
230
411
  client_az: Optional[str] = None,
231
412
  advanced_config: Optional[AdvancedBaseClientConfiguration] = None,
413
+ lazy_connect: Optional[bool] = None,
232
414
  ):
233
415
  self.addresses = addresses
234
416
  self.use_tls = use_tls
@@ -236,11 +418,13 @@ class BaseClientConfiguration:
236
418
  self.read_from = read_from
237
419
  self.request_timeout = request_timeout
238
420
  self.reconnect_strategy = reconnect_strategy
421
+ self.database_id = database_id
239
422
  self.client_name = client_name
240
423
  self.protocol = protocol
241
424
  self.inflight_requests_limit = inflight_requests_limit
242
425
  self.client_az = client_az
243
426
  self.advanced_config = advanced_config
427
+ self.lazy_connect = lazy_connect
244
428
 
245
429
  if read_from == ReadFrom.AZ_AFFINITY and not client_az:
246
430
  raise ValueError(
@@ -252,6 +436,73 @@ class BaseClientConfiguration:
252
436
  "client_az must be set when read_from is set to AZ_AFFINITY_REPLICAS_AND_PRIMARY"
253
437
  )
254
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
+
255
506
  def _create_a_protobuf_conn_request(
256
507
  self, cluster_mode: bool = False
257
508
  ) -> ConnectionRequest:
@@ -265,41 +516,33 @@ class BaseClientConfiguration:
265
516
  ConnectionRequest: Protobuf ConnectionRequest.
266
517
  """
267
518
  request = ConnectionRequest()
268
- for address in self.addresses:
269
- address_info = request.addresses.add()
270
- address_info.host = address.host
271
- address_info.port = address.port
519
+
520
+ # Set basic configuration
521
+ self._set_addresses_in_request(request)
272
522
  request.tls_mode = TlsMode.SecureTls if self.use_tls else TlsMode.NoTls
273
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
274
528
  if self.request_timeout:
275
529
  request.request_timeout = self.request_timeout
276
- if self.reconnect_strategy:
277
- request.connection_retry_strategy.number_of_retries = (
278
- self.reconnect_strategy.num_of_retries
279
- )
280
- request.connection_retry_strategy.factor = self.reconnect_strategy.factor
281
- request.connection_retry_strategy.exponent_base = (
282
- self.reconnect_strategy.exponent_base
283
- )
284
- if self.reconnect_strategy.jitter_percent is not None:
285
- request.connection_retry_strategy.jitter_percent = (
286
- self.reconnect_strategy.jitter_percent
287
- )
288
530
 
289
- request.cluster_mode_enabled = True if cluster_mode else False
290
- if self.credentials:
291
- if self.credentials.username:
292
- request.authentication_info.username = self.credentials.username
293
- request.authentication_info.password = self.credentials.password
531
+ self._set_reconnect_strategy_in_request(request)
532
+ self._set_credentials_in_request(request)
533
+
294
534
  if self.client_name:
295
535
  request.client_name = self.client_name
296
- request.protocol = self.protocol.value
297
536
  if self.inflight_requests_limit:
298
537
  request.inflight_requests_limit = self.inflight_requests_limit
299
538
  if self.client_az:
300
539
  request.client_az = self.client_az
540
+ if self.database_id is not None:
541
+ request.database_id = self.database_id
301
542
  if self.advanced_config:
302
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
303
546
 
304
547
  return request
305
548
 
@@ -308,7 +551,7 @@ class BaseClientConfiguration:
308
551
 
309
552
  def _get_pubsub_callback_and_context(
310
553
  self,
311
- ) -> Tuple[Optional[Callable[[CoreCommands.PubSubMsg, Any], None]], Any]:
554
+ ) -> Tuple[Optional[Callable[[PubSubMsg, Any], None]], Any]:
312
555
  return None, None
313
556
 
314
557
 
@@ -317,9 +560,13 @@ class AdvancedGlideClientConfiguration(AdvancedBaseClientConfiguration):
317
560
  Represents the advanced configuration settings for a Standalone Glide client.
318
561
  """
319
562
 
320
- def __init__(self, connection_timeout: Optional[int] = None):
563
+ def __init__(
564
+ self,
565
+ connection_timeout: Optional[int] = None,
566
+ tls_config: Optional[TlsAdvancedConfiguration] = None,
567
+ ):
321
568
 
322
- super().__init__(connection_timeout)
569
+ super().__init__(connection_timeout, tls_config)
323
570
 
324
571
 
325
572
  class GlideClientConfiguration(BaseClientConfiguration):
@@ -328,27 +575,28 @@ class GlideClientConfiguration(BaseClientConfiguration):
328
575
 
329
576
  Attributes:
330
577
  addresses (List[NodeAddress]): DNS Addresses and ports of known nodes in the cluster.
331
- Only nodes whose addresses were provided will be used by the client.
578
+ Only nodes whose addresses were provided will be used by the client.
332
579
  For example::
333
580
 
334
581
  [
335
- {address:sample-address-0001.use1.cache.amazonaws.com, port:6379},
336
- {address: sample-address-0002.use2.cache.amazonaws.com, port:6379}
582
+ NodeAddress("sample-address-0001.use1.cache.amazonaws.com", 6379),
583
+ NodeAddress("sample-address-0002.use1.cache.amazonaws.com", 6379)
337
584
  ]
338
585
 
339
586
  use_tls (bool): True if communication with the cluster should use Transport Level Security.
587
+ Please use `AdvancedGlideClusterClientConfiguration`.
340
588
  credentials (ServerCredentials): Credentials for authentication process.
341
589
  If none are set, the client will not authenticate itself with the server.
342
590
  read_from (ReadFrom): If not set, `PRIMARY` will be used.
343
591
  request_timeout (Optional[int]): The duration in milliseconds that the client should wait for a request to complete.
344
592
  This duration encompasses sending the request, awaiting for a response from the server, and any required
345
- reconnections or retries.
593
+ reconnection or retries.
346
594
  If the specified timeout is exceeded for a pending request, it will result in a timeout error.
347
595
  If not explicitly set, a default value of 250 milliseconds will be used.
348
596
  reconnect_strategy (Optional[BackoffStrategy]): Strategy used to determine how and when to reconnect, in case of
349
597
  connection failures.
350
598
  If not set, a default backoff strategy will be used.
351
- database_id (Optional[int]): index of the logical database to connect to.
599
+ database_id (Optional[int]): Index of the logical database to connect to.
352
600
  client_name (Optional[str]): Client name to be used for the client. Will be used with CLIENT SETNAME command during
353
601
  connection establishment.
354
602
  protocol (ProtocolVersion): The version of the RESP protocol to communicate with the server.
@@ -387,7 +635,7 @@ class GlideClientConfiguration(BaseClientConfiguration):
387
635
  Attributes:
388
636
  channels_and_patterns (Dict[GlideClientConfiguration.PubSubChannelModes, Set[str]]):
389
637
  Channels and patterns by modes.
390
- callback (Optional[Callable[[CoreCommands.PubSubMsg, Any], None]]):
638
+ callback (Optional[Callable[[PubSubMsg, Any], None]]):
391
639
  Optional callback to accept the incomming messages.
392
640
  context (Any):
393
641
  Arbitrary context to pass to the callback.
@@ -396,7 +644,7 @@ class GlideClientConfiguration(BaseClientConfiguration):
396
644
  channels_and_patterns: Dict[
397
645
  GlideClientConfiguration.PubSubChannelModes, Set[str]
398
646
  ]
399
- callback: Optional[Callable[[CoreCommands.PubSubMsg, Any], None]]
647
+ callback: Optional[Callable[[PubSubMsg, Any], None]]
400
648
  context: Any
401
649
 
402
650
  def __init__(
@@ -414,6 +662,7 @@ class GlideClientConfiguration(BaseClientConfiguration):
414
662
  inflight_requests_limit: Optional[int] = None,
415
663
  client_az: Optional[str] = None,
416
664
  advanced_config: Optional[AdvancedGlideClientConfiguration] = None,
665
+ lazy_connect: Optional[bool] = None,
417
666
  ):
418
667
  super().__init__(
419
668
  addresses=addresses,
@@ -422,13 +671,14 @@ class GlideClientConfiguration(BaseClientConfiguration):
422
671
  read_from=read_from,
423
672
  request_timeout=request_timeout,
424
673
  reconnect_strategy=reconnect_strategy,
674
+ database_id=database_id,
425
675
  client_name=client_name,
426
676
  protocol=protocol,
427
677
  inflight_requests_limit=inflight_requests_limit,
428
678
  client_az=client_az,
429
679
  advanced_config=advanced_config,
680
+ lazy_connect=lazy_connect,
430
681
  )
431
- self.database_id = database_id
432
682
  self.pubsub_subscriptions = pubsub_subscriptions
433
683
 
434
684
  def _create_a_protobuf_conn_request(
@@ -436,8 +686,6 @@ class GlideClientConfiguration(BaseClientConfiguration):
436
686
  ) -> ConnectionRequest:
437
687
  assert cluster_mode is False
438
688
  request = super()._create_a_protobuf_conn_request(cluster_mode)
439
- if self.database_id:
440
- request.database_id = self.database_id
441
689
 
442
690
  if self.pubsub_subscriptions:
443
691
  if self.protocol == ProtocolVersion.RESP2:
@@ -468,7 +716,7 @@ class GlideClientConfiguration(BaseClientConfiguration):
468
716
 
469
717
  def _get_pubsub_callback_and_context(
470
718
  self,
471
- ) -> Tuple[Optional[Callable[[CoreCommands.PubSubMsg, Any], None]], Any]:
719
+ ) -> Tuple[Optional[Callable[[PubSubMsg, Any], None]], Any]:
472
720
  if self.pubsub_subscriptions:
473
721
  return self.pubsub_subscriptions.callback, self.pubsub_subscriptions.context
474
722
  return None, None
@@ -477,10 +725,38 @@ class GlideClientConfiguration(BaseClientConfiguration):
477
725
  class AdvancedGlideClusterClientConfiguration(AdvancedBaseClientConfiguration):
478
726
  """
479
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.
480
740
  """
481
741
 
482
- def __init__(self, connection_timeout: Optional[int] = None):
483
- super().__init__(connection_timeout)
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
484
760
 
485
761
 
486
762
  class GlideClusterClientConfiguration(BaseClientConfiguration):
@@ -493,21 +769,23 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
493
769
  For example::
494
770
 
495
771
  [
496
- {address:configuration-endpoint.use1.cache.amazonaws.com, port:6379}
772
+ NodeAddress("sample-address-0001.use1.cache.amazonaws.com", 6379),
497
773
  ]
498
774
 
499
775
  use_tls (bool): True if communication with the cluster should use Transport Level Security.
776
+ For advanced tls configuration, please use `AdvancedGlideClusterClientConfiguration`.
500
777
  credentials (ServerCredentials): Credentials for authentication process.
501
778
  If none are set, the client will not authenticate itself with the server.
502
779
  read_from (ReadFrom): If not set, `PRIMARY` will be used.
503
780
  request_timeout (Optional[int]): The duration in milliseconds that the client should wait for a request to complete.
504
781
  This duration encompasses sending the request, awaiting for a response from the server, and any required
505
- reconnections or retries.
782
+ reconnection or retries.
506
783
  If the specified timeout is exceeded for a pending request, it will result in a timeout error. If not explicitly
507
784
  set, a default value of 250 milliseconds will be used.
508
785
  reconnect_strategy (Optional[BackoffStrategy]): Strategy used to determine how and when to reconnect, in case of
509
786
  connection failures.
510
787
  If not set, a default backoff strategy will be used.
788
+ database_id (Optional[int]): Index of the logical database to connect to.
511
789
  client_name (Optional[str]): Client name to be used for the client. Will be used with CLIENT SETNAME command during
512
790
  connection establishment.
513
791
  protocol (ProtocolVersion): The version of the RESP protocol to communicate with the server.
@@ -557,7 +835,7 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
557
835
  Attributes:
558
836
  channels_and_patterns (Dict[GlideClusterClientConfiguration.PubSubChannelModes, Set[str]]):
559
837
  Channels and patterns by modes.
560
- callback (Optional[Callable[[CoreCommands.PubSubMsg, Any], None]]):
838
+ callback (Optional[Callable[[PubSubMsg, Any], None]]):
561
839
  Optional callback to accept the incoming messages.
562
840
  context (Any):
563
841
  Arbitrary context to pass to the callback.
@@ -566,7 +844,7 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
566
844
  channels_and_patterns: Dict[
567
845
  GlideClusterClientConfiguration.PubSubChannelModes, Set[str]
568
846
  ]
569
- callback: Optional[Callable[[CoreCommands.PubSubMsg, Any], None]]
847
+ callback: Optional[Callable[[PubSubMsg, Any], None]]
570
848
  context: Any
571
849
 
572
850
  def __init__(
@@ -577,6 +855,7 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
577
855
  read_from: ReadFrom = ReadFrom.PRIMARY,
578
856
  request_timeout: Optional[int] = None,
579
857
  reconnect_strategy: Optional[BackoffStrategy] = None,
858
+ database_id: Optional[int] = None,
580
859
  client_name: Optional[str] = None,
581
860
  protocol: ProtocolVersion = ProtocolVersion.RESP3,
582
861
  periodic_checks: Union[
@@ -586,6 +865,7 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
586
865
  inflight_requests_limit: Optional[int] = None,
587
866
  client_az: Optional[str] = None,
588
867
  advanced_config: Optional[AdvancedGlideClusterClientConfiguration] = None,
868
+ lazy_connect: Optional[bool] = None,
589
869
  ):
590
870
  super().__init__(
591
871
  addresses=addresses,
@@ -594,11 +874,13 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
594
874
  read_from=read_from,
595
875
  request_timeout=request_timeout,
596
876
  reconnect_strategy=reconnect_strategy,
877
+ database_id=database_id,
597
878
  client_name=client_name,
598
879
  protocol=protocol,
599
880
  inflight_requests_limit=inflight_requests_limit,
600
881
  client_az=client_az,
601
882
  advanced_config=advanced_config,
883
+ lazy_connect=lazy_connect,
602
884
  )
603
885
  self.periodic_checks = periodic_checks
604
886
  self.pubsub_subscriptions = pubsub_subscriptions
@@ -637,6 +919,8 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
637
919
  for channel_pattern in channels_patterns:
638
920
  entry.channels_or_patterns.append(str.encode(channel_pattern))
639
921
 
922
+ if self.lazy_connect is not None:
923
+ request.lazy_connect = self.lazy_connect
640
924
  return request
641
925
 
642
926
  def _is_pubsub_configured(self) -> bool:
@@ -644,7 +928,48 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
644
928
 
645
929
  def _get_pubsub_callback_and_context(
646
930
  self,
647
- ) -> Tuple[Optional[Callable[[CoreCommands.PubSubMsg, Any], None]], Any]:
931
+ ) -> Tuple[Optional[Callable[[PubSubMsg, Any], None]], Any]:
648
932
  if self.pubsub_subscriptions:
649
933
  return self.pubsub_subscriptions.callback, self.pubsub_subscriptions.context
650
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