kiarina-lib-redisearch 1.0.0__py3-none-any.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.
- kiarina/lib/redisearch/__init__.py +35 -0
- kiarina/lib/redisearch/_async/__init__.py +0 -0
- kiarina/lib/redisearch/_async/client.py +181 -0
- kiarina/lib/redisearch/_async/registry.py +16 -0
- kiarina/lib/redisearch/_core/__init__.py +0 -0
- kiarina/lib/redisearch/_core/context.py +69 -0
- kiarina/lib/redisearch/_core/operations/__init__.py +0 -0
- kiarina/lib/redisearch/_core/operations/count.py +55 -0
- kiarina/lib/redisearch/_core/operations/create_index.py +52 -0
- kiarina/lib/redisearch/_core/operations/delete.py +43 -0
- kiarina/lib/redisearch/_core/operations/drop_index.py +59 -0
- kiarina/lib/redisearch/_core/operations/exists_index.py +56 -0
- kiarina/lib/redisearch/_core/operations/find.py +105 -0
- kiarina/lib/redisearch/_core/operations/get.py +61 -0
- kiarina/lib/redisearch/_core/operations/get_info.py +155 -0
- kiarina/lib/redisearch/_core/operations/get_key.py +8 -0
- kiarina/lib/redisearch/_core/operations/migrate_index.py +160 -0
- kiarina/lib/redisearch/_core/operations/reset_index.py +60 -0
- kiarina/lib/redisearch/_core/operations/search.py +111 -0
- kiarina/lib/redisearch/_core/operations/set.py +65 -0
- kiarina/lib/redisearch/_core/utils/__init__.py +0 -0
- kiarina/lib/redisearch/_core/utils/calc_score.py +35 -0
- kiarina/lib/redisearch/_core/utils/marshal_mappings.py +57 -0
- kiarina/lib/redisearch/_core/utils/parse_search_result.py +57 -0
- kiarina/lib/redisearch/_core/utils/unmarshal_mappings.py +57 -0
- kiarina/lib/redisearch/_core/views/__init__.py +0 -0
- kiarina/lib/redisearch/_core/views/document.py +25 -0
- kiarina/lib/redisearch/_core/views/info_result.py +24 -0
- kiarina/lib/redisearch/_core/views/search_result.py +31 -0
- kiarina/lib/redisearch/_sync/__init__.py +0 -0
- kiarina/lib/redisearch/_sync/client.py +179 -0
- kiarina/lib/redisearch/_sync/registry.py +16 -0
- kiarina/lib/redisearch/asyncio.py +33 -0
- kiarina/lib/redisearch/filter/__init__.py +61 -0
- kiarina/lib/redisearch/filter/_decorators.py +28 -0
- kiarina/lib/redisearch/filter/_enums.py +28 -0
- kiarina/lib/redisearch/filter/_field/__init__.py +5 -0
- kiarina/lib/redisearch/filter/_field/base.py +67 -0
- kiarina/lib/redisearch/filter/_field/numeric.py +178 -0
- kiarina/lib/redisearch/filter/_field/tag.py +142 -0
- kiarina/lib/redisearch/filter/_field/text.py +111 -0
- kiarina/lib/redisearch/filter/_model.py +93 -0
- kiarina/lib/redisearch/filter/_registry.py +153 -0
- kiarina/lib/redisearch/filter/_types.py +32 -0
- kiarina/lib/redisearch/filter/_utils.py +18 -0
- kiarina/lib/redisearch/py.typed +0 -0
- kiarina/lib/redisearch/schema/__init__.py +25 -0
- kiarina/lib/redisearch/schema/_field/__init__.py +0 -0
- kiarina/lib/redisearch/schema/_field/base.py +20 -0
- kiarina/lib/redisearch/schema/_field/numeric.py +33 -0
- kiarina/lib/redisearch/schema/_field/tag.py +46 -0
- kiarina/lib/redisearch/schema/_field/text.py +44 -0
- kiarina/lib/redisearch/schema/_field/vector/__init__.py +0 -0
- kiarina/lib/redisearch/schema/_field/vector/base.py +61 -0
- kiarina/lib/redisearch/schema/_field/vector/flat.py +40 -0
- kiarina/lib/redisearch/schema/_field/vector/hnsw.py +53 -0
- kiarina/lib/redisearch/schema/_model.py +98 -0
- kiarina/lib/redisearch/schema/_types.py +16 -0
- kiarina/lib/redisearch/settings.py +47 -0
- kiarina_lib_redisearch-1.0.0.dist-info/METADATA +886 -0
- kiarina_lib_redisearch-1.0.0.dist-info/RECORD +62 -0
- kiarina_lib_redisearch-1.0.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,178 @@
|
|
1
|
+
from .._decorators import check_operator_misuse
|
2
|
+
from .._enums import RedisearchFilterOperator
|
3
|
+
from .._model import RedisearchFilter
|
4
|
+
from .base import RedisearchFieldFilter
|
5
|
+
|
6
|
+
|
7
|
+
class Numeric(RedisearchFieldFilter):
|
8
|
+
"""
|
9
|
+
Filter for numeric fields.
|
10
|
+
"""
|
11
|
+
|
12
|
+
# --------------------------------------------------
|
13
|
+
# Class Variables
|
14
|
+
# --------------------------------------------------
|
15
|
+
|
16
|
+
OPERATORS: dict[RedisearchFilterOperator, str] = {
|
17
|
+
RedisearchFilterOperator.EQ: "==",
|
18
|
+
RedisearchFilterOperator.NE: "!=",
|
19
|
+
RedisearchFilterOperator.LT: "<",
|
20
|
+
RedisearchFilterOperator.GT: ">",
|
21
|
+
RedisearchFilterOperator.LE: "<=",
|
22
|
+
RedisearchFilterOperator.GE: ">=",
|
23
|
+
}
|
24
|
+
"""Supported operators"""
|
25
|
+
|
26
|
+
OPERATOR_MAP: dict[RedisearchFilterOperator, str] = {
|
27
|
+
RedisearchFilterOperator.EQ: "@%s:[%s %s]",
|
28
|
+
RedisearchFilterOperator.NE: "(-@%s:[%s %s])",
|
29
|
+
RedisearchFilterOperator.GT: "@%s:[(%s +inf]",
|
30
|
+
RedisearchFilterOperator.LT: "@%s:[-inf (%s]",
|
31
|
+
RedisearchFilterOperator.GE: "@%s:[%s +inf]",
|
32
|
+
RedisearchFilterOperator.LE: "@%s:[-inf %s]",
|
33
|
+
}
|
34
|
+
"""Operator and query mapping"""
|
35
|
+
|
36
|
+
SUPPORTED_VALUE_TYPES = (int, float, type(None))
|
37
|
+
"""Supported value types"""
|
38
|
+
|
39
|
+
# --------------------------------------------------
|
40
|
+
# Magic Methods
|
41
|
+
# --------------------------------------------------
|
42
|
+
|
43
|
+
def __str__(self) -> str:
|
44
|
+
"""
|
45
|
+
Stringification
|
46
|
+
|
47
|
+
Converting filter expressions into query strings
|
48
|
+
"""
|
49
|
+
if self._value is None:
|
50
|
+
return "*"
|
51
|
+
|
52
|
+
if (
|
53
|
+
self._operator == RedisearchFilterOperator.EQ
|
54
|
+
or self._operator == RedisearchFilterOperator.NE
|
55
|
+
):
|
56
|
+
return self.OPERATOR_MAP[self._operator] % (
|
57
|
+
self._field_name,
|
58
|
+
self._value,
|
59
|
+
self._value,
|
60
|
+
)
|
61
|
+
else:
|
62
|
+
return self.OPERATOR_MAP[self._operator] % (self._field_name, self._value)
|
63
|
+
|
64
|
+
@check_operator_misuse
|
65
|
+
def __eq__(self, other: int | float) -> RedisearchFilter:
|
66
|
+
"""
|
67
|
+
Create a numerical equivalence filter expression.
|
68
|
+
|
69
|
+
Args:
|
70
|
+
other (int | float): Value to filter by
|
71
|
+
|
72
|
+
Example:
|
73
|
+
>>> import kiarina.lib.redisearch.field as rf
|
74
|
+
>>> filter = rf.Numeric("zipcode") == 90210
|
75
|
+
"""
|
76
|
+
self._set(
|
77
|
+
operator=RedisearchFilterOperator.EQ,
|
78
|
+
value=other,
|
79
|
+
value_type=self.SUPPORTED_VALUE_TYPES, # type: ignore
|
80
|
+
)
|
81
|
+
|
82
|
+
return RedisearchFilter(str(self))
|
83
|
+
|
84
|
+
@check_operator_misuse
|
85
|
+
def __ne__(self, other: int | float) -> RedisearchFilter:
|
86
|
+
"""
|
87
|
+
Create a numerical inequality filter expression.
|
88
|
+
|
89
|
+
Args:
|
90
|
+
other (int | float): Value to filter by
|
91
|
+
|
92
|
+
Example:
|
93
|
+
>>> import kiarina.lib.redisearch.field as rf
|
94
|
+
>>> filter = rf.Numeric("zipcode") != 90210
|
95
|
+
"""
|
96
|
+
self._set(
|
97
|
+
operator=RedisearchFilterOperator.NE,
|
98
|
+
value=other,
|
99
|
+
value_type=self.SUPPORTED_VALUE_TYPES, # type: ignore
|
100
|
+
)
|
101
|
+
|
102
|
+
return RedisearchFilter(str(self))
|
103
|
+
|
104
|
+
def __gt__(self, other: int | float) -> RedisearchFilter:
|
105
|
+
"""
|
106
|
+
Create a numerical "greater than" filter expression.
|
107
|
+
|
108
|
+
Args:
|
109
|
+
other (int | float): Value to filter by
|
110
|
+
|
111
|
+
Example:
|
112
|
+
>>> import kiarina.lib.redisearch.field as rf
|
113
|
+
>>> filter = rf.Numeric("age") > 18
|
114
|
+
"""
|
115
|
+
self._set(
|
116
|
+
operator=RedisearchFilterOperator.GT,
|
117
|
+
value=other,
|
118
|
+
value_type=self.SUPPORTED_VALUE_TYPES, # type: ignore
|
119
|
+
)
|
120
|
+
|
121
|
+
return RedisearchFilter(str(self))
|
122
|
+
|
123
|
+
def __lt__(self, other: int | float) -> RedisearchFilter:
|
124
|
+
"""
|
125
|
+
Create a numerical "less than" filter expression.
|
126
|
+
|
127
|
+
Args:
|
128
|
+
other (int | float): Value to filter by
|
129
|
+
|
130
|
+
Example:
|
131
|
+
>>> import kiarina.lib.redisearch.field as rf
|
132
|
+
>>> filter = rf.Numeric("age") < 18
|
133
|
+
"""
|
134
|
+
self._set(
|
135
|
+
operator=RedisearchFilterOperator.LT,
|
136
|
+
value=other,
|
137
|
+
value_type=self.SUPPORTED_VALUE_TYPES, # type: ignore
|
138
|
+
)
|
139
|
+
|
140
|
+
return RedisearchFilter(str(self))
|
141
|
+
|
142
|
+
def __ge__(self, other: int | float) -> RedisearchFilter:
|
143
|
+
"""
|
144
|
+
Create a numerical "greater than or equal to" filter expression.
|
145
|
+
|
146
|
+
Args:
|
147
|
+
other (int | float): Value to filter by
|
148
|
+
|
149
|
+
Example:
|
150
|
+
>>> import kiarina.lib.redisearch.field as rf
|
151
|
+
>>> filter = rf.Numeric("age") >= 18
|
152
|
+
"""
|
153
|
+
self._set(
|
154
|
+
operator=RedisearchFilterOperator.GE,
|
155
|
+
value=other,
|
156
|
+
value_type=self.SUPPORTED_VALUE_TYPES, # type: ignore
|
157
|
+
)
|
158
|
+
|
159
|
+
return RedisearchFilter(str(self))
|
160
|
+
|
161
|
+
def __le__(self, other: int | float) -> RedisearchFilter:
|
162
|
+
"""
|
163
|
+
Create a numerical "less than or equal to" filter expression.
|
164
|
+
|
165
|
+
Args:
|
166
|
+
other (int | float): Value to filter by
|
167
|
+
|
168
|
+
Example:
|
169
|
+
>>> import kiarina.lib.redisearch.field as rf
|
170
|
+
>>> filter = rf.Numeric("age") <= 18
|
171
|
+
"""
|
172
|
+
self._set(
|
173
|
+
operator=RedisearchFilterOperator.LE,
|
174
|
+
value=other,
|
175
|
+
value_type=self.SUPPORTED_VALUE_TYPES, # type: ignore
|
176
|
+
)
|
177
|
+
|
178
|
+
return RedisearchFilter(str(self))
|
@@ -0,0 +1,142 @@
|
|
1
|
+
from typing import Dict, List, Set, Tuple, Union
|
2
|
+
|
3
|
+
from .._decorators import check_operator_misuse
|
4
|
+
from .._enums import RedisearchFilterOperator
|
5
|
+
from .._model import RedisearchFilter
|
6
|
+
from .._utils import escape_token
|
7
|
+
from .base import RedisearchFieldFilter
|
8
|
+
|
9
|
+
|
10
|
+
class Tag(RedisearchFieldFilter):
|
11
|
+
"""
|
12
|
+
Filter for tag fields.
|
13
|
+
"""
|
14
|
+
|
15
|
+
# --------------------------------------------------
|
16
|
+
# Class Variables
|
17
|
+
# --------------------------------------------------
|
18
|
+
|
19
|
+
OPERATORS: Dict[RedisearchFilterOperator, str] = {
|
20
|
+
RedisearchFilterOperator.EQ: "==",
|
21
|
+
RedisearchFilterOperator.NE: "!=",
|
22
|
+
RedisearchFilterOperator.IN: "==",
|
23
|
+
}
|
24
|
+
"""Supported operators"""
|
25
|
+
|
26
|
+
OPERATOR_MAP: Dict[RedisearchFilterOperator, str] = {
|
27
|
+
RedisearchFilterOperator.EQ: "@%s:{%s}",
|
28
|
+
RedisearchFilterOperator.NE: "(-@%s:{%s})",
|
29
|
+
RedisearchFilterOperator.IN: "@%s:{%s}",
|
30
|
+
}
|
31
|
+
"""Operator and query mapping"""
|
32
|
+
|
33
|
+
SUPPORTED_VALUE_TYPES = (list, set, tuple, str, type(None))
|
34
|
+
"""Supported value types"""
|
35
|
+
|
36
|
+
# --------------------------------------------------
|
37
|
+
# Properties
|
38
|
+
# --------------------------------------------------
|
39
|
+
|
40
|
+
@property
|
41
|
+
def _formatted_tag_value(self) -> str:
|
42
|
+
"""
|
43
|
+
Format the tag value for query representation.
|
44
|
+
"""
|
45
|
+
return "|".join([escape_token(tag) for tag in self._value])
|
46
|
+
|
47
|
+
# --------------------------------------------------
|
48
|
+
# Magic Methods
|
49
|
+
# --------------------------------------------------
|
50
|
+
|
51
|
+
def __str__(self) -> str:
|
52
|
+
"""
|
53
|
+
Stringification
|
54
|
+
|
55
|
+
Converting filter expressions into query strings
|
56
|
+
"""
|
57
|
+
if not self._value:
|
58
|
+
return "*"
|
59
|
+
|
60
|
+
return self.OPERATOR_MAP[self._operator] % (
|
61
|
+
self._field_name,
|
62
|
+
self._formatted_tag_value,
|
63
|
+
)
|
64
|
+
|
65
|
+
@check_operator_misuse
|
66
|
+
def __eq__(
|
67
|
+
self, other: Union[List[str], Set[str], Tuple[str], str]
|
68
|
+
) -> RedisearchFilter:
|
69
|
+
"""
|
70
|
+
Create an equality filter expression for tags.
|
71
|
+
|
72
|
+
Args:
|
73
|
+
other (Union[List[str], Set[str], Tuple[str], str]):
|
74
|
+
The tags to filter by.
|
75
|
+
|
76
|
+
Example:
|
77
|
+
>>> import kiarina.lib.redisearch as rf
|
78
|
+
>>> filter = rf.Tag("color") == "blue"
|
79
|
+
"""
|
80
|
+
self._set(
|
81
|
+
operator=RedisearchFilterOperator.EQ,
|
82
|
+
value=self._normalize_tag_value(other),
|
83
|
+
value_type=self.SUPPORTED_VALUE_TYPES, # type: ignore
|
84
|
+
)
|
85
|
+
|
86
|
+
return RedisearchFilter(str(self))
|
87
|
+
|
88
|
+
@check_operator_misuse
|
89
|
+
def __ne__(
|
90
|
+
self, other: Union[List[str], Set[str], Tuple[str], str]
|
91
|
+
) -> RedisearchFilter:
|
92
|
+
"""
|
93
|
+
Create a not-equal filter expression for tags.
|
94
|
+
|
95
|
+
Args:
|
96
|
+
other (Union[List[str], Set[str], Tuple[str], str]):
|
97
|
+
The tags to filter by.
|
98
|
+
|
99
|
+
Example:
|
100
|
+
>>> import kiarina.lib.redisearch as rf
|
101
|
+
>>> filter = rf.Tag("color") != "blue"
|
102
|
+
"""
|
103
|
+
self._set(
|
104
|
+
operator=RedisearchFilterOperator.NE,
|
105
|
+
value=self._normalize_tag_value(other),
|
106
|
+
value_type=self.SUPPORTED_VALUE_TYPES, # type: ignore
|
107
|
+
)
|
108
|
+
|
109
|
+
return RedisearchFilter(str(self))
|
110
|
+
|
111
|
+
# --------------------------------------------------
|
112
|
+
# Private Methods
|
113
|
+
# --------------------------------------------------
|
114
|
+
|
115
|
+
def _normalize_tag_value(
|
116
|
+
self, other: Union[List[str], Set[str], Tuple[str], str]
|
117
|
+
) -> List[str]:
|
118
|
+
"""
|
119
|
+
Normalize the tag value to a list of strings.
|
120
|
+
|
121
|
+
Args:
|
122
|
+
other: The tag value to normalize.
|
123
|
+
|
124
|
+
Returns:
|
125
|
+
List[str]: Normalized tag values.
|
126
|
+
|
127
|
+
Raises:
|
128
|
+
ValueError: If tags within collection cannot be converted to strings.
|
129
|
+
"""
|
130
|
+
if isinstance(other, (list, set, tuple)):
|
131
|
+
try:
|
132
|
+
return [str(val) for val in other if val]
|
133
|
+
except ValueError:
|
134
|
+
raise ValueError("All tags within collection must be strings")
|
135
|
+
|
136
|
+
elif not other:
|
137
|
+
return []
|
138
|
+
|
139
|
+
elif isinstance(other, str):
|
140
|
+
return [other]
|
141
|
+
|
142
|
+
return []
|
@@ -0,0 +1,111 @@
|
|
1
|
+
from .._decorators import check_operator_misuse
|
2
|
+
from .._enums import RedisearchFilterOperator
|
3
|
+
from .._model import RedisearchFilter
|
4
|
+
from .base import RedisearchFieldFilter
|
5
|
+
|
6
|
+
|
7
|
+
class Text(RedisearchFieldFilter):
|
8
|
+
"""
|
9
|
+
Field for text fields.
|
10
|
+
"""
|
11
|
+
|
12
|
+
# --------------------------------------------------
|
13
|
+
# Class Variables
|
14
|
+
# --------------------------------------------------
|
15
|
+
|
16
|
+
OPERATORS: dict[RedisearchFilterOperator, str] = {
|
17
|
+
RedisearchFilterOperator.EQ: "==",
|
18
|
+
RedisearchFilterOperator.NE: "!=",
|
19
|
+
RedisearchFilterOperator.LIKE: "%",
|
20
|
+
}
|
21
|
+
"""Supported operators"""
|
22
|
+
|
23
|
+
OPERATOR_MAP: dict[RedisearchFilterOperator, str] = {
|
24
|
+
RedisearchFilterOperator.EQ: '@%s:("%s")',
|
25
|
+
RedisearchFilterOperator.NE: '(-@%s:"%s")',
|
26
|
+
RedisearchFilterOperator.LIKE: "@%s:(%s)",
|
27
|
+
}
|
28
|
+
"""Operator and query mapping"""
|
29
|
+
|
30
|
+
SUPPORTED_VALUE_TYPES = (str, type(None))
|
31
|
+
"""Supported value types"""
|
32
|
+
|
33
|
+
# --------------------------------------------------
|
34
|
+
# Magic Methods
|
35
|
+
# --------------------------------------------------
|
36
|
+
|
37
|
+
def __str__(self) -> str:
|
38
|
+
"""
|
39
|
+
Stringification
|
40
|
+
|
41
|
+
Converting filter expressions into query strings
|
42
|
+
"""
|
43
|
+
if not self._value:
|
44
|
+
return "*"
|
45
|
+
|
46
|
+
return self.OPERATOR_MAP[self._operator] % (
|
47
|
+
self._field_name,
|
48
|
+
self._value,
|
49
|
+
)
|
50
|
+
|
51
|
+
@check_operator_misuse
|
52
|
+
def __eq__(self, other: str) -> RedisearchFilter:
|
53
|
+
"""
|
54
|
+
Create an equality (exact match) filter expression for strings.
|
55
|
+
|
56
|
+
Args:
|
57
|
+
other (str): The text value to filter by.
|
58
|
+
|
59
|
+
Example:
|
60
|
+
>>> import kiarina.lib.redisearch as rf
|
61
|
+
>>> filter = rf.Text("job") == "engineer"
|
62
|
+
"""
|
63
|
+
self._set(
|
64
|
+
operator=RedisearchFilterOperator.EQ,
|
65
|
+
value=other,
|
66
|
+
value_type=self.SUPPORTED_VALUE_TYPES, # type: ignore
|
67
|
+
)
|
68
|
+
|
69
|
+
return RedisearchFilter(str(self))
|
70
|
+
|
71
|
+
@check_operator_misuse
|
72
|
+
def __ne__(self, other: str) -> RedisearchFilter:
|
73
|
+
"""
|
74
|
+
Create a non-equality (not equal) filter expression for strings.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
other (str): The text value to filter by.
|
78
|
+
|
79
|
+
Example:
|
80
|
+
>>> import kiarina.lib.redisearch as rf
|
81
|
+
>>> filter = rf.Text("job") != "engineer"
|
82
|
+
"""
|
83
|
+
self._set(
|
84
|
+
operator=RedisearchFilterOperator.NE,
|
85
|
+
value=other,
|
86
|
+
value_type=self.SUPPORTED_VALUE_TYPES, # type: ignore
|
87
|
+
)
|
88
|
+
|
89
|
+
return RedisearchFilter(str(self))
|
90
|
+
|
91
|
+
def __mod__(self, other: str) -> RedisearchFilter:
|
92
|
+
"""
|
93
|
+
Create a "LIKE" filter expression for strings.
|
94
|
+
|
95
|
+
Args:
|
96
|
+
other (str): The text value to filter by.
|
97
|
+
|
98
|
+
Example:
|
99
|
+
>>> import kiarina.lib.redisearch as rf
|
100
|
+
>>> filter = rf.Text("job") % "engine*" # Suffix wildcard match
|
101
|
+
>>> filter = rf.Text("job") % "%%engine%%" # Fuzzy match (using edit distance)
|
102
|
+
>>> filter = rf.Text("job") % "engineer|doctor" # Contains either term
|
103
|
+
>>> filter = rf.Text("job") % "engineer doctor" # Contains both terms
|
104
|
+
"""
|
105
|
+
self._set(
|
106
|
+
operator=RedisearchFilterOperator.LIKE,
|
107
|
+
value=other,
|
108
|
+
value_type=self.SUPPORTED_VALUE_TYPES, # type: ignore
|
109
|
+
)
|
110
|
+
|
111
|
+
return RedisearchFilter(str(self))
|
@@ -0,0 +1,93 @@
|
|
1
|
+
from typing import Self
|
2
|
+
|
3
|
+
from ._enums import RedisearchFilterOperator
|
4
|
+
|
5
|
+
|
6
|
+
class RedisearchFilter:
|
7
|
+
"""
|
8
|
+
A class representing the filter condition expression for a Redisearch query.
|
9
|
+
|
10
|
+
RedisearchFilter can be combined using & and | operators to create
|
11
|
+
complex logical expressions that are evaluated in the Redis Query language.
|
12
|
+
|
13
|
+
This interface allows users to construct complex queries without needing to know
|
14
|
+
the Redis Query language.
|
15
|
+
|
16
|
+
Filter-based fields are not initialised directly.
|
17
|
+
Instead, they are constructed by combining RedisFilterFields
|
18
|
+
using the & and | operators.
|
19
|
+
|
20
|
+
Examples:
|
21
|
+
>>> import kiarina.lib.redisearch.filter as rf
|
22
|
+
>>> filter = (rf.Tag("color") == "blue") & (rf.Numeric("price") < 100)
|
23
|
+
>>> print(str(filter))
|
24
|
+
(@color:{blue} @price:[-inf (100)])
|
25
|
+
"""
|
26
|
+
|
27
|
+
def __init__(
|
28
|
+
self,
|
29
|
+
query: str | None = None,
|
30
|
+
*,
|
31
|
+
left: Self | None = None,
|
32
|
+
operator: RedisearchFilterOperator | None = None,
|
33
|
+
right: Self | None = None,
|
34
|
+
):
|
35
|
+
"""
|
36
|
+
Initialization
|
37
|
+
"""
|
38
|
+
self._query: str | None = query
|
39
|
+
"""Query string"""
|
40
|
+
|
41
|
+
self._left: Self | None = left
|
42
|
+
"""Left operand"""
|
43
|
+
|
44
|
+
self._operator: RedisearchFilterOperator | None = operator
|
45
|
+
"""Logical operator"""
|
46
|
+
|
47
|
+
self._right: Self | None = right
|
48
|
+
"""Right operand"""
|
49
|
+
|
50
|
+
def __and__(self, other: Self) -> Self:
|
51
|
+
"""
|
52
|
+
AND operator for concatenation
|
53
|
+
"""
|
54
|
+
return type(self)(
|
55
|
+
left=self,
|
56
|
+
operator=RedisearchFilterOperator.AND,
|
57
|
+
right=other,
|
58
|
+
)
|
59
|
+
|
60
|
+
def __or__(self, other: Self) -> Self:
|
61
|
+
"""
|
62
|
+
OR operator for concatenation
|
63
|
+
"""
|
64
|
+
return type(self)(
|
65
|
+
left=self,
|
66
|
+
operator=RedisearchFilterOperator.OR,
|
67
|
+
right=other,
|
68
|
+
)
|
69
|
+
|
70
|
+
def __str__(self) -> str:
|
71
|
+
"""
|
72
|
+
Stringification
|
73
|
+
"""
|
74
|
+
if self._query:
|
75
|
+
return self._query
|
76
|
+
|
77
|
+
if self._left and self._operator and self._right:
|
78
|
+
operator = " | " if self._operator == RedisearchFilterOperator.OR else " "
|
79
|
+
|
80
|
+
left, right = str(self._left), str(self._right)
|
81
|
+
|
82
|
+
if (left == right) and (right == "*"):
|
83
|
+
return "*"
|
84
|
+
|
85
|
+
if (left == "*") and (right != "*"):
|
86
|
+
return right
|
87
|
+
|
88
|
+
if (left != "*") and (right == "*"):
|
89
|
+
return left
|
90
|
+
|
91
|
+
return f"({left}{operator}{right})"
|
92
|
+
|
93
|
+
raise ValueError("Improperly initialized RedisearchFilter")
|
@@ -0,0 +1,153 @@
|
|
1
|
+
from ..schema import RedisearchSchema
|
2
|
+
from ._field.numeric import Numeric
|
3
|
+
from ._field.tag import Tag
|
4
|
+
from ._field.text import Text
|
5
|
+
from ._model import RedisearchFilter
|
6
|
+
from ._types import RedisearchFilterConditions
|
7
|
+
|
8
|
+
|
9
|
+
def create_redisearch_filter(
|
10
|
+
*,
|
11
|
+
filter: RedisearchFilter | RedisearchFilterConditions,
|
12
|
+
schema: RedisearchSchema,
|
13
|
+
) -> RedisearchFilter | None:
|
14
|
+
"""
|
15
|
+
Create a RedisearchFilter from a list of conditions.
|
16
|
+
|
17
|
+
Each condition is combined using the & operator.
|
18
|
+
|
19
|
+
Examples:
|
20
|
+
>>> create_redisearch_filter(
|
21
|
+
... schema=schema,
|
22
|
+
... conditions=[
|
23
|
+
... ["color", "in", ["blue", "red"]],
|
24
|
+
... ["price", "<", 1000],
|
25
|
+
... ["title", "like", "*hello*"]
|
26
|
+
... ],
|
27
|
+
... )
|
28
|
+
"""
|
29
|
+
if isinstance(filter, RedisearchFilter):
|
30
|
+
return filter
|
31
|
+
|
32
|
+
conditions = filter
|
33
|
+
|
34
|
+
filters: list[RedisearchFilter] = []
|
35
|
+
|
36
|
+
for condition in conditions:
|
37
|
+
if len(condition) != 3:
|
38
|
+
raise ValueError("Each condition must have exactly 3 elements")
|
39
|
+
|
40
|
+
field_name = condition[0]
|
41
|
+
|
42
|
+
if not isinstance(field_name, str):
|
43
|
+
raise ValueError("Field name must be a string")
|
44
|
+
|
45
|
+
field = schema.get_field(field_name)
|
46
|
+
|
47
|
+
if field is None:
|
48
|
+
raise ValueError(f"Field '{field_name}' not found in schema")
|
49
|
+
|
50
|
+
if field.type == "tag":
|
51
|
+
filters.append(_create_tag_filter(condition))
|
52
|
+
elif field.type == "numeric":
|
53
|
+
filters.append(_create_numeric_filter(condition))
|
54
|
+
elif field.type == "text":
|
55
|
+
filters.append(_create_text_filter(condition))
|
56
|
+
else:
|
57
|
+
raise ValueError(
|
58
|
+
f"Unsupported field type: {field.type}, field: {field_name}"
|
59
|
+
)
|
60
|
+
|
61
|
+
if not filters:
|
62
|
+
return None
|
63
|
+
|
64
|
+
return _combine_filters(filters)
|
65
|
+
|
66
|
+
|
67
|
+
def _create_tag_filter(condition: list[str | tuple[str]]) -> RedisearchFilter:
|
68
|
+
"""
|
69
|
+
Create a Redisearch tag filter.
|
70
|
+
"""
|
71
|
+
field, operator, values = condition
|
72
|
+
|
73
|
+
tag_field = Tag(str(field))
|
74
|
+
|
75
|
+
result: RedisearchFilter
|
76
|
+
|
77
|
+
if operator == "==" or operator == "=":
|
78
|
+
result = tag_field == values
|
79
|
+
elif operator == "!=":
|
80
|
+
result = tag_field != values
|
81
|
+
elif operator == "in":
|
82
|
+
result = tag_field == values
|
83
|
+
elif operator == "not in":
|
84
|
+
result = tag_field != values
|
85
|
+
else:
|
86
|
+
raise ValueError(f"Invalid operator: {operator}")
|
87
|
+
|
88
|
+
return result
|
89
|
+
|
90
|
+
|
91
|
+
def _create_numeric_filter(condition: list[str | int | float]) -> RedisearchFilter:
|
92
|
+
"""
|
93
|
+
Create a Redisearch numeric filter.
|
94
|
+
"""
|
95
|
+
field, operator, value = condition
|
96
|
+
|
97
|
+
if isinstance(value, str):
|
98
|
+
raise ValueError("Numeric value must be int or float")
|
99
|
+
|
100
|
+
numeric_field = Numeric(str(field))
|
101
|
+
|
102
|
+
result: RedisearchFilter
|
103
|
+
|
104
|
+
if operator == "==" or operator == "=":
|
105
|
+
result = numeric_field == value
|
106
|
+
elif operator == "!=":
|
107
|
+
result = numeric_field != value
|
108
|
+
elif operator == ">":
|
109
|
+
result = numeric_field > value
|
110
|
+
elif operator == "<":
|
111
|
+
result = numeric_field < value
|
112
|
+
elif operator == ">=":
|
113
|
+
result = numeric_field >= value
|
114
|
+
elif operator == "<=":
|
115
|
+
result = numeric_field <= value
|
116
|
+
else:
|
117
|
+
raise ValueError(f"Invalid operator: {operator}")
|
118
|
+
|
119
|
+
return result
|
120
|
+
|
121
|
+
|
122
|
+
def _create_text_filter(condition: list[str]) -> RedisearchFilter:
|
123
|
+
"""
|
124
|
+
Create a Redisearch text filter.
|
125
|
+
"""
|
126
|
+
field, operator, value = condition
|
127
|
+
|
128
|
+
text_field = Text(str(field))
|
129
|
+
value = str(value)
|
130
|
+
result: RedisearchFilter
|
131
|
+
|
132
|
+
if operator == "==" or operator == "=":
|
133
|
+
result = text_field == value
|
134
|
+
elif operator == "!=":
|
135
|
+
result = text_field != value
|
136
|
+
elif operator == "%" or operator == "like":
|
137
|
+
result = text_field % value
|
138
|
+
else:
|
139
|
+
raise ValueError(f"Invalid operator: {operator}")
|
140
|
+
|
141
|
+
return result
|
142
|
+
|
143
|
+
|
144
|
+
def _combine_filters(filters: list[RedisearchFilter]) -> RedisearchFilter:
|
145
|
+
"""
|
146
|
+
Combine multiple RedisearchFilter instances using the & operator.
|
147
|
+
"""
|
148
|
+
combined = filters[0]
|
149
|
+
|
150
|
+
for filter in filters[1:]:
|
151
|
+
combined = combined & filter
|
152
|
+
|
153
|
+
return combined
|