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.
Files changed (62) hide show
  1. kiarina/lib/redisearch/__init__.py +35 -0
  2. kiarina/lib/redisearch/_async/__init__.py +0 -0
  3. kiarina/lib/redisearch/_async/client.py +181 -0
  4. kiarina/lib/redisearch/_async/registry.py +16 -0
  5. kiarina/lib/redisearch/_core/__init__.py +0 -0
  6. kiarina/lib/redisearch/_core/context.py +69 -0
  7. kiarina/lib/redisearch/_core/operations/__init__.py +0 -0
  8. kiarina/lib/redisearch/_core/operations/count.py +55 -0
  9. kiarina/lib/redisearch/_core/operations/create_index.py +52 -0
  10. kiarina/lib/redisearch/_core/operations/delete.py +43 -0
  11. kiarina/lib/redisearch/_core/operations/drop_index.py +59 -0
  12. kiarina/lib/redisearch/_core/operations/exists_index.py +56 -0
  13. kiarina/lib/redisearch/_core/operations/find.py +105 -0
  14. kiarina/lib/redisearch/_core/operations/get.py +61 -0
  15. kiarina/lib/redisearch/_core/operations/get_info.py +155 -0
  16. kiarina/lib/redisearch/_core/operations/get_key.py +8 -0
  17. kiarina/lib/redisearch/_core/operations/migrate_index.py +160 -0
  18. kiarina/lib/redisearch/_core/operations/reset_index.py +60 -0
  19. kiarina/lib/redisearch/_core/operations/search.py +111 -0
  20. kiarina/lib/redisearch/_core/operations/set.py +65 -0
  21. kiarina/lib/redisearch/_core/utils/__init__.py +0 -0
  22. kiarina/lib/redisearch/_core/utils/calc_score.py +35 -0
  23. kiarina/lib/redisearch/_core/utils/marshal_mappings.py +57 -0
  24. kiarina/lib/redisearch/_core/utils/parse_search_result.py +57 -0
  25. kiarina/lib/redisearch/_core/utils/unmarshal_mappings.py +57 -0
  26. kiarina/lib/redisearch/_core/views/__init__.py +0 -0
  27. kiarina/lib/redisearch/_core/views/document.py +25 -0
  28. kiarina/lib/redisearch/_core/views/info_result.py +24 -0
  29. kiarina/lib/redisearch/_core/views/search_result.py +31 -0
  30. kiarina/lib/redisearch/_sync/__init__.py +0 -0
  31. kiarina/lib/redisearch/_sync/client.py +179 -0
  32. kiarina/lib/redisearch/_sync/registry.py +16 -0
  33. kiarina/lib/redisearch/asyncio.py +33 -0
  34. kiarina/lib/redisearch/filter/__init__.py +61 -0
  35. kiarina/lib/redisearch/filter/_decorators.py +28 -0
  36. kiarina/lib/redisearch/filter/_enums.py +28 -0
  37. kiarina/lib/redisearch/filter/_field/__init__.py +5 -0
  38. kiarina/lib/redisearch/filter/_field/base.py +67 -0
  39. kiarina/lib/redisearch/filter/_field/numeric.py +178 -0
  40. kiarina/lib/redisearch/filter/_field/tag.py +142 -0
  41. kiarina/lib/redisearch/filter/_field/text.py +111 -0
  42. kiarina/lib/redisearch/filter/_model.py +93 -0
  43. kiarina/lib/redisearch/filter/_registry.py +153 -0
  44. kiarina/lib/redisearch/filter/_types.py +32 -0
  45. kiarina/lib/redisearch/filter/_utils.py +18 -0
  46. kiarina/lib/redisearch/py.typed +0 -0
  47. kiarina/lib/redisearch/schema/__init__.py +25 -0
  48. kiarina/lib/redisearch/schema/_field/__init__.py +0 -0
  49. kiarina/lib/redisearch/schema/_field/base.py +20 -0
  50. kiarina/lib/redisearch/schema/_field/numeric.py +33 -0
  51. kiarina/lib/redisearch/schema/_field/tag.py +46 -0
  52. kiarina/lib/redisearch/schema/_field/text.py +44 -0
  53. kiarina/lib/redisearch/schema/_field/vector/__init__.py +0 -0
  54. kiarina/lib/redisearch/schema/_field/vector/base.py +61 -0
  55. kiarina/lib/redisearch/schema/_field/vector/flat.py +40 -0
  56. kiarina/lib/redisearch/schema/_field/vector/hnsw.py +53 -0
  57. kiarina/lib/redisearch/schema/_model.py +98 -0
  58. kiarina/lib/redisearch/schema/_types.py +16 -0
  59. kiarina/lib/redisearch/settings.py +47 -0
  60. kiarina_lib_redisearch-1.0.0.dist-info/METADATA +886 -0
  61. kiarina_lib_redisearch-1.0.0.dist-info/RECORD +62 -0
  62. 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