valkey-glide 2.0.0rc7__cp312-cp312-macosx_11_0_arm64.whl → 2.0.1__cp312-cp312-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of valkey-glide might be problematic. Click here for more details.
- glide/__init__.py +21 -1
- glide/async_commands/batch.py +16 -9
- glide/async_commands/batch_options.py +261 -0
- glide/async_commands/cluster_commands.py +74 -92
- glide/async_commands/core.py +18 -11
- glide/async_commands/standalone_commands.py +10 -7
- glide/config.py +93 -12
- glide/glide.cpython-312-darwin.so +0 -0
- glide/glide.pyi +25 -0
- glide/glide_client.py +32 -0
- glide/logger.py +30 -18
- glide/opentelemetry.py +181 -0
- glide/protobuf/command_request_pb2.py +8 -8
- glide/protobuf/command_request_pb2.pyi +8 -2
- glide/protobuf/connection_request_pb2.py +13 -15
- glide/protobuf/connection_request_pb2.pyi +5 -26
- glide/protobuf/response_pb2.py +6 -6
- glide/protobuf/response_pb2.pyi +8 -2
- {valkey_glide-2.0.0rc7.dist-info → valkey_glide-2.0.1.dist-info}/METADATA +10 -5
- valkey_glide-2.0.1.dist-info/RECORD +39 -0
- valkey_glide-2.0.0rc7.dist-info/RECORD +0 -37
- {valkey_glide-2.0.0rc7.dist-info → valkey_glide-2.0.1.dist-info}/WHEEL +0 -0
glide/async_commands/core.py
CHANGED
|
@@ -61,6 +61,7 @@ from glide.constants import (
|
|
|
61
61
|
TXInfoStreamFullResponse,
|
|
62
62
|
TXInfoStreamResponse,
|
|
63
63
|
)
|
|
64
|
+
from glide.exceptions import RequestError
|
|
64
65
|
from glide.protobuf.command_request_pb2 import RequestType
|
|
65
66
|
from glide.routes import Route
|
|
66
67
|
|
|
@@ -2535,8 +2536,8 @@ class CoreCommands(Protocol):
|
|
|
2535
2536
|
Returns:
|
|
2536
2537
|
TOK: A simple "OK" response.
|
|
2537
2538
|
|
|
2538
|
-
If `start` exceeds the end of the list, or if `start` is greater than `end`, the
|
|
2539
|
-
|
|
2539
|
+
If `start` exceeds the end of the list, or if `start` is greater than `end`, the list is emptied
|
|
2540
|
+
and the key is removed.
|
|
2540
2541
|
|
|
2541
2542
|
If `end` exceeds the actual end of the list, it will be treated like the last element of the list.
|
|
2542
2543
|
|
|
@@ -2554,9 +2555,6 @@ class CoreCommands(Protocol):
|
|
|
2554
2555
|
async def lrem(self, key: TEncodable, count: int, element: TEncodable) -> int:
|
|
2555
2556
|
"""
|
|
2556
2557
|
Removes the first `count` occurrences of elements equal to `element` from the list stored at `key`.
|
|
2557
|
-
If `count` is positive, it removes elements equal to `element` moving from head to tail.
|
|
2558
|
-
If `count` is negative, it removes elements equal to `element` moving from tail to head.
|
|
2559
|
-
If `count` is 0 or greater than the occurrences of elements equal to `element`, it removes all elements
|
|
2560
2558
|
equal to `element`.
|
|
2561
2559
|
|
|
2562
2560
|
See [valkey.io](https://valkey.io/commands/lrem/) for more details.
|
|
@@ -2564,6 +2562,11 @@ class CoreCommands(Protocol):
|
|
|
2564
2562
|
Args:
|
|
2565
2563
|
key (TEncodable): The key of the list.
|
|
2566
2564
|
count (int): The count of occurrences of elements equal to `element` to remove.
|
|
2565
|
+
|
|
2566
|
+
- If `count` is positive, it removes elements equal to `element` moving from head to tail.
|
|
2567
|
+
- If `count` is negative, it removes elements equal to `element` moving from tail to head.
|
|
2568
|
+
- If `count` is 0 or greater than the occurrences of elements equal to `element`, it removes all elements
|
|
2569
|
+
|
|
2567
2570
|
element (TEncodable): The element to remove from the list.
|
|
2568
2571
|
|
|
2569
2572
|
Returns:
|
|
@@ -6048,19 +6051,19 @@ class CoreCommands(Protocol):
|
|
|
6048
6051
|
elements (List[TEncodable]): A list of members to add to the HyperLogLog stored at `key`.
|
|
6049
6052
|
|
|
6050
6053
|
Returns:
|
|
6051
|
-
|
|
6052
|
-
altered, then returns
|
|
6054
|
+
bool: If the HyperLogLog is newly created, or if the HyperLogLog approximated cardinality is
|
|
6055
|
+
altered, then returns `True`.
|
|
6053
6056
|
|
|
6054
|
-
Otherwise, returns
|
|
6057
|
+
Otherwise, returns `False`.
|
|
6055
6058
|
|
|
6056
6059
|
Examples:
|
|
6057
6060
|
>>> await client.pfadd("hll_1", ["a", "b", "c" ])
|
|
6058
|
-
|
|
6061
|
+
True # A data structure was created or modified
|
|
6059
6062
|
>>> await client.pfadd("hll_2", [])
|
|
6060
|
-
|
|
6063
|
+
True # A new empty data structure was created
|
|
6061
6064
|
"""
|
|
6062
6065
|
return cast(
|
|
6063
|
-
|
|
6066
|
+
bool,
|
|
6064
6067
|
await self._execute_command(RequestType.PfAdd, [key] + elements),
|
|
6065
6068
|
)
|
|
6066
6069
|
|
|
@@ -6649,6 +6652,10 @@ class CoreCommands(Protocol):
|
|
|
6649
6652
|
args.append("REPLACE")
|
|
6650
6653
|
if absttl is True:
|
|
6651
6654
|
args.append("ABSTTL")
|
|
6655
|
+
if idletime is not None and frequency is not None:
|
|
6656
|
+
raise RequestError(
|
|
6657
|
+
"syntax error: IDLETIME and FREQ cannot be set at the same time."
|
|
6658
|
+
)
|
|
6652
6659
|
if idletime is not None:
|
|
6653
6660
|
args.extend(["IDLETIME", str(idletime)])
|
|
6654
6661
|
if frequency is not None:
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from typing import Dict, List, Mapping, Optional, Union, cast
|
|
6
6
|
|
|
7
7
|
from glide.async_commands.batch import Batch
|
|
8
|
+
from glide.async_commands.batch_options import BatchOptions
|
|
8
9
|
from glide.async_commands.command_args import ObjectType
|
|
9
10
|
from glide.async_commands.core import (
|
|
10
11
|
CoreCommands,
|
|
@@ -76,7 +77,7 @@ class StandaloneCommands(CoreCommands):
|
|
|
76
77
|
self,
|
|
77
78
|
batch: Batch,
|
|
78
79
|
raise_on_error: bool,
|
|
79
|
-
|
|
80
|
+
options: Optional[BatchOptions] = None,
|
|
80
81
|
) -> Optional[List[TResult]]:
|
|
81
82
|
"""
|
|
82
83
|
Executes a batch by processing the queued commands.
|
|
@@ -96,10 +97,7 @@ class StandaloneCommands(CoreCommands):
|
|
|
96
97
|
When set to ``False``, errors will be included as part of the batch response array, allowing
|
|
97
98
|
the caller to process both successful and failed commands together. In this case, error details
|
|
98
99
|
will be provided as instances of ``RequestError``.
|
|
99
|
-
|
|
100
|
-
to complete. This duration encompasses sending the request, awaiting a response from the server, and any
|
|
101
|
-
required reconnections or retries. If the specified timeout is exceeded for the request,
|
|
102
|
-
a timeout error will be raised. If not explicitly set, the client's default request timeout will be used.
|
|
100
|
+
options (Optional[BatchOptions]): A ``BatchOptions`` object containing execution options.
|
|
103
101
|
|
|
104
102
|
Returns:
|
|
105
103
|
Optional[List[TResult]]: An array of results, where each entry corresponds to a command's execution result.
|
|
@@ -125,33 +123,38 @@ class StandaloneCommands(CoreCommands):
|
|
|
125
123
|
# Expected Output: Pipeline Batch Result: [OK, OK, b'value1', b'value2']
|
|
126
124
|
|
|
127
125
|
Example (Atomic Batch - Transaction with options):
|
|
126
|
+
>>> from glide import BatchOptions
|
|
128
127
|
>>> transaction = Batch(is_atomic=True)
|
|
129
128
|
>>> transaction.set("key", "1")
|
|
130
129
|
>>> transaction.incr("key")
|
|
131
130
|
>>> transaction.custom_command(["get", "key"])
|
|
131
|
+
>>> options = BatchOptions(timeout=1000) # Set a timeout of 1000 milliseconds
|
|
132
132
|
>>> result = await client.exec(
|
|
133
133
|
... transaction,
|
|
134
134
|
... raise_on_error=False, # Do not raise an error on failure
|
|
135
|
-
...
|
|
135
|
+
... options=options
|
|
136
136
|
... )
|
|
137
137
|
>>> print(f"Transaction Result: {result}")
|
|
138
138
|
# Expected Output: Transaction Result: [OK, 2, b'2']
|
|
139
139
|
|
|
140
140
|
Example (Non-Atomic Batch - Pipeline with options):
|
|
141
|
+
>>> from glide import BatchOptions
|
|
141
142
|
>>> pipeline = Batch(is_atomic=False)
|
|
142
143
|
>>> pipeline.custom_command(["set", "key1", "value1"])
|
|
143
144
|
>>> pipeline.custom_command(["set", "key2", "value2"])
|
|
144
145
|
>>> pipeline.custom_command(["get", "key1"])
|
|
145
146
|
>>> pipeline.custom_command(["get", "key2"])
|
|
147
|
+
>>> options = BatchOptions(timeout=1000) # Set a timeout of 1000 milliseconds
|
|
146
148
|
>>> result = await client.exec(
|
|
147
149
|
... pipeline,
|
|
148
150
|
... raise_on_error=False, # Do not raise an error on failure
|
|
149
|
-
...
|
|
151
|
+
... options=options
|
|
150
152
|
... )
|
|
151
153
|
>>> print(f"Pipeline Result: {result}")
|
|
152
154
|
# Expected Output: Pipeline Result: [OK, OK, b'value1', b'value2']
|
|
153
155
|
"""
|
|
154
156
|
commands = batch.commands[:]
|
|
157
|
+
timeout = options.timeout if options else None
|
|
155
158
|
return await self._execute_batch(
|
|
156
159
|
commands,
|
|
157
160
|
is_atomic=batch.is_atomic,
|
glide/config.py
CHANGED
|
@@ -73,7 +73,7 @@ class BackoffStrategy:
|
|
|
73
73
|
"""
|
|
74
74
|
Represents the strategy used to determine how and when to reconnect, in case of connection failures.
|
|
75
75
|
The time between attempts grows exponentially, to the formula rand(0 .. factor * (exponentBase ^ N)), where N
|
|
76
|
-
is the number of failed attempts.
|
|
76
|
+
is the number of failed attempts, and `rand(...)` applies a jitter of up to `jitter_percent`% to introduce randomness and reduce retry storms.
|
|
77
77
|
Once the maximum value is reached, that will remain the time between retry attempts until a reconnect attempt is
|
|
78
78
|
successful.
|
|
79
79
|
The client will attempt to reconnect indefinitely.
|
|
@@ -147,25 +147,66 @@ class PeriodicChecksStatus(Enum):
|
|
|
147
147
|
"""
|
|
148
148
|
|
|
149
149
|
|
|
150
|
+
class TlsAdvancedConfiguration:
|
|
151
|
+
"""
|
|
152
|
+
Represents advanced TLS configuration settings.
|
|
153
|
+
|
|
154
|
+
Attributes:
|
|
155
|
+
use_insecure_tls (Optional[bool]): Whether to bypass TLS certificate verification.
|
|
156
|
+
|
|
157
|
+
- When set to True, the client skips certificate validation.
|
|
158
|
+
This is useful when connecting to servers or clusters using self-signed certificates,
|
|
159
|
+
or when DNS entries (e.g., CNAMEs) don't match certificate hostnames.
|
|
160
|
+
|
|
161
|
+
- This setting is typically used in development or testing environments. **It is
|
|
162
|
+
strongly discouraged in production**, as it introduces security risks such as man-in-the-middle attacks.
|
|
163
|
+
|
|
164
|
+
- Only valid if TLS is already enabled in the base client configuration.
|
|
165
|
+
Enabling it without TLS will result in a `ConfigurationError`.
|
|
166
|
+
|
|
167
|
+
- Default: False (verification is enforced).
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
def __init__(self, use_insecure_tls: Optional[bool] = None):
|
|
171
|
+
self.use_insecure_tls = use_insecure_tls
|
|
172
|
+
|
|
173
|
+
|
|
150
174
|
class AdvancedBaseClientConfiguration:
|
|
151
175
|
"""
|
|
152
176
|
Represents the advanced configuration settings for a base Glide client.
|
|
153
177
|
|
|
154
178
|
Attributes:
|
|
155
179
|
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
|
|
180
|
+
This applies both during initial client creation and any reconnection that may occur during request processing.
|
|
157
181
|
**Note**: A high connection timeout may lead to prolonged blocking of the entire command pipeline.
|
|
158
|
-
If not explicitly set, a default value of
|
|
182
|
+
If not explicitly set, a default value of 2000 milliseconds will be used.
|
|
183
|
+
tls_config (Optional[TlsAdvancedConfiguration]): The advanced TLS configuration settings.
|
|
184
|
+
This allows for more granular control of TLS behavior, such as enabling an insecure mode
|
|
185
|
+
that bypasses certificate validation.
|
|
159
186
|
"""
|
|
160
187
|
|
|
161
|
-
def __init__(
|
|
188
|
+
def __init__(
|
|
189
|
+
self,
|
|
190
|
+
connection_timeout: Optional[int] = None,
|
|
191
|
+
tls_config: Optional[TlsAdvancedConfiguration] = None,
|
|
192
|
+
):
|
|
162
193
|
self.connection_timeout = connection_timeout
|
|
194
|
+
self.tls_config = tls_config
|
|
163
195
|
|
|
164
196
|
def _create_a_protobuf_conn_request(
|
|
165
197
|
self, request: ConnectionRequest
|
|
166
198
|
) -> ConnectionRequest:
|
|
167
199
|
if self.connection_timeout:
|
|
168
200
|
request.connection_timeout = self.connection_timeout
|
|
201
|
+
|
|
202
|
+
if self.tls_config and self.tls_config.use_insecure_tls:
|
|
203
|
+
if request.tls_mode == TlsMode.SecureTls:
|
|
204
|
+
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
|
+
)
|
|
209
|
+
|
|
169
210
|
return request
|
|
170
211
|
|
|
171
212
|
|
|
@@ -187,14 +228,15 @@ class BaseClientConfiguration:
|
|
|
187
228
|
].
|
|
188
229
|
|
|
189
230
|
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
|
|
231
|
+
Should match the TLS configuration of the server/cluster, otherwise the connection attempt will fail.
|
|
232
|
+
For advanced tls configuration, please use `AdvancedBaseClientConfiguration`.
|
|
191
233
|
credentials (ServerCredentials): Credentials for authentication process.
|
|
192
234
|
If none are set, the client will not authenticate itself with the server.
|
|
193
235
|
read_from (ReadFrom): If not set, `PRIMARY` will be used.
|
|
194
236
|
request_timeout (Optional[int]): The duration in milliseconds that the client should wait for a request to
|
|
195
237
|
complete.
|
|
196
238
|
This duration encompasses sending the request, awaiting for a response from the server, and any required
|
|
197
|
-
|
|
239
|
+
reconnection or retries.
|
|
198
240
|
If the specified timeout is exceeded for a pending request, it will result in a timeout error. If not
|
|
199
241
|
explicitly set, a default value of 250 milliseconds will be used.
|
|
200
242
|
reconnect_strategy (Optional[BackoffStrategy]): Strategy used to determine how and when to reconnect, in case of
|
|
@@ -214,6 +256,25 @@ class BaseClientConfiguration:
|
|
|
214
256
|
If ReadFrom strategy is AZAffinityReplicasAndPrimary, this setting ensures that readonly commands are directed
|
|
215
257
|
to nodes (first replicas then primary) within the specified AZ if they exist.
|
|
216
258
|
advanced_config (Optional[AdvancedBaseClientConfiguration]): Advanced configuration settings for the client.
|
|
259
|
+
|
|
260
|
+
lazy_connect (Optional[bool]): Enables lazy connection mode, where physical connections to the server(s)
|
|
261
|
+
are deferred until the first command is sent. This can reduce startup latency and allow for client
|
|
262
|
+
creation in disconnected environments.
|
|
263
|
+
|
|
264
|
+
When set to `True`, the client will not attempt to connect to the specified nodes during
|
|
265
|
+
initialization. Instead, connections will be established only when a command is actually executed.
|
|
266
|
+
|
|
267
|
+
Note that the first command executed with lazy connections may experience additional latency
|
|
268
|
+
as it needs to establish the connection first. During this initial connection, the standard
|
|
269
|
+
request timeout does not apply yet - instead, the connection establishment is governed by
|
|
270
|
+
`AdvancedBaseClientConfiguration.connection_timeout`. The request timeout (`request_timeout`)
|
|
271
|
+
only begins counting after the connection has been successfully established. This behavior
|
|
272
|
+
can effectively increase the total time needed for the first command to complete.
|
|
273
|
+
|
|
274
|
+
This setting applies to both standalone and cluster modes. Note that if an operation is
|
|
275
|
+
attempted and connection fails (e.g., unreachable nodes), errors will surface at that point.
|
|
276
|
+
|
|
277
|
+
If not set, connections are established immediately during client creation (equivalent to `False`).
|
|
217
278
|
"""
|
|
218
279
|
|
|
219
280
|
def __init__(
|
|
@@ -229,6 +290,7 @@ class BaseClientConfiguration:
|
|
|
229
290
|
inflight_requests_limit: Optional[int] = None,
|
|
230
291
|
client_az: Optional[str] = None,
|
|
231
292
|
advanced_config: Optional[AdvancedBaseClientConfiguration] = None,
|
|
293
|
+
lazy_connect: Optional[bool] = None,
|
|
232
294
|
):
|
|
233
295
|
self.addresses = addresses
|
|
234
296
|
self.use_tls = use_tls
|
|
@@ -241,6 +303,7 @@ class BaseClientConfiguration:
|
|
|
241
303
|
self.inflight_requests_limit = inflight_requests_limit
|
|
242
304
|
self.client_az = client_az
|
|
243
305
|
self.advanced_config = advanced_config
|
|
306
|
+
self.lazy_connect = lazy_connect
|
|
244
307
|
|
|
245
308
|
if read_from == ReadFrom.AZ_AFFINITY and not client_az:
|
|
246
309
|
raise ValueError(
|
|
@@ -301,6 +364,8 @@ class BaseClientConfiguration:
|
|
|
301
364
|
if self.advanced_config:
|
|
302
365
|
self.advanced_config._create_a_protobuf_conn_request(request)
|
|
303
366
|
|
|
367
|
+
if self.lazy_connect is not None:
|
|
368
|
+
request.lazy_connect = self.lazy_connect
|
|
304
369
|
return request
|
|
305
370
|
|
|
306
371
|
def _is_pubsub_configured(self) -> bool:
|
|
@@ -317,9 +382,13 @@ class AdvancedGlideClientConfiguration(AdvancedBaseClientConfiguration):
|
|
|
317
382
|
Represents the advanced configuration settings for a Standalone Glide client.
|
|
318
383
|
"""
|
|
319
384
|
|
|
320
|
-
def __init__(
|
|
385
|
+
def __init__(
|
|
386
|
+
self,
|
|
387
|
+
connection_timeout: Optional[int] = None,
|
|
388
|
+
tls_config: Optional[TlsAdvancedConfiguration] = None,
|
|
389
|
+
):
|
|
321
390
|
|
|
322
|
-
super().__init__(connection_timeout)
|
|
391
|
+
super().__init__(connection_timeout, tls_config)
|
|
323
392
|
|
|
324
393
|
|
|
325
394
|
class GlideClientConfiguration(BaseClientConfiguration):
|
|
@@ -337,12 +406,13 @@ class GlideClientConfiguration(BaseClientConfiguration):
|
|
|
337
406
|
]
|
|
338
407
|
|
|
339
408
|
use_tls (bool): True if communication with the cluster should use Transport Level Security.
|
|
409
|
+
Please use `AdvancedGlideClusterClientConfiguration`.
|
|
340
410
|
credentials (ServerCredentials): Credentials for authentication process.
|
|
341
411
|
If none are set, the client will not authenticate itself with the server.
|
|
342
412
|
read_from (ReadFrom): If not set, `PRIMARY` will be used.
|
|
343
413
|
request_timeout (Optional[int]): The duration in milliseconds that the client should wait for a request to complete.
|
|
344
414
|
This duration encompasses sending the request, awaiting for a response from the server, and any required
|
|
345
|
-
|
|
415
|
+
reconnection or retries.
|
|
346
416
|
If the specified timeout is exceeded for a pending request, it will result in a timeout error.
|
|
347
417
|
If not explicitly set, a default value of 250 milliseconds will be used.
|
|
348
418
|
reconnect_strategy (Optional[BackoffStrategy]): Strategy used to determine how and when to reconnect, in case of
|
|
@@ -414,6 +484,7 @@ class GlideClientConfiguration(BaseClientConfiguration):
|
|
|
414
484
|
inflight_requests_limit: Optional[int] = None,
|
|
415
485
|
client_az: Optional[str] = None,
|
|
416
486
|
advanced_config: Optional[AdvancedGlideClientConfiguration] = None,
|
|
487
|
+
lazy_connect: Optional[bool] = None,
|
|
417
488
|
):
|
|
418
489
|
super().__init__(
|
|
419
490
|
addresses=addresses,
|
|
@@ -427,6 +498,7 @@ class GlideClientConfiguration(BaseClientConfiguration):
|
|
|
427
498
|
inflight_requests_limit=inflight_requests_limit,
|
|
428
499
|
client_az=client_az,
|
|
429
500
|
advanced_config=advanced_config,
|
|
501
|
+
lazy_connect=lazy_connect,
|
|
430
502
|
)
|
|
431
503
|
self.database_id = database_id
|
|
432
504
|
self.pubsub_subscriptions = pubsub_subscriptions
|
|
@@ -479,8 +551,12 @@ class AdvancedGlideClusterClientConfiguration(AdvancedBaseClientConfiguration):
|
|
|
479
551
|
Represents the advanced configuration settings for a Glide Cluster client.
|
|
480
552
|
"""
|
|
481
553
|
|
|
482
|
-
def __init__(
|
|
483
|
-
|
|
554
|
+
def __init__(
|
|
555
|
+
self,
|
|
556
|
+
connection_timeout: Optional[int] = None,
|
|
557
|
+
tls_config: Optional[TlsAdvancedConfiguration] = None,
|
|
558
|
+
):
|
|
559
|
+
super().__init__(connection_timeout, tls_config)
|
|
484
560
|
|
|
485
561
|
|
|
486
562
|
class GlideClusterClientConfiguration(BaseClientConfiguration):
|
|
@@ -497,12 +573,13 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
|
|
|
497
573
|
]
|
|
498
574
|
|
|
499
575
|
use_tls (bool): True if communication with the cluster should use Transport Level Security.
|
|
576
|
+
For advanced tls configuration, please use `AdvancedGlideClusterClientConfiguration`.
|
|
500
577
|
credentials (ServerCredentials): Credentials for authentication process.
|
|
501
578
|
If none are set, the client will not authenticate itself with the server.
|
|
502
579
|
read_from (ReadFrom): If not set, `PRIMARY` will be used.
|
|
503
580
|
request_timeout (Optional[int]): The duration in milliseconds that the client should wait for a request to complete.
|
|
504
581
|
This duration encompasses sending the request, awaiting for a response from the server, and any required
|
|
505
|
-
|
|
582
|
+
reconnection or retries.
|
|
506
583
|
If the specified timeout is exceeded for a pending request, it will result in a timeout error. If not explicitly
|
|
507
584
|
set, a default value of 250 milliseconds will be used.
|
|
508
585
|
reconnect_strategy (Optional[BackoffStrategy]): Strategy used to determine how and when to reconnect, in case of
|
|
@@ -586,6 +663,7 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
|
|
|
586
663
|
inflight_requests_limit: Optional[int] = None,
|
|
587
664
|
client_az: Optional[str] = None,
|
|
588
665
|
advanced_config: Optional[AdvancedGlideClusterClientConfiguration] = None,
|
|
666
|
+
lazy_connect: Optional[bool] = None,
|
|
589
667
|
):
|
|
590
668
|
super().__init__(
|
|
591
669
|
addresses=addresses,
|
|
@@ -599,6 +677,7 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
|
|
|
599
677
|
inflight_requests_limit=inflight_requests_limit,
|
|
600
678
|
client_az=client_az,
|
|
601
679
|
advanced_config=advanced_config,
|
|
680
|
+
lazy_connect=lazy_connect,
|
|
602
681
|
)
|
|
603
682
|
self.periodic_checks = periodic_checks
|
|
604
683
|
self.pubsub_subscriptions = pubsub_subscriptions
|
|
@@ -637,6 +716,8 @@ class GlideClusterClientConfiguration(BaseClientConfiguration):
|
|
|
637
716
|
for channel_pattern in channels_patterns:
|
|
638
717
|
entry.channels_or_patterns.append(str.encode(channel_pattern))
|
|
639
718
|
|
|
719
|
+
if self.lazy_connect is not None:
|
|
720
|
+
request.lazy_connect = self.lazy_connect
|
|
640
721
|
return request
|
|
641
722
|
|
|
642
723
|
def _is_pubsub_configured(self) -> bool:
|
|
Binary file
|
glide/glide.pyi
CHANGED
|
@@ -27,6 +27,28 @@ class ClusterScanCursor:
|
|
|
27
27
|
def get_cursor(self) -> str: ...
|
|
28
28
|
def is_finished(self) -> bool: ...
|
|
29
29
|
|
|
30
|
+
class OpenTelemetryConfig:
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
traces: Optional[OpenTelemetryTracesConfig] = None,
|
|
34
|
+
metrics: Optional[OpenTelemetryMetricsConfig] = None,
|
|
35
|
+
flush_interval_ms: Optional[int] = None,
|
|
36
|
+
) -> None: ...
|
|
37
|
+
def get_traces(self) -> Optional[OpenTelemetryTracesConfig]: ...
|
|
38
|
+
def set_traces(self, traces: OpenTelemetryTracesConfig) -> None: ...
|
|
39
|
+
def get_metrics(self) -> Optional[OpenTelemetryMetricsConfig]: ...
|
|
40
|
+
|
|
41
|
+
class OpenTelemetryTracesConfig:
|
|
42
|
+
def __init__(
|
|
43
|
+
self, endpoint: str, sample_percentage: Optional[int] = None
|
|
44
|
+
) -> None: ...
|
|
45
|
+
def get_endpoint(self) -> str: ...
|
|
46
|
+
def get_sample_percentage(self) -> Optional[int]: ...
|
|
47
|
+
|
|
48
|
+
class OpenTelemetryMetricsConfig:
|
|
49
|
+
def __init__(self, endpoint: str) -> None: ...
|
|
50
|
+
def get_endpoint(self) -> str: ...
|
|
51
|
+
|
|
30
52
|
def start_socket_listener_external(init_callback: Callable) -> None: ...
|
|
31
53
|
def value_from_pointer(pointer: int) -> TResult: ...
|
|
32
54
|
def create_leaked_value(message: str) -> int: ...
|
|
@@ -34,3 +56,6 @@ def create_leaked_bytes_vec(args_vec: List[bytes]) -> int: ...
|
|
|
34
56
|
def get_statistics() -> dict: ...
|
|
35
57
|
def py_init(level: Optional[Level], file_name: Optional[str]) -> Level: ...
|
|
36
58
|
def py_log(log_level: Level, log_identifier: str, message: str) -> None: ...
|
|
59
|
+
def create_otel_span(name: str) -> int: ...
|
|
60
|
+
def drop_otel_span(span_ptr: int) -> None: ...
|
|
61
|
+
def init_opentelemetry(config: OpenTelemetryConfig) -> None: ...
|
glide/glide_client.py
CHANGED
|
@@ -36,6 +36,7 @@ from glide.exceptions import (
|
|
|
36
36
|
)
|
|
37
37
|
from glide.logger import Level as LogLevel
|
|
38
38
|
from glide.logger import Logger as ClientLogger
|
|
39
|
+
from glide.opentelemetry import OpenTelemetry
|
|
39
40
|
from glide.protobuf.command_request_pb2 import Command, CommandRequest, RequestType
|
|
40
41
|
from glide.protobuf.connection_request_pb2 import ConnectionRequest
|
|
41
42
|
from glide.protobuf.response_pb2 import RequestErrorType, Response
|
|
@@ -47,6 +48,8 @@ from .glide import (
|
|
|
47
48
|
MAX_REQUEST_ARGS_LEN,
|
|
48
49
|
ClusterScanCursor,
|
|
49
50
|
create_leaked_bytes_vec,
|
|
51
|
+
create_otel_span,
|
|
52
|
+
drop_otel_span,
|
|
50
53
|
get_statistics,
|
|
51
54
|
start_socket_listener_external,
|
|
52
55
|
value_from_pointer,
|
|
@@ -410,6 +413,13 @@ class BaseClient(CoreCommands):
|
|
|
410
413
|
raise ClosingError(
|
|
411
414
|
"Unable to execute requests; the client is closed. Please create a new client."
|
|
412
415
|
)
|
|
416
|
+
|
|
417
|
+
# Create span if OpenTelemetry is configured and sampling indicates we should trace
|
|
418
|
+
span = None
|
|
419
|
+
if OpenTelemetry.should_sample():
|
|
420
|
+
command_name = RequestType.Name(request_type)
|
|
421
|
+
span = create_otel_span(command_name)
|
|
422
|
+
|
|
413
423
|
request = CommandRequest()
|
|
414
424
|
request.callback_idx = self._get_callback_index()
|
|
415
425
|
request.single_command.request_type = request_type
|
|
@@ -424,6 +434,11 @@ class BaseClient(CoreCommands):
|
|
|
424
434
|
request.single_command.args_vec_pointer = create_leaked_bytes_vec(
|
|
425
435
|
encoded_args
|
|
426
436
|
)
|
|
437
|
+
|
|
438
|
+
# Add span pointer to request if span was created
|
|
439
|
+
if span:
|
|
440
|
+
request.root_span_ptr = span
|
|
441
|
+
|
|
427
442
|
set_protobuf_route(request, route)
|
|
428
443
|
return await self._write_request_await_response(request)
|
|
429
444
|
|
|
@@ -441,6 +456,14 @@ class BaseClient(CoreCommands):
|
|
|
441
456
|
raise ClosingError(
|
|
442
457
|
"Unable to execute requests; the client is closed. Please create a new client."
|
|
443
458
|
)
|
|
459
|
+
|
|
460
|
+
# Create span if OpenTelemetry is configured and sampling indicates we should trace
|
|
461
|
+
span = None
|
|
462
|
+
|
|
463
|
+
if OpenTelemetry.should_sample():
|
|
464
|
+
# Use "Batch" as span name for batches
|
|
465
|
+
span = create_otel_span("Batch")
|
|
466
|
+
|
|
444
467
|
request = CommandRequest()
|
|
445
468
|
request.callback_idx = self._get_callback_index()
|
|
446
469
|
batch_commands = []
|
|
@@ -462,6 +485,11 @@ class BaseClient(CoreCommands):
|
|
|
462
485
|
request.batch.timeout = timeout
|
|
463
486
|
request.batch.retry_server_error = retry_server_error
|
|
464
487
|
request.batch.retry_connection_error = retry_connection_error
|
|
488
|
+
|
|
489
|
+
# Add span pointer to request if span was created
|
|
490
|
+
if span:
|
|
491
|
+
request.root_span_ptr = span
|
|
492
|
+
|
|
465
493
|
set_protobuf_route(request, route)
|
|
466
494
|
return await self._write_request_await_response(request)
|
|
467
495
|
|
|
@@ -656,6 +684,10 @@ class BaseClient(CoreCommands):
|
|
|
656
684
|
else:
|
|
657
685
|
res_future.set_result(None)
|
|
658
686
|
|
|
687
|
+
# Clean up span if it was created
|
|
688
|
+
if response.HasField("root_span_ptr"):
|
|
689
|
+
drop_otel_span(response.root_span_ptr)
|
|
690
|
+
|
|
659
691
|
async def _process_push(self, response: Response) -> None:
|
|
660
692
|
if response.HasField("closing_error") or not response.HasField("resp_pointer"):
|
|
661
693
|
err_msg = (
|
glide/logger.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import traceback
|
|
5
6
|
from enum import Enum
|
|
6
7
|
from typing import Optional
|
|
7
8
|
|
|
@@ -20,13 +21,12 @@ class Level(Enum):
|
|
|
20
21
|
|
|
21
22
|
class Logger:
|
|
22
23
|
"""
|
|
23
|
-
A singleton class that allows logging which is consistent with logs from the internal
|
|
24
|
+
A singleton class that allows logging which is consistent with logs from the internal GLIDE core.
|
|
24
25
|
The logger can be set up in 2 ways -
|
|
25
26
|
1. By calling Logger.init, which configures the logger only if it wasn't previously configured.
|
|
26
27
|
2. By calling Logger.set_logger_config, which replaces the existing configuration, and means that new logs will not be
|
|
27
28
|
saved with the logs that were sent before the call.
|
|
28
|
-
If
|
|
29
|
-
by the Rust core.
|
|
29
|
+
If none of these functions are called, the first log attempt will initialize a new logger with default configuration.
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
32
|
_instance = None
|
|
@@ -38,48 +38,60 @@ class Logger:
|
|
|
38
38
|
|
|
39
39
|
@classmethod
|
|
40
40
|
def init(cls, level: Optional[Level] = None, file_name: Optional[str] = None):
|
|
41
|
-
"""
|
|
41
|
+
"""
|
|
42
42
|
Initialize a logger if it wasn't initialized before - this method is meant to be used when there is no intention to
|
|
43
43
|
replace an existing logger.
|
|
44
|
-
The logger will filter all logs with a level lower than the given level
|
|
45
|
-
If given a
|
|
44
|
+
The logger will filter all logs with a level lower than the given level.
|
|
45
|
+
If given a file_name argument, will write the logs to files postfixed with file_name. If file_name isn't provided,
|
|
46
46
|
the logs will be written to the console.
|
|
47
|
+
|
|
47
48
|
Args:
|
|
48
49
|
level (Optional[Level]): Set the logger level to one of [ERROR, WARN, INFO, DEBUG, TRACE, OFF].
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
If log level isn't provided, the logger will be configured with default configuration.
|
|
51
|
+
To turn off logging completely, set the level to Level.OFF.
|
|
52
|
+
file_name (Optional[str]): If provided the target of the logs will be the file mentioned.
|
|
53
|
+
Otherwise, logs will be printed to the console.
|
|
53
54
|
"""
|
|
54
55
|
if cls._instance is None:
|
|
55
56
|
cls._instance = cls(level, file_name)
|
|
56
57
|
|
|
57
58
|
@classmethod
|
|
58
|
-
def log(
|
|
59
|
-
|
|
59
|
+
def log(
|
|
60
|
+
cls,
|
|
61
|
+
log_level: Level,
|
|
62
|
+
log_identifier: str,
|
|
63
|
+
message: str,
|
|
64
|
+
err: Optional[Exception] = None,
|
|
65
|
+
):
|
|
66
|
+
"""
|
|
67
|
+
Logs the provided message if the provided log level is lower then the logger level.
|
|
60
68
|
|
|
61
69
|
Args:
|
|
62
|
-
log_level (Level): The log level of the provided message
|
|
70
|
+
log_level (Level): The log level of the provided message.
|
|
63
71
|
log_identifier (str): The log identifier should give the log a context.
|
|
64
72
|
message (str): The message to log.
|
|
73
|
+
err (Optional[Exception]): The exception or error to log.
|
|
65
74
|
"""
|
|
66
75
|
if not cls._instance:
|
|
67
76
|
cls._instance = cls(None)
|
|
68
77
|
if not log_level.value.is_lower(Logger.logger_level):
|
|
69
78
|
return
|
|
79
|
+
if err:
|
|
80
|
+
message = f"{message}: {traceback.format_exception(err)}"
|
|
70
81
|
py_log(log_level.value, log_identifier, message)
|
|
71
82
|
|
|
72
83
|
@classmethod
|
|
73
84
|
def set_logger_config(
|
|
74
85
|
cls, level: Optional[Level] = None, file_name: Optional[str] = None
|
|
75
86
|
):
|
|
76
|
-
"""
|
|
87
|
+
"""
|
|
88
|
+
Creates a new logger instance and configure it with the provided log level and file name.
|
|
77
89
|
|
|
78
90
|
Args:
|
|
79
91
|
level (Optional[Level]): Set the logger level to one of [ERROR, WARN, INFO, DEBUG, TRACE, OFF].
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
92
|
+
If log level isn't provided, the logger will be configured with default configuration.
|
|
93
|
+
To turn off logging completely, set the level to OFF.
|
|
94
|
+
file_name (Optional[str]): If provided the target of the logs will be the file mentioned.
|
|
95
|
+
Otherwise, logs will be printed to the console.
|
|
84
96
|
"""
|
|
85
97
|
Logger._instance = Logger(level, file_name)
|