fix
All checks were successful
Deploy Prod / Build (pull_request) Successful in 9s
Deploy Prod / Push (pull_request) Successful in 12s
Deploy Prod / Deploy prod (pull_request) Successful in 10s

This commit is contained in:
Egor Matveev
2024-12-28 22:48:16 +03:00
parent c1249bfcd0
commit 6c6a549aff
2532 changed files with 562109 additions and 1 deletions

View File

@@ -0,0 +1,108 @@
import redis
from redis._parsers.helpers import bool_ok
from ..helpers import get_protocol_version, parse_to_list
from .commands import (
ALTER_CMD,
CREATE_CMD,
CREATERULE_CMD,
DEL_CMD,
DELETERULE_CMD,
GET_CMD,
INFO_CMD,
MGET_CMD,
MRANGE_CMD,
MREVRANGE_CMD,
QUERYINDEX_CMD,
RANGE_CMD,
REVRANGE_CMD,
TimeSeriesCommands,
)
from .info import TSInfo
from .utils import parse_get, parse_m_get, parse_m_range, parse_range
class TimeSeries(TimeSeriesCommands):
"""
This class subclasses redis-py's `Redis` and implements RedisTimeSeries's
commands (prefixed with "ts").
The client allows to interact with RedisTimeSeries and use all of it's
functionality.
"""
def __init__(self, client=None, **kwargs):
"""Create a new RedisTimeSeries client."""
# Set the module commands' callbacks
self._MODULE_CALLBACKS = {
ALTER_CMD: bool_ok,
CREATE_CMD: bool_ok,
CREATERULE_CMD: bool_ok,
DELETERULE_CMD: bool_ok,
}
_RESP2_MODULE_CALLBACKS = {
DEL_CMD: int,
GET_CMD: parse_get,
INFO_CMD: TSInfo,
MGET_CMD: parse_m_get,
MRANGE_CMD: parse_m_range,
MREVRANGE_CMD: parse_m_range,
RANGE_CMD: parse_range,
REVRANGE_CMD: parse_range,
QUERYINDEX_CMD: parse_to_list,
}
_RESP3_MODULE_CALLBACKS = {}
self.client = client
self.execute_command = client.execute_command
if get_protocol_version(self.client) in ["3", 3]:
self._MODULE_CALLBACKS.update(_RESP3_MODULE_CALLBACKS)
else:
self._MODULE_CALLBACKS.update(_RESP2_MODULE_CALLBACKS)
for k, v in self._MODULE_CALLBACKS.items():
self.client.set_response_callback(k, v)
def pipeline(self, transaction=True, shard_hint=None):
"""Creates a pipeline for the TimeSeries module, that can be used
for executing only TimeSeries commands and core commands.
Usage example:
r = redis.Redis()
pipe = r.ts().pipeline()
for i in range(100):
pipeline.add("with_pipeline", i, 1.1 * i)
pipeline.execute()
"""
if isinstance(self.client, redis.RedisCluster):
p = ClusterPipeline(
nodes_manager=self.client.nodes_manager,
commands_parser=self.client.commands_parser,
startup_nodes=self.client.nodes_manager.startup_nodes,
result_callbacks=self.client.result_callbacks,
cluster_response_callbacks=self.client.cluster_response_callbacks,
cluster_error_retry_attempts=self.client.cluster_error_retry_attempts,
read_from_replicas=self.client.read_from_replicas,
reinitialize_steps=self.client.reinitialize_steps,
lock=self.client._lock,
)
else:
p = Pipeline(
connection_pool=self.client.connection_pool,
response_callbacks=self._MODULE_CALLBACKS,
transaction=transaction,
shard_hint=shard_hint,
)
return p
class ClusterPipeline(TimeSeriesCommands, redis.cluster.ClusterPipeline):
"""Cluster pipeline for the module."""
class Pipeline(TimeSeriesCommands, redis.client.Pipeline):
"""Pipeline for the module."""

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,91 @@
from ..helpers import nativestr
from .utils import list_to_dict
class TSInfo:
"""
Hold information and statistics on the time-series.
Can be created using ``tsinfo`` command
https://redis.io/docs/latest/commands/ts.info/
"""
rules = []
labels = []
sourceKey = None
chunk_count = None
memory_usage = None
total_samples = None
retention_msecs = None
last_time_stamp = None
first_time_stamp = None
max_samples_per_chunk = None
chunk_size = None
duplicate_policy = None
def __init__(self, args):
"""
Hold information and statistics on the time-series.
The supported params that can be passed as args:
rules:
A list of compaction rules of the time series.
sourceKey:
Key name for source time series in case the current series
is a target of a rule.
chunkCount:
Number of Memory Chunks used for the time series.
memoryUsage:
Total number of bytes allocated for the time series.
totalSamples:
Total number of samples in the time series.
labels:
A list of label-value pairs that represent the metadata
labels of the time series.
retentionTime:
Retention time, in milliseconds, for the time series.
lastTimestamp:
Last timestamp present in the time series.
firstTimestamp:
First timestamp present in the time series.
maxSamplesPerChunk:
Deprecated.
chunkSize:
Amount of memory, in bytes, allocated for data.
duplicatePolicy:
Policy that will define handling of duplicate samples.
Can read more about on
https://redis.io/docs/latest/develop/data-types/timeseries/configuration/#duplicate_policy
"""
response = dict(zip(map(nativestr, args[::2]), args[1::2]))
self.rules = response.get("rules")
self.source_key = response.get("sourceKey")
self.chunk_count = response.get("chunkCount")
self.memory_usage = response.get("memoryUsage")
self.total_samples = response.get("totalSamples")
self.labels = list_to_dict(response.get("labels"))
self.retention_msecs = response.get("retentionTime")
self.last_timestamp = response.get("lastTimestamp")
self.first_timestamp = response.get("firstTimestamp")
if "maxSamplesPerChunk" in response:
self.max_samples_per_chunk = response["maxSamplesPerChunk"]
self.chunk_size = (
self.max_samples_per_chunk * 16
) # backward compatible changes
if "chunkSize" in response:
self.chunk_size = response["chunkSize"]
if "duplicatePolicy" in response:
self.duplicate_policy = response["duplicatePolicy"]
if isinstance(self.duplicate_policy, bytes):
self.duplicate_policy = self.duplicate_policy.decode()
def get(self, item):
try:
return self.__getitem__(item)
except AttributeError:
return None
def __getitem__(self, item):
return getattr(self, item)

View File

@@ -0,0 +1,44 @@
from ..helpers import nativestr
def list_to_dict(aList):
return {nativestr(aList[i][0]): nativestr(aList[i][1]) for i in range(len(aList))}
def parse_range(response, **kwargs):
"""Parse range response. Used by TS.RANGE and TS.REVRANGE."""
return [tuple((r[0], float(r[1]))) for r in response]
def parse_m_range(response):
"""Parse multi range response. Used by TS.MRANGE and TS.MREVRANGE."""
res = []
for item in response:
res.append({nativestr(item[0]): [list_to_dict(item[1]), parse_range(item[2])]})
return sorted(res, key=lambda d: list(d.keys()))
def parse_get(response):
"""Parse get response. Used by TS.GET."""
if not response:
return None
return int(response[0]), float(response[1])
def parse_m_get(response):
"""Parse multi get response. Used by TS.MGET."""
res = []
for item in response:
if not item[2]:
res.append({nativestr(item[0]): [list_to_dict(item[1]), None, None]})
else:
res.append(
{
nativestr(item[0]): [
list_to_dict(item[1]),
int(item[2][0]),
float(item[2][1]),
]
}
)
return sorted(res, key=lambda d: list(d.keys()))