valkey-glide 2.0.0__cp39-cp39-macosx_11_0_arm64.whl → 2.2.2__cp39-cp39-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 (44) hide show
  1. glide/__init__.py +152 -118
  2. glide/async_commands/cluster_commands.py +29 -14
  3. glide/async_commands/core.py +600 -414
  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 +10 -51
  7. glide/glide.cpython-39-darwin.so +0 -0
  8. glide/glide.pyi +1 -1
  9. glide/glide_client.py +54 -48
  10. glide/logger.py +3 -3
  11. glide/opentelemetry.py +8 -4
  12. glide_shared/__init__.py +330 -0
  13. glide_shared/commands/__init__.py +0 -0
  14. {glide/async_commands → glide_shared/commands}/batch.py +426 -32
  15. {glide/async_commands → glide_shared/commands}/batch_options.py +1 -1
  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 +302 -58
  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}/routes.py +29 -15
  31. {valkey_glide-2.0.0.dist-info → valkey_glide-2.2.2.dist-info}/METADATA +120 -58
  32. valkey_glide-2.2.2.dist-info/RECORD +40 -0
  33. glide/protobuf/command_request_pb2.py +0 -54
  34. glide/protobuf/command_request_pb2.pyi +0 -1193
  35. glide/protobuf/connection_request_pb2.py +0 -52
  36. glide/protobuf/connection_request_pb2.pyi +0 -299
  37. glide/protobuf/response_pb2.pyi +0 -106
  38. valkey_glide-2.0.0.dist-info/RECORD +0 -39
  39. {glide/async_commands → glide_shared/commands}/bitmap.py +0 -0
  40. {glide/async_commands → glide_shared/commands}/command_args.py +0 -0
  41. {glide/async_commands → glide_shared/commands}/server_modules/ft_options/ft_constants.py +0 -0
  42. {glide → glide_shared}/protobuf/response_pb2.py +0 -0
  43. {glide → glide_shared}/protobuf_codec.py +0 -0
  44. {valkey_glide-2.0.0.dist-info → valkey_glide-2.2.2.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, and `rand(...)` applies a jitter of up to `jitter_percent`% to introduce randomness and reduce retry storms.
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:
@@ -165,10 +241,40 @@ class TlsAdvancedConfiguration:
165
241
  Enabling it without TLS will result in a `ConfigurationError`.
166
242
 
167
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)
168
269
  """
169
270
 
170
- def __init__(self, use_insecure_tls: Optional[bool] = None):
271
+ def __init__(
272
+ self,
273
+ use_insecure_tls: Optional[bool] = None,
274
+ root_pem_cacerts: Optional[bytes] = None,
275
+ ):
171
276
  self.use_insecure_tls = use_insecure_tls
277
+ self.root_pem_cacerts = root_pem_cacerts
172
278
 
173
279
 
174
280
  class AdvancedBaseClientConfiguration:
@@ -199,13 +305,24 @@ class AdvancedBaseClientConfiguration:
199
305
  if self.connection_timeout:
200
306
  request.connection_timeout = self.connection_timeout
201
307
 
202
- if self.tls_config and self.tls_config.use_insecure_tls:
203
- if request.tls_mode == TlsMode.SecureTls:
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
204
317
  request.tls_mode = TlsMode.InsecureTls
205
- elif request.tls_mode == TlsMode.NoTls:
206
- raise ConfigurationError(
207
- "use_insecure_tls cannot be enabled when use_tls is disabled."
208
- )
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)
209
326
 
210
327
  return request
211
328
 
@@ -223,9 +340,9 @@ class BaseClientConfiguration:
223
340
  For example::
224
341
 
225
342
  [
226
- {address:sample-address-0001.use1.cache.amazonaws.com, port:6379},
227
- {address: sample-address-0002.use2.cache.amazonaws.com, port:6379}
228
- ].
343
+ NodeAddress("sample-address-0001.use1.cache.amazonaws.com", 6379),
344
+ NodeAddress("sample-address-0002.use1.cache.amazonaws.com", 6379)
345
+ ]
229
346
 
230
347
  use_tls (bool): True if communication with the cluster should use Transport Level Security.
231
348
  Should match the TLS configuration of the server/cluster, otherwise the connection attempt will fail.
@@ -242,6 +359,8 @@ class BaseClientConfiguration:
242
359
  reconnect_strategy (Optional[BackoffStrategy]): Strategy used to determine how and when to reconnect, in case of
243
360
  connection failures.
244
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.
245
364
  client_name (Optional[str]): Client name to be used for the client. Will be used with CLIENT SETNAME command
246
365
  during connection establishment.
247
366
  protocol (ProtocolVersion): Serialization protocol to be used. If not set, `RESP3` will be used.
@@ -285,6 +404,7 @@ class BaseClientConfiguration:
285
404
  read_from: ReadFrom = ReadFrom.PRIMARY,
286
405
  request_timeout: Optional[int] = None,
287
406
  reconnect_strategy: Optional[BackoffStrategy] = None,
407
+ database_id: Optional[int] = None,
288
408
  client_name: Optional[str] = None,
289
409
  protocol: ProtocolVersion = ProtocolVersion.RESP3,
290
410
  inflight_requests_limit: Optional[int] = None,
@@ -298,6 +418,7 @@ class BaseClientConfiguration:
298
418
  self.read_from = read_from
299
419
  self.request_timeout = request_timeout
300
420
  self.reconnect_strategy = reconnect_strategy
421
+ self.database_id = database_id
301
422
  self.client_name = client_name
302
423
  self.protocol = protocol
303
424
  self.inflight_requests_limit = inflight_requests_limit
@@ -315,6 +436,73 @@ class BaseClientConfiguration:
315
436
  "client_az must be set when read_from is set to AZ_AFFINITY_REPLICAS_AND_PRIMARY"
316
437
  )
317
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
+
318
506
  def _create_a_protobuf_conn_request(
319
507
  self, cluster_mode: bool = False
320
508
  ) -> ConnectionRequest:
@@ -328,44 +516,34 @@ class BaseClientConfiguration:
328
516
  ConnectionRequest: Protobuf ConnectionRequest.
329
517
  """
