ransacklib 1.1.0.dev1__tar.gz → 1.1.0.dev3__tar.gz
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.
- {ransacklib-1.1.0.dev1/ransacklib.egg-info → ransacklib-1.1.0.dev3}/PKG-INFO +1 -1
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/pyproject.toml +1 -1
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/ransack/operator.py +26 -0
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/ransack/transformer.py +43 -12
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3/ransacklib.egg-info}/PKG-INFO +1 -1
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/tests/test_transformer.py +13 -13
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/LICENSE +0 -0
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/README.rst +0 -0
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/ransack/__init__.py +0 -0
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/ransack/exceptions.py +0 -0
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/ransack/function.py +0 -0
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/ransack/parser.py +0 -0
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/ransack/py.typed +0 -0
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/ransacklib.egg-info/SOURCES.txt +0 -0
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/ransacklib.egg-info/dependency_links.txt +0 -0
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/ransacklib.egg-info/requires.txt +0 -0
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/ransacklib.egg-info/top_level.txt +0 -0
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/setup.cfg +0 -0
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/tests/test_operator.py +0 -0
- {ransacklib-1.1.0.dev1 → ransacklib-1.1.0.dev3}/tests/test_parser.py +0 -0
|
@@ -546,10 +546,36 @@ def _operator_map_sql(op, l_sql, r_sql, t1, t2) -> tuple[str, Any]:
|
|
|
546
546
|
"or": {(bool, bool): f"{l_sql} OR {r_sql}"},
|
|
547
547
|
"contains": {(str, str): f"position({r_sql} in {l_sql})>0"},
|
|
548
548
|
}
|
|
549
|
+
d2: dict[str, dict] = {
|
|
550
|
+
"+": {
|
|
551
|
+
(Number, Number): (f"{l_sql} + {r_sql}", Number),
|
|
552
|
+
(datetime, timedelta): (f"{l_sql} + {r_sql}", datetime),
|
|
553
|
+
(timedelta, timedelta): (f"{l_sql} + {r_sql}", timedelta),
|
|
554
|
+
},
|
|
555
|
+
"-": {
|
|
556
|
+
(Number, Number): (f"{l_sql} - {r_sql}", Number),
|
|
557
|
+
(datetime, timedelta): (f"{l_sql} - {r_sql}", datetime),
|
|
558
|
+
(timedelta, timedelta): (f"{l_sql} - {r_sql}", timedelta),
|
|
559
|
+
(datetime, datetime): (f"{l_sql} - {r_sql}", timedelta),
|
|
560
|
+
},
|
|
561
|
+
"*": {
|
|
562
|
+
(timedelta, Number): (f"{l_sql} * {r_sql}", timedelta),
|
|
563
|
+
(Number, Number): (f"{l_sql} * {r_sql}", Number),
|
|
564
|
+
},
|
|
565
|
+
"/": {
|
|
566
|
+
(timedelta, timedelta): (f"{l_sql} / {r_sql}", timedelta),
|
|
567
|
+
(Number, Number): (f"{l_sql} / {r_sql}", Number),
|
|
568
|
+
},
|
|
569
|
+
"%": {
|
|
570
|
+
(Number, Number): (f"{l_sql} % {r_sql}", timedelta),
|
|
571
|
+
},
|
|
572
|
+
}
|
|
549
573
|
if op == "==":
|
|
550
574
|
return (f"{l_sql} = {r_sql}", bool)
|
|
551
575
|
if op == "in":
|
|
552
576
|
return (d["="][(t1, t2)], bool)
|
|
577
|
+
if op in d2:
|
|
578
|
+
return d2[op][(t1, t2)]
|
|
553
579
|
return (d[op][(t1, t2)], bool)
|
|
554
580
|
|
|
555
581
|
|
|
@@ -20,7 +20,7 @@ Classes:
|
|
|
20
20
|
|
|
21
21
|
import re
|
|
22
22
|
from collections.abc import Mapping, MutableSequence
|
|
23
|
-
from datetime import datetime, timedelta
|
|
23
|
+
from datetime import datetime, timedelta
|
|
24
24
|
from typing import Any, cast
|
|
25
25
|
|
|
26
26
|
from ipranges import IP4, IP6, IP4Net, IP4Range, IP6Net, IP6Range
|
|
@@ -322,27 +322,22 @@ class ExpressionTransformer(Transformer):
|
|
|
322
322
|
|
|
323
323
|
# Convert to datetime object
|
|
324
324
|
dtime = datetime.fromisoformat(datetime_str)
|
|
325
|
-
|
|
326
|
-
# If no time zone was specified, then set to UTC
|
|
327
|
-
if dtime.tzinfo is None:
|
|
328
|
-
dtime = dtime.replace(tzinfo=timezone.utc)
|
|
329
|
-
|
|
330
325
|
tw = TokenWrapper(_add_tokens(date, time), dtime)
|
|
331
326
|
|
|
332
327
|
return Tree("datetime", [tw], _create_meta(tw))
|
|
333
328
|
|
|
334
329
|
def datetime_only_date(self, date: Token) -> Tree[TokenWrapper]:
|
|
335
330
|
"""
|
|
336
|
-
Transforms a date string into a datetime object set to midnight
|
|
331
|
+
Transforms a date string into a datetime object set to midnight.
|
|
337
332
|
|
|
338
333
|
Parameters:
|
|
339
334
|
date: A token representing the date.
|
|
340
335
|
Returns:
|
|
341
|
-
A tree node wrapping a datetime object set to midnight
|
|
336
|
+
A tree node wrapping a datetime object set to midnight.
|
|
342
337
|
"""
|
|
343
338
|
return Tree(
|
|
344
339
|
"datetime",
|
|
345
|
-
[TokenWrapper(date, datetime.fromisoformat(date + "T00:00:00
|
|
340
|
+
[TokenWrapper(date, datetime.fromisoformat(date + "T00:00:00"))],
|
|
346
341
|
_create_meta(date),
|
|
347
342
|
)
|
|
348
343
|
|
|
@@ -837,9 +832,12 @@ class SQLInterpreter(Interpreter):
|
|
|
837
832
|
return (str(token.real_value), type(token.real_value))
|
|
838
833
|
|
|
839
834
|
def datetime(self, token: TokenWrapper) -> tuple[str, type[datetime]]:
|
|
840
|
-
|
|
835
|
+
if token.real_value.tzinfo is not None:
|
|
836
|
+
type_ = "TIMESTAMP WITH TIME ZONE"
|
|
837
|
+
else:
|
|
838
|
+
type_ = "TIMESTAMP"
|
|
841
839
|
return (
|
|
842
|
-
f"
|
|
840
|
+
f"{type_} '{token.real_value.isoformat()}'",
|
|
843
841
|
type(token.real_value),
|
|
844
842
|
)
|
|
845
843
|
|
|
@@ -877,7 +875,10 @@ class SQLInterpreter(Interpreter):
|
|
|
877
875
|
elif issubclass(l_type, float) and issubclass(r_type, float):
|
|
878
876
|
range_type = "numrange"
|
|
879
877
|
elif issubclass(l_type, datetime) and issubclass(r_type, datetime):
|
|
880
|
-
|
|
878
|
+
if "WITH TIME ZONE" in l_sql or "WITH TIME ZONE" in r_sql:
|
|
879
|
+
range_type = "tstzrange"
|
|
880
|
+
else:
|
|
881
|
+
range_type = "tsrange"
|
|
881
882
|
elif issubclass(l_type, (IP4, IP6)) and issubclass(r_type, (IP4, IP6)):
|
|
882
883
|
return (
|
|
883
884
|
f"iprange({l_sql}, {r_sql})",
|
|
@@ -923,6 +924,21 @@ class SQLInterpreter(Interpreter):
|
|
|
923
924
|
|
|
924
925
|
return binary_operation_sql(operator, l_sql, r_sql, l_type, r_type)
|
|
925
926
|
|
|
927
|
+
def add(self, l_tree: Tree, r_tree: Tree) -> tuple[str, Any]:
|
|
928
|
+
return self._binary_operation("+", l_tree, r_tree)
|
|
929
|
+
|
|
930
|
+
def sub(self, l_tree: Tree, r_tree: Tree) -> tuple[str, Any]:
|
|
931
|
+
return self._binary_operation("-", l_tree, r_tree)
|
|
932
|
+
|
|
933
|
+
def mul(self, l_tree: Tree, r_tree: Tree) -> tuple[str, Any]:
|
|
934
|
+
return self._binary_operation("*", l_tree, r_tree)
|
|
935
|
+
|
|
936
|
+
def div(self, l_tree: Tree, r_tree: Tree) -> tuple[str, Any]:
|
|
937
|
+
return self._binary_operation("/", l_tree, r_tree)
|
|
938
|
+
|
|
939
|
+
def mod(self, l_tree: Tree, r_tree: Tree) -> tuple[str, Any]:
|
|
940
|
+
return self._binary_operation("-", l_tree, r_tree)
|
|
941
|
+
|
|
926
942
|
def eq(self, l_tree: Tree, r_tree: Tree) -> tuple[str, type[bool]]:
|
|
927
943
|
return self._binary_operation("==", l_tree, r_tree)
|
|
928
944
|
|
|
@@ -965,3 +981,18 @@ class SQLInterpreter(Interpreter):
|
|
|
965
981
|
var, _ = self.var_from_data(TokenWrapper(path, path))
|
|
966
982
|
sql, type_ = self.visit(default)
|
|
967
983
|
return f"COALESCE({var!s}, {sql})", type_
|
|
984
|
+
|
|
985
|
+
def function_(self, name: TokenWrapper, args: Tree | None) -> tuple[str, Any]:
|
|
986
|
+
function_name = cast("str", name.real_value)
|
|
987
|
+
args = [self.visit(x) for x in (args.children if args else [])] # type: ignore
|
|
988
|
+
if function_name == "now":
|
|
989
|
+
return "localtimestamp", datetime
|
|
990
|
+
raise EvaluationError(
|
|
991
|
+
f"Function '{name.real_value}' was not found.",
|
|
992
|
+
line=name.line,
|
|
993
|
+
column=name.column,
|
|
994
|
+
start_pos=name.start_pos,
|
|
995
|
+
end_line=name.end_line,
|
|
996
|
+
end_column=name.end_column,
|
|
997
|
+
end_pos=name.end_pos,
|
|
998
|
+
)
|
|
@@ -150,7 +150,7 @@ class TestExpressionTransformer:
|
|
|
150
150
|
),
|
|
151
151
|
("2024-01-01T12:00:00Z", datetime, "2024-01-01 12:00:00+00:00"),
|
|
152
152
|
("2024-01-01 12:00:00-02:00", datetime, "2024-01-01 12:00:00-02:00"),
|
|
153
|
-
("2024-01-01T12:00:00", datetime, "2024-01-01 12:00:00
|
|
153
|
+
("2024-01-01T12:00:00", datetime, "2024-01-01 12:00:00"),
|
|
154
154
|
("01:00:00", timedelta, "1:00:00"),
|
|
155
155
|
("1d01:00:00", timedelta, "1 day, 1:00:00"),
|
|
156
156
|
("'constant1'", str, "constant1"),
|
|
@@ -233,11 +233,11 @@ class TestFilter:
|
|
|
233
233
|
("(4 + 5 * 2 - 6 / 3) % 5", 2),
|
|
234
234
|
(
|
|
235
235
|
"2024-01-01 - 1D00:00:00",
|
|
236
|
-
datetime.fromisoformat("2023-12-31T00:00:00
|
|
236
|
+
datetime.fromisoformat("2023-12-31T00:00:00"),
|
|
237
237
|
),
|
|
238
238
|
(
|
|
239
239
|
"2023-11-28 15:30:00 + 0D10:00:00",
|
|
240
|
-
datetime.fromisoformat("2023-11-29T01:30:00
|
|
240
|
+
datetime.fromisoformat("2023-11-29T01:30:00"),
|
|
241
241
|
),
|
|
242
242
|
("3D00:00:00 + 0D05:00:00", timedelta(days=3, hours=5)),
|
|
243
243
|
("0D10:00:00 + 0D00:20:00", timedelta(hours=10, minutes=20)),
|
|
@@ -249,15 +249,15 @@ class TestFilter:
|
|
|
249
249
|
(
|
|
250
250
|
"1D00:00:00 + 2024-02-28..2025-02-28",
|
|
251
251
|
(
|
|
252
|
-
datetime.fromisoformat("2024-02-29T00:00:00
|
|
253
|
-
datetime.fromisoformat("2025-03-01T00:00:00
|
|
252
|
+
datetime.fromisoformat("2024-02-29T00:00:00"),
|
|
253
|
+
datetime.fromisoformat("2025-03-01T00:00:00"),
|
|
254
254
|
),
|
|
255
255
|
),
|
|
256
256
|
(
|
|
257
257
|
"2024-01-01 .. 2024-12-31 - 1D00:00:00",
|
|
258
258
|
(
|
|
259
|
-
datetime.fromisoformat("2023-12-31T00:00:00
|
|
260
|
-
datetime.fromisoformat("2024-12-30T00:00:00
|
|
259
|
+
datetime.fromisoformat("2023-12-31T00:00:00"),
|
|
260
|
+
datetime.fromisoformat("2024-12-30T00:00:00"),
|
|
261
261
|
),
|
|
262
262
|
),
|
|
263
263
|
# Scalar and list
|
|
@@ -286,8 +286,8 @@ class TestFilter:
|
|
|
286
286
|
(
|
|
287
287
|
"2023-11-28 + [1D00:00:00, 2D00:00:00]",
|
|
288
288
|
[
|
|
289
|
-
datetime.fromisoformat("2023-11-29T00:00:00
|
|
290
|
-
datetime.fromisoformat("2023-11-30T00:00:00
|
|
289
|
+
datetime.fromisoformat("2023-11-29T00:00:00"),
|
|
290
|
+
datetime.fromisoformat("2023-11-30T00:00:00"),
|
|
291
291
|
],
|
|
292
292
|
),
|
|
293
293
|
# Test Comparisons
|
|
@@ -534,7 +534,7 @@ class TestSQLInterpreter:
|
|
|
534
534
|
("23.22", "23.22"),
|
|
535
535
|
("-0.224", "-0.224"),
|
|
536
536
|
("13e19", "1.3e+20"),
|
|
537
|
-
("2025-01-01", "TIMESTAMP
|
|
537
|
+
("2025-01-01", "TIMESTAMP '2025-01-01T00:00:00'"),
|
|
538
538
|
(
|
|
539
539
|
"2025-01-01T22:14:24.142+02:00",
|
|
540
540
|
"TIMESTAMP WITH TIME ZONE '2025-01-01T22:14:24.142000+02:00'",
|
|
@@ -554,8 +554,8 @@ class TestSQLInterpreter:
|
|
|
554
554
|
(
|
|
555
555
|
"[2024-12-12, 2025-12-12]",
|
|
556
556
|
(
|
|
557
|
-
"ARRAY[TIMESTAMP
|
|
558
|
-
"TIMESTAMP
|
|
557
|
+
"ARRAY[TIMESTAMP '2024-12-12T00:00:00', "
|
|
558
|
+
"TIMESTAMP '2025-12-12T00:00:00']"
|
|
559
559
|
),
|
|
560
560
|
),
|
|
561
561
|
("[]", "ARRAY[]"),
|
|
@@ -584,7 +584,7 @@ class TestSQLInterpreter:
|
|
|
584
584
|
),
|
|
585
585
|
),
|
|
586
586
|
(
|
|
587
|
-
"2024-01-
|
|
587
|
+
"2024-01-01T00:00:00+00:00..2024-12-31T00:00:00Z",
|
|
588
588
|
(
|
|
589
589
|
"CASE WHEN TIMESTAMP WITH TIME ZONE '2024-01-01T00:00:00+00:00' "
|
|
590
590
|
"< TIMESTAMP WITH TIME ZONE '2024-12-31T00:00:00+00:00' THEN "
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|