Skip to content

Commit 647b0cf

Browse files
authored
Added testing with Timeseries samples with NaN values (#3932)
1 parent faf917a commit 647b0cf

File tree

3 files changed

+435
-7
lines changed

3 files changed

+435
-7
lines changed

redis/commands/timeseries/commands.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def add(
170170
self,
171171
key: KeyT,
172172
timestamp: Union[int, str],
173-
value: Number,
173+
value: Union[Number, str],
174174
retention_msecs: Optional[int] = None,
175175
uncompressed: Optional[bool] = False,
176176
labels: Optional[Dict[str, str]] = None,
@@ -251,7 +251,7 @@ def add(
251251

252252
return self.execute_command(ADD_CMD, *params)
253253

254-
def madd(self, ktv_tuples: List[Tuple[KeyT, Union[int, str], Number]]):
254+
def madd(self, ktv_tuples: List[Tuple[KeyT, Union[int, str], Union[Number, str]]]):
255255
"""
256256
Append new samples to one or more time series.
257257
@@ -507,7 +507,7 @@ def createrule(
507507
aggregation_type:
508508
Aggregation type: One of the following:
509509
[`avg`, `sum`, `min`, `max`, `range`, `count`, `first`, `last`, `std.p`,
510-
`std.s`, `var.p`, `var.s`, `twa`]
510+
`std.s`, `var.p`, `var.s`, `twa`, 'countNaN', 'countAll']
511511
bucket_size_msec:
512512
Duration of each bucket, in milliseconds.
513513
align_timestamp:
@@ -593,7 +593,7 @@ def range(
593593
aggregation_type:
594594
Optional aggregation type. Can be one of [`avg`, `sum`, `min`, `max`,
595595
`range`, `count`, `first`, `last`, `std.p`, `std.s`, `var.p`, `var.s`,
596-
`twa`]
596+
`twa`, 'countNaN', 'countAll']
597597
bucket_size_msec:
598598
Time bucket for aggregation in milliseconds.
599599
filter_by_ts:
@@ -669,7 +669,7 @@ def revrange(
669669
aggregation_type:
670670
Optional aggregation type. Can be one of [`avg`, `sum`, `min`, `max`,
671671
`range`, `count`, `first`, `last`, `std.p`, `std.s`, `var.p`, `var.s`,
672-
`twa`]
672+
`twa`, 'countNaN', 'countAll']
673673
bucket_size_msec:
674674
Time bucket for aggregation in milliseconds.
675675
filter_by_ts:
@@ -783,7 +783,7 @@ def mrange(
783783
aggregation_type:
784784
Optional aggregation type. Can be one of [`avg`, `sum`, `min`, `max`,
785785
`range`, `count`, `first`, `last`, `std.p`, `std.s`, `var.p`, `var.s`,
786-
`twa`]
786+
`twa`, 'countNaN', 'countAll']
787787
bucket_size_msec:
788788
Time bucket for aggregation in milliseconds.
789789
with_labels:
@@ -877,7 +877,7 @@ def mrevrange(
877877
aggregation_type:
878878
Optional aggregation type. Can be one of [`avg`, `sum`, `min`, `max`,
879879
`range`, `count`, `first`, `last`, `std.p`, `std.s`, `var.p`, `var.s`,
880-
`twa`].
880+
`twa`, 'countNaN', 'countAll'].
881881
bucket_size_msec:
882882
Time bucket for aggregation in milliseconds.
883883
with_labels:

tests/test_asyncio/test_timeseries.py

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,3 +915,217 @@ async def test_decrby_with_insertion_filters(decoded_r: redis.Redis):
915915

916916
data_points = await decoded_r.ts().range("time-series-1", "-", "+")
917917
assert_resp_response(decoded_r, data_points, [(1000, -11.1)], [[1000, -11.1]])
918+
919+
920+
@pytest.mark.redismod
921+
@skip_if_server_version_lt("8.5.0")
922+
async def test_range_with_count_nan_count_all_aggregators(decoded_r: redis.Redis):
923+
await decoded_r.ts().create(
924+
"temperature:2:32",
925+
)
926+
927+
# Fill with values
928+
assert await decoded_r.ts().add("temperature:2:32", 1000, "NaN") == 1000
929+
assert await decoded_r.ts().add("temperature:2:32", 1003, 25) == 1003
930+
assert await decoded_r.ts().add("temperature:2:32", 1005, "NaN") == 1005
931+
assert await decoded_r.ts().add("temperature:2:32", 1006, "NaN") == 1006
932+
933+
# Ensure we count only NaN values
934+
data_points = await decoded_r.ts().range(
935+
"temperature:2:32",
936+
1000,
937+
1006,
938+
aggregation_type="countNan",
939+
bucket_size_msec=1000,
940+
)
941+
assert_resp_response(
942+
decoded_r,
943+
data_points,
944+
[(1000, 3)],
945+
[[1000, 3]],
946+
)
947+
948+
# Ensure we count ALL values
949+
data_points = await decoded_r.ts().range(
950+
"temperature:2:32",
951+
1000,
952+
1006,
953+
aggregation_type="countAll",
954+
bucket_size_msec=1000,
955+
)
956+
assert_resp_response(
957+
decoded_r,
958+
data_points,
959+
[(1000, 4)],
960+
[[1000, 4]],
961+
)
962+
963+
964+
@pytest.mark.redismod
965+
@skip_if_server_version_lt("8.5.0")
966+
async def test_rev_range_with_count_nan_count_all_aggregators(decoded_r: redis.Redis):
967+
await decoded_r.ts().create(
968+
"temperature:2:32",
969+
)
970+
971+
# Fill with values
972+
assert await decoded_r.ts().add("temperature:2:32", 1000, "NaN") == 1000
973+
assert await decoded_r.ts().add("temperature:2:32", 1003, 25) == 1003
974+
assert await decoded_r.ts().add("temperature:2:32", 1005, "NaN") == 1005
975+
assert await decoded_r.ts().add("temperature:2:32", 1006, "NaN") == 1006
976+
977+
# Ensure we count only NaN values
978+
data_points = await decoded_r.ts().revrange(
979+
"temperature:2:32",
980+
1000,
981+
1006,
982+
aggregation_type="countNan",
983+
bucket_size_msec=1000,
984+
)
985+
assert_resp_response(
986+
decoded_r,
987+
data_points,
988+
[(1000, 3)],
989+
[[1000, 3]],
990+
)
991+
992+
# Ensure we count ALL values
993+
data_points = await decoded_r.ts().revrange(
994+
"temperature:2:32",
995+
1000,
996+
1006,
997+
aggregation_type="countAll",
998+
bucket_size_msec=1000,
999+
)
1000+
assert_resp_response(
1001+
decoded_r,
1002+
data_points,
1003+
[(1000, 4)],
1004+
[[1000, 4]],
1005+
)
1006+
1007+
1008+
@pytest.mark.redismod
1009+
@skip_if_server_version_lt("8.5.0")
1010+
async def test_mrange_with_count_nan_count_all_aggregators(decoded_r: redis.Redis):
1011+
await decoded_r.ts().create(
1012+
"temperature:A",
1013+
labels={"type": "temperature", "name": "A"},
1014+
)
1015+
await decoded_r.ts().create(
1016+
"temperature:B",
1017+
labels={"type": "temperature", "name": "B"},
1018+
)
1019+
1020+
# Fill with values
1021+
assert await decoded_r.ts().madd(
1022+
[("temperature:A", 1000, "NaN"), ("temperature:A", 1001, 27)]
1023+
)
1024+
assert await decoded_r.ts().madd(
1025+
[("temperature:B", 1000, "NaN"), ("temperature:B", 1001, 28)]
1026+
)
1027+
1028+
# Ensure we count only NaN values
1029+
data_points = await decoded_r.ts().mrange(
1030+
1000,
1031+
1001,
1032+
aggregation_type="countNan",
1033+
bucket_size_msec=1000,
1034+
filters=["type=temperature"],
1035+
)
1036+
assert_resp_response(
1037+
decoded_r,
1038+
data_points,
1039+
[
1040+
{"temperature:A": [{}, [(1000, 1.0)]]},
1041+
{"temperature:B": [{}, [(1000, 1.0)]]},
1042+
],
1043+
{
1044+
"temperature:A": [{}, {"aggregators": ["countnan"]}, [[1000, 1.0]]],
1045+
"temperature:B": [{}, {"aggregators": ["countnan"]}, [[1000, 1.0]]],
1046+
},
1047+
)
1048+
1049+
# Ensure we count ALL values
1050+
data_points = await decoded_r.ts().mrange(
1051+
1000,
1052+
1001,
1053+
aggregation_type="countAll",
1054+
bucket_size_msec=1000,
1055+
filters=["type=temperature"],
1056+
)
1057+
assert_resp_response(
1058+
decoded_r,
1059+
data_points,
1060+
[
1061+
{"temperature:A": [{}, [(1000, 2.0)]]},
1062+
{"temperature:B": [{}, [(1000, 2.0)]]},
1063+
],
1064+
{
1065+
"temperature:A": [{}, {"aggregators": ["countall"]}, [[1000, 2.0]]],
1066+
"temperature:B": [{}, {"aggregators": ["countall"]}, [[1000, 2.0]]],
1067+
},
1068+
)
1069+
1070+
1071+
@pytest.mark.redismod
1072+
@skip_if_server_version_lt("8.5.0")
1073+
async def test_mrevrange_with_count_nan_count_all_aggregators(decoded_r: redis.Redis):
1074+
await decoded_r.ts().create(
1075+
"temperature:A",
1076+
labels={"type": "temperature", "name": "A"},
1077+
)
1078+
await decoded_r.ts().create(
1079+
"temperature:B",
1080+
labels={"type": "temperature", "name": "B"},
1081+
)
1082+
1083+
# Fill with values
1084+
assert await decoded_r.ts().madd(
1085+
[("temperature:A", 1000, "NaN"), ("temperature:A", 1001, 27)]
1086+
)
1087+
assert await decoded_r.ts().madd(
1088+
[("temperature:B", 1000, "NaN"), ("temperature:B", 1001, 28)]
1089+
)
1090+
1091+
# Ensure we count only NaN values
1092+
data_points = await decoded_r.ts().mrevrange(
1093+
1000,
1094+
1001,
1095+
aggregation_type="countNan",
1096+
bucket_size_msec=1000,
1097+
filters=["type=temperature"],
1098+
)
1099+
assert_resp_response(
1100+
decoded_r,
1101+
data_points,
1102+
[
1103+
{"temperature:A": [{}, [(1000, 1.0)]]},
1104+
{"temperature:B": [{}, [(1000, 1.0)]]},
1105+
],
1106+
{
1107+
"temperature:A": [{}, {"aggregators": ["countnan"]}, [[1000, 1.0]]],
1108+
"temperature:B": [{}, {"aggregators": ["countnan"]}, [[1000, 1.0]]],
1109+
},
1110+
)
1111+
1112+
# Ensure we count ALL values
1113+
data_points = await decoded_r.ts().mrevrange(
1114+
1000,
1115+
1001,
1116+
aggregation_type="countAll",
1117+
bucket_size_msec=1000,
1118+
filters=["type=temperature"],
1119+
)
1120+
assert_resp_response(
1121+
decoded_r,
1122+
data_points,
1123+
[
1124+
{"temperature:A": [{}, [(1000, 2.0)]]},
1125+
{"temperature:B": [{}, [(1000, 2.0)]]},
1126+
],
1127+
{
1128+
"temperature:A": [{}, {"aggregators": ["countall"]}, [[1000, 2.0]]],
1129+
"temperature:B": [{}, {"aggregators": ["countall"]}, [[1000, 2.0]]],
1130+
},
1131+
)

0 commit comments

Comments
 (0)