330
518
  request = ConnectionRequest()
331
- for address in self.addresses:
332
- address_info = request.addresses.add()
333
- address_info.host = address.host
334
- address_info.port = address.port
519
+
520
+ # Set basic configuration
521
+ self._set_addresses_in_request(request)
335
522
  request.tls_mode = TlsMode.SecureTls if self.use_tls else TlsMode.NoTls
336
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
337
528
  if self.request_timeout:
338
529
  request.request_timeout = self.request_timeout
339
- if self.reconnect_strategy:
340
- request.connection_retry_strategy.number_of_retries = (
341
- self.reconnect_strategy.num_of_retries
342
- )
343
- request.connection_retry_strategy.factor = self.reconnect_strategy.factor
344
- request.connection_retry_strategy.exponent_base = (
345
- self.reconnect_strategy.exponent_base
346
- )
347
- if self.reconnect_strategy.jitter_percent is not None:
348
- request.connection_retry_strategy.jitter_percent = (
349
- self.reconnect_strategy.jitter_percent
350
- )
351
530
 
352
- request.cluster_mode_enabled = True if cluster_mode else False
353
- if self.credentials:
354
- if self.credentials.username:
355
- request.authentication_info.username = self.credentials.username
356
- request.authentication_info.password = self.credentials.password
531
+ self._set_reconnect_strategy_in_request(request)
532
+ self._set_credentials_in_request(request)
533
+
357
534
  if self.client_name:
358
535
  request.client_name = self.client_name
359
- request.protocol = self.protocol.value
360
536
  if self.inflight_requests_limit:
361
537
  request.inflight_requests_limit = self.inflight_requests_limit
362
538
  if self.client_az:
363
539
  request.client_az = self.client_az
540
+ if self.database_id is not None:
541
+ request.database_id = self.database_id
364
542
  if self.advanced_config:
365
543
  self.advanced_config._create_a_protobuf_conn_request(request)
366
-
367
544
  if self.lazy_connect is not None:
368
545
  request.lazy_connect = self.lazy_connect
546
+
369
547
  return request
370
548
 
371
549
  def _is_pubsub_configured(self) -> bool:
@@ -373,7 +551,7 @@ class BaseClientConfiguration:
373
551
 
374
552
  def _get_pubsub_callback_and_context(
375
553
  self,
376
- ) -> Tuple[Optional[Callable[[CoreCommands.PubSubMsg, Any], None]], Any]:
554
+ ) -> Tuple[Optional[Callable[[PubSubMsg, Any], None]], Any]:
377
555
  return None, None
378
556
 
379
557
 
@@ -397,12 +575,12 @@ class GlideClientConfiguration(BaseClientConfiguration):
397
575
 
398
576
  Attributes:
399
577
  addresses (List[NodeAddress]): DNS Addresses and ports of known nodes in the cluster.
400
- 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.
401
579
  For example::
402
580
 
403
581
  [
404
- {address:sample-address-0001.use1.cache.amazonaws.com, port:6379},
405
- {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)
406
584
  ]
407
585
 
408
586
  use_tls (bool): True if communication with the cluster should use Transport Level Security.
@@ -418,7 +596,7 @@ class GlideClientConfiguration(BaseClientConfiguration):
418
596
  reconnect_strategy (Optional[BackoffStrategy]): Strategy used to determine how and when to reconnect, in case of
419
597
  connection failures.
420
598
  If not set, a default backoff strategy will be used.
421
- 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.
422
600
  client_name (Optional[str]): Client name to be used for the client. Will be used with CLIENT SETNAME command during
423
601
  connection establishment.
424
602
  protocol (ProtocolVersion): The version of the RESP protocol to communicate with the server.
@@ -457,7 +635,7 @@ class GlideClientConfiguration(BaseClientConfiguration):
457
635
  Attributes:
458
636
  channels_and_patterns (Dict[GlideClientConfiguration.PubSubChannelModes, Set[str]]):
459
637
  Channels and patterns by modes.
460
- callback (Optional[Callable[[CoreCommands.PubSubMsg, Any], None]]):
638
+ callback (Optional[Callable[[PubSubMsg, Any], None]]):
461
639
  Optional callback to accept the incomming messages.
462
640
  context (Any):
463
641
  Arbitrary context to pass to the callback.
@@ -466,7 +644,7 @@ class GlideClientConfiguration(BaseClientConfiguration):
466
644
  channels_and_patterns: Dict[
467
645
  GlideClientConfiguration.PubSubChannelModes, Set[str]
468
646
  ]
469
- callback: Optional[Callable[[CoreCommands.PubSubMsg, Any], None]]
647
+ callback: Optional[Callable[[PubSubMsg, Any], None]]
470
648
  context: Any
471
649
 
472
650
  def __init__(
@@ -493,6 +671,7 @@ class GlideClientConfiguration(BaseClientConfiguration):
493
671
  read_from=read_from,
494
672
  request_timeout=request_timeout,
495
673
  reconnect_strategy=reconnect_strategy,
674
+ database_id=database_id,
496
675
  client_name=client_name,
497
676
  protocol=protocol,
498
677
  inflight_requests_limit=inflight_requests_limit,
@@ -500,7 +679,6 @@ class GlideClientConfiguration(BaseClientConfiguration):
500
679
  advanced_config=advanced_config,
501
680
  lazy_connect=lazy_connect,
502
681
  )
503
- self.database_id = database_id
504
682
  self.pubsub_subscriptions = pubsub_subscriptions
505
683
 
506
684
  def _create_a_protobuf_conn_request(
@@ -508,8 +686,6 @@ class GlideClientConfiguration(BaseClientConfiguration):
508
686
  ) -> ConnectionRequest:
509
687
  assert cluster_mode is False
510
688
  request = super()._create_a_protobuf_conn_request(cluster_mode)
511
- if self.database_id:
512
- request.database_id = self.database_id
513
689
 
514
690
  if self.pubsub_subscriptions:
515
691
  if self.protocol == ProtocolVersion.RESP2:
@@ -540,7 +716,7 @@ class GlideClientConfiguration(BaseClientConfiguration):
540
716
 
541
717
  def _get_pubsub_callback_and_context(
542
718
  self,
543
- ) -> Tuple[Optional[Callable[[CoreCommands.PubSubMsg, Any], None]], Any]:
719
+ ) -> Tuple[Optional[Callable[[PubSubMsg, Any], None]], Any]:
544
720
  if self.pubsub_subscriptions:
545
721
  return self.pubsub_subscriptions.callback, self.pubsub_subscriptions.context
546
722
  return None, None
@@ -549,14 +725,38 @@ class GlideClientConfiguration(BaseClientConfiguration):
549
725
  class AdvancedGlideClusterClientConfiguration(AdvancedBaseClientConfiguration):
550
726
  """
551
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.
552
740
  """
553
741
 
554
742
  def __init__(
555
743
  self,
556
744
  connection_timeout: Optional[int] = None,
557
745
  tls_config: Optional[TlsAdvancedConfiguration] = None,
746
+ refresh_topology_from_initial_nodes: bool = False,
558
747
  ):
559
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
560
760
 
561
761
 
562
762
  class GlideClusterClientConfiguration(BaseClientConfiguration):
@@ -569,7 +769,7 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
569
769
  For example::
570
770
 
571
771
  [
572
- {address:configuration-endpoint.use1.cache.amazonaws.com, port:6379}
772
+ NodeAddress("sample-address-0001.use1.cache.amazonaws.com", 6379),
573
773
  ]
574
774
 
575
775
  use_tls (bool): True if communication with the cluster should use Transport Level Security.
@@ -585,6 +785,7 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
585
785
  reconnect_strategy (Optional[BackoffStrategy]): Strategy used to determine how and when to reconnect, in case of
586
786
  connection failures.
587
787
  If not set, a default backoff strategy will be used.
788
+ database_id (Optional[int]): Index of the logical database to connect to.
588
789
  client_name (Optional[str]): Client name to be used for the client. Will be used with CLIENT SETNAME command during
589
790
  connection establishment.
590
791
  protocol (ProtocolVersion): The version of the RESP protocol to communicate with the server.
@@ -634,7 +835,7 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
634
835
  Attributes:
635
836
  channels_and_patterns (Dict[GlideClusterClientConfiguration.PubSubChannelModes, Set[str]]):
636
837
  Channels and patterns by modes.
637
- callback (Optional[Callable[[CoreCommands.PubSubMsg, Any], None]]):
838
+ callback (Optional[Callable[[PubSubMsg, Any], None]]):
638
839
  Optional callback to accept the incoming messages.
639
840
  context (Any):
640
841
  Arbitrary context to pass to the callback.
@@ -643,7 +844,7 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
643
844
  channels_and_patterns: Dict[
644
845
  GlideClusterClientConfiguration.PubSubChannelModes, Set[str]
645
846
  ]
646
- callback: Optional[Callable[[CoreCommands.PubSubMsg, Any], None]]
847
+ callback: Optional[Callable[[PubSubMsg, Any], None]]
647
848
  context: Any
648
849
 
649
850
  def __init__(
@@ -654,6 +855,7 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
654
855
  read_from: ReadFrom = ReadFrom.PRIMARY,
655
856
  request_timeout: Optional[int] = None,
656
857
  reconnect_strategy: Optional[BackoffStrategy] = None,
858
+ database_id: Optional[int] = None,
657
859
  client_name: Optional[str] = None,
658
860
  protocol: ProtocolVersion = ProtocolVersion.RESP3,
659
861
  periodic_checks: Union[
@@ -672,6 +874,7 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
672
874
  read_from=read_from,
673
875
  request_timeout=request_timeout,
674
876
  reconnect_strategy=reconnect_strategy,
877
+ database_id=database_id,
675
878
  client_name=client_name,
676
879
  protocol=protocol,
677
880
  inflight_requests_limit=inflight_requests_limit,
@@ -725,7 +928,48 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
725
928
 
726
929
  def _get_pubsub_callback_and_context(
727
930
  self,
728
- ) -> Tuple[Optional[Callable[[CoreCommands.PubSubMsg, Any], None]], Any]:
931
+ ) -> Tuple[Optional[Callable[[PubSubMsg, Any], None]], Any]:
729
932
  if self.pubsub_subscriptions:
730
933
  return self.pubsub_subscriptions.callback, self.pubsub_subscriptions.context
731
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
@@ -2,9 +2,9 @@
2
2
 
3
3
  from typing import Any, Dict, List, Literal, Mapping, Optional, Set, TypeVar, Union
4
4
 
5
- from glide.protobuf.command_request_pb2 import CommandRequest
6
- from glide.protobuf.connection_request_pb2 import ConnectionRequest
7
- from glide.routes import ByAddressRoute, RandomNode, SlotIdRoute, SlotKeyRoute
5
+ from glide_shared.protobuf.command_request_pb2 import CommandRequest
6
+ from glide_shared.protobuf.connection_request_pb2 import ConnectionRequest
7
+ from glide_shared.routes import ByAddressRoute, RandomNode, SlotIdRoute, SlotKeyRoute
8
8
 
9
9
  OK: str = "OK"
10
10
  DEFAULT_READ_BYTES_SIZE: int = pow(2, 16)
@@ -1,6 +1,8 @@
1
1
  # Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
2
2
 
3
- from typing import Optional
3
+ from typing import Optional, Type
4
+
5
+ from glide_shared.protobuf.response_pb2 import RequestErrorType
4
6
 
5
7
 
6
8
  class GlideError(Exception):
@@ -60,3 +62,27 @@ class ConfigurationError(RequestError):
60
62
  """
61
63
  Errors that are thrown when a request cannot be completed in current configuration settings.
62
64
  """
65
+
66
+ pass
67
+
68
+
69
+ class LoggerError(GlideError):
70
+ """
71
+ Errors that are thrown when the logger has an error initializing.
72
+ """
73
+
74
+ pass
75
+
76
+
77
+ def get_request_error_class(
78
+ error_type: Optional[RequestErrorType.ValueType],
79
+ ) -> Type[RequestError]:
80
+ if error_type == RequestErrorType.Disconnect:
81
+ return ConnectionError
82
+ if error_type == RequestErrorType.ExecAbort:
83
+ return ExecAbortError
84
+ if error_type == RequestErrorType.Timeout:
85
+ return TimeoutError
86
+ if error_type == RequestErrorType.Unspecified:
87
+ return RequestError
88
+ return RequestError