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,32 @@
1
+ from typing import Any, TypeAlias
2
+
3
+ RedisearchFilterConditions: TypeAlias = list[list[Any]]
4
+ """
5
+ RedisearchFilter に変換可能なリスト
6
+
7
+ Examples:
8
+ >>> [
9
+ ... ["color", "in", ["blue", "red"]],
10
+ ... ["price", "<", 1000],
11
+ ... ["title", "like", "*hello*"]
12
+ ... ]
13
+
14
+ Tag conditions:
15
+ - ["color", "==", "blue"]
16
+ - ["color", "!=", "blue"]
17
+ - ["color", "in", ["blue", "red"]]
18
+ - ["color", "not in", ["blue", "red"]]
19
+
20
+ Numeric conditions:
21
+ - ["price", "==", 1000]
22
+ - ["price", "!=", 1000]
23
+ - ["price", ">", 1000]
24
+ - ["price", "<", 1000]
25
+ - ["price", ">=", 1000]
26
+ - ["price", "<=", 1000]
27
+
28
+ Text conditions:
29
+ - ["title", "==", "hello"]
30
+ - ["title", "!=", "hello"]
31
+ - ["title", "like", "*hello*"]
32
+ """
@@ -0,0 +1,18 @@
1
+ import re
2
+
3
+ ESCAPED_CHARACTERS: str = r"[,.<>{}\[\]\\\"\':;!@#$%^&*()\-+=~\/ ]"
4
+ """
5
+ Regular expression patterns
6
+
7
+ for characters requiring escaping in Redisearch queries
8
+ """
9
+
10
+ ESCAPED_CHARACTERS_RE: re.Pattern[str] = re.compile(ESCAPED_CHARACTERS)
11
+ """Compiled regular expression for escaped characters"""
12
+
13
+
14
+ def escape_token(value: str) -> str:
15
+ """
16
+ Escape special characters in a Redisearch query token.
17
+ """
18
+ return ESCAPED_CHARACTERS_RE.sub(r"\\\g<0>", value)
File without changes
@@ -0,0 +1,25 @@
1
+ """
2
+ フィールド名に `payload`, `distance` は使用できません。
3
+ フィールド名に `id` を使用する場合、ドキュメントの ID と同じ値として扱われます。
4
+ """
5
+
6
+ from ._field.numeric import NumericFieldSchema
7
+ from ._field.tag import TagFieldSchema
8
+ from ._field.text import TextFieldSchema
9
+ from ._field.vector.flat import FlatVectorFieldSchema
10
+ from ._field.vector.hnsw import HNSWVectorFieldSchema
11
+ from ._model import RedisearchSchema
12
+ from ._types import FieldSchema
13
+
14
+ __all__ = [
15
+ # ._field
16
+ "FlatVectorFieldSchema",
17
+ "HNSWVectorFieldSchema",
18
+ "NumericFieldSchema",
19
+ "TagFieldSchema",
20
+ "TextFieldSchema",
21
+ # ._model
22
+ "RedisearchSchema",
23
+ # ._types
24
+ "FieldSchema",
25
+ ]
File without changes
@@ -0,0 +1,20 @@
1
+ from pydantic import BaseModel, Field, field_validator
2
+
3
+
4
+ class BaseFieldSchema(BaseModel):
5
+ """
6
+ Base class of the field schema
7
+ """
8
+
9
+ name: str = Field(...)
10
+ """Redisearch field name"""
11
+
12
+ @field_validator("name")
13
+ @classmethod
14
+ def forbid_reserved_names(cls, v: str) -> str:
15
+ reserved = {"payload", "distance"}
16
+
17
+ if v in reserved:
18
+ raise ValueError(f'"{v}" is a reserved name and cannot be used.')
19
+
20
+ return v
@@ -0,0 +1,33 @@
1
+ from typing import Literal
2
+
3
+ from redis.commands.search.field import NumericField
4
+
5
+ from .base import BaseFieldSchema
6
+
7
+
8
+ class NumericFieldSchema(BaseFieldSchema):
9
+ """
10
+ Schema for numeric fields
11
+ """
12
+
13
+ type: Literal["numeric"] = "numeric"
14
+
15
+ no_index: bool = False
16
+ """Flag to prevent index creation"""
17
+
18
+ sortable: bool | None = False
19
+ """Flag to indicate if the field is sortable"""
20
+
21
+ # --------------------------------------------------
22
+ # Public Methods
23
+ # --------------------------------------------------
24
+
25
+ def to_field(self) -> NumericField:
26
+ """
27
+ Convert the field schema to a Redisearch field
28
+ """
29
+ return NumericField(
30
+ self.name,
31
+ sortable=self.sortable,
32
+ no_index=self.no_index,
33
+ )
@@ -0,0 +1,46 @@
1
+ from typing import Literal
2
+
3
+ from pydantic import Field
4
+ from redis.commands.search.field import TagField
5
+
6
+ from .base import BaseFieldSchema
7
+
8
+
9
+ class TagFieldSchema(BaseFieldSchema):
10
+ """
11
+ Schema for tag fields
12
+ """
13
+
14
+ type: Literal["tag"] = "tag"
15
+
16
+ separator: str = ","
17
+ """Tag separator"""
18
+
19
+ case_sensitive: bool = False
20
+ """Flag to indicate if case sensitivity is enabled"""
21
+
22
+ no_index: bool = False
23
+ """Flag to prevent index creation"""
24
+
25
+ sortable: bool | None = False
26
+ """Flag to indicate if the field is sortable"""
27
+
28
+ multiple: bool = Field(False, exclude=True)
29
+ """
30
+ Flag to indicate if multiple tags are allowed
31
+
32
+ This field is not a feature of Redisearch and is only used within this library.
33
+ Therefore, it does not affect the migration.
34
+ """
35
+
36
+ def to_field(self) -> TagField:
37
+ """
38
+ Convert the field schema to a Redisearch field
39
+ """
40
+ return TagField(
41
+ self.name,
42
+ separator=self.separator,
43
+ case_sensitive=self.case_sensitive,
44
+ sortable=self.sortable,
45
+ no_index=self.no_index,
46
+ )
@@ -0,0 +1,44 @@
1
+ from typing import Literal
2
+
3
+ from redis.commands.search.field import TextField
4
+
5
+ from .base import BaseFieldSchema
6
+
7
+
8
+ class TextFieldSchema(BaseFieldSchema):
9
+ """
10
+ Schema for text fields
11
+ """
12
+
13
+ type: Literal["text"] = "text"
14
+
15
+ weight: float = 1
16
+ """Weight"""
17
+
18
+ no_stem: bool = False
19
+ """Flag to indicate if stemming is disabled"""
20
+
21
+ phonetic_matcher: str | None = None
22
+ """Phonetic matcher"""
23
+
24
+ withsuffixtrie: bool = False
25
+ """Flag to indicate if suffix trie is used"""
26
+
27
+ no_index: bool = False
28
+ """Flag to prevent index creation"""
29
+
30
+ sortable: bool | None = False
31
+ """Flag to indicate if the field is sortable"""
32
+
33
+ def to_field(self) -> TextField:
34
+ """
35
+ Convert the field schema to a Redisearch field
36
+ """
37
+ return TextField(
38
+ self.name,
39
+ weight=self.weight,
40
+ no_stem=self.no_stem,
41
+ phonetic_matcher=self.phonetic_matcher, # type: ignore
42
+ sortable=self.sortable,
43
+ no_index=self.no_index,
44
+ )
@@ -0,0 +1,61 @@
1
+ from typing import Any, Literal
2
+
3
+ import numpy as np
4
+ from pydantic import Field
5
+
6
+ from ..base import BaseFieldSchema
7
+
8
+
9
+ class VectorFieldSchema(BaseFieldSchema):
10
+ """
11
+ Base class for vector field schemas
12
+ """
13
+
14
+ type: Literal["vector"] = "vector"
15
+
16
+ dims: int = Field(...)
17
+ """Dimensionality"""
18
+
19
+ datatype: Literal["FLOAT32", "FLOAT64"] = "FLOAT32"
20
+ """Data type"""
21
+
22
+ distance_metric: Literal["L2", "COSINE", "IP"] = "COSINE"
23
+ """Distance metric"""
24
+
25
+ initial_cap: int | None = None
26
+ """Initial capacity"""
27
+
28
+ # --------------------------------------------------
29
+ # Properties
30
+ # --------------------------------------------------
31
+
32
+ @property
33
+ def dtype(self) -> Any:
34
+ """
35
+ Get the numpy data type
36
+ """
37
+ if self.datatype == "FLOAT32":
38
+ return np.float32
39
+ elif self.datatype == "FLOAT64":
40
+ return np.float64
41
+ else:
42
+ raise ValueError(f"Unsupported datatype: {self.datatype}")
43
+
44
+ # --------------------------------------------------
45
+ # Protected Methods
46
+ # --------------------------------------------------
47
+
48
+ def _get_attributes(self) -> dict[str, Any]:
49
+ """
50
+ Get attributes for the vector field
51
+ """
52
+ attributes = {
53
+ "TYPE": self.datatype,
54
+ "DIM": self.dims,
55
+ "DISTANCE_METRIC": self.distance_metric,
56
+ }
57
+
58
+ if self.initial_cap is not None:
59
+ attributes["INITIAL_CAP"] = self.initial_cap
60
+
61
+ return attributes
@@ -0,0 +1,40 @@
1
+ from typing import Any, Literal
2
+
3
+ from redis.commands.search.field import VectorField
4
+
5
+ from .base import VectorFieldSchema
6
+
7
+
8
+ class FlatVectorFieldSchema(VectorFieldSchema):
9
+ """
10
+ Schema for FLAT vector fields
11
+ """
12
+
13
+ algorithm: Literal["FLAT"] = "FLAT"
14
+
15
+ block_size: int | None = None
16
+
17
+ # --------------------------------------------------
18
+ # Public Methods
19
+ # --------------------------------------------------
20
+
21
+ def to_field(self) -> VectorField:
22
+ """
23
+ Convert field schema to Redisearch field
24
+ """
25
+ return VectorField(self.name, self.algorithm, self._get_attributes())
26
+
27
+ # --------------------------------------------------
28
+ # Protected Methods
29
+ # --------------------------------------------------
30
+
31
+ def _get_attributes(self) -> dict[str, Any]:
32
+ """
33
+ Get attributes for the vector field
34
+ """
35
+ attributes = super()._get_attributes()
36
+
37
+ if self.block_size is not None:
38
+ attributes["BLOCK_SIZE"] = self.block_size
39
+
40
+ return attributes
@@ -0,0 +1,53 @@
1
+ from typing import Any, Literal
2
+
3
+ from pydantic import Field
4
+ from redis.commands.search.field import VectorField
5
+
6
+ from .base import VectorFieldSchema
7
+
8
+
9
+ class HNSWVectorFieldSchema(VectorFieldSchema):
10
+ """
11
+ Schema for HNSW vector fields
12
+ """
13
+
14
+ algorithm: Literal["HNSW"] = "HNSW"
15
+
16
+ m: int = Field(default=16)
17
+
18
+ ef_construction: int = Field(default=200)
19
+
20
+ ef_runtime: int = Field(default=10)
21
+
22
+ epsilon: float = Field(default=0.01)
23
+
24
+ # --------------------------------------------------
25
+ # Public Methods
26
+ # --------------------------------------------------
27
+
28
+ def to_field(self) -> VectorField:
29
+ """
30
+ Convert field schema to Redisearch field
31
+ """
32
+ return VectorField(self.name, self.algorithm, self._get_attributes())
33
+
34
+ # --------------------------------------------------
35
+ # Protected Methods
36
+ # --------------------------------------------------
37
+
38
+ def _get_attributes(self) -> dict[str, Any]:
39
+ """
40
+ Get attributes for the vector field
41
+ """
42
+ attributes = super()._get_attributes()
43
+
44
+ attributes.update(
45
+ {
46
+ "M": self.m,
47
+ "EF_CONSTRUCTION": self.ef_construction,
48
+ "EF_RUNTIME": self.ef_runtime,
49
+ "EPSILON": self.epsilon,
50
+ }
51
+ )
52
+
53
+ return attributes
@@ -0,0 +1,98 @@
1
+ from typing import Any, Self
2
+
3
+ from pydantic import BaseModel
4
+ from pydantic import Field as PydanticField
5
+ from redis.commands.search.field import Field as RedisearchField
6
+
7
+ from ._field.vector.flat import FlatVectorFieldSchema
8
+ from ._field.vector.hnsw import HNSWVectorFieldSchema
9
+ from ._types import FieldSchema
10
+
11
+
12
+ class RedisearchSchema(BaseModel):
13
+ """
14
+ Redisearch index schema
15
+ """
16
+
17
+ fields: list[FieldSchema] = PydanticField(default_factory=list)
18
+ """All fields"""
19
+
20
+ # --------------------------------------------------
21
+ # Properties
22
+ # --------------------------------------------------
23
+
24
+ @property
25
+ def field_names(self) -> list[str]:
26
+ """
27
+ Obtain the names of all fields in the schema
28
+ """
29
+ return [field.name for field in self.fields if field.name]
30
+
31
+ @property
32
+ def vector_field(self) -> FlatVectorFieldSchema | HNSWVectorFieldSchema:
33
+ """
34
+ Obtain the vector field from the schema
35
+
36
+ Even if there are two or more vector fields, the first one is returned.
37
+ The vector field used for vector search is assumed to exist only once,
38
+ if at all, within the schema.
39
+ """
40
+ for field in self.fields:
41
+ if field.type == "vector":
42
+ return field
43
+
44
+ raise ValueError("No vector field found")
45
+
46
+ # --------------------------------------------------
47
+ # Magic Methods
48
+ # --------------------------------------------------
49
+
50
+ def __eq__(self, other: Any) -> bool:
51
+ if not isinstance(other, RedisearchSchema):
52
+ return False
53
+
54
+ # Ignore fields not output by model_dump() when comparing
55
+ return self.model_dump() == other.model_dump()
56
+
57
+ # --------------------------------------------------
58
+ # Public Methods
59
+ # --------------------------------------------------
60
+
61
+ def get_field(self, name: str) -> FieldSchema | None:
62
+ """
63
+ Get a field by its name
64
+
65
+ Args:
66
+ name (str): Field name
67
+
68
+ Returns:
69
+ FieldSchema | None: The field if found, otherwise None
70
+ """
71
+ for field in self.fields:
72
+ if field.name == name:
73
+ return field
74
+
75
+ return None
76
+
77
+ def to_fields(self) -> list[RedisearchField]:
78
+ """
79
+ Convert the schema model to a list of Redisearch fields
80
+ """
81
+ return [field.to_field() for field in self.fields]
82
+
83
+ # --------------------------------------------------
84
+ # Class Methods
85
+ # --------------------------------------------------
86
+
87
+ @classmethod
88
+ def from_field_dicts(cls, field_dicts: list[dict[str, Any]]) -> Self:
89
+ """
90
+ Create a schema model from a list of field dictionaries
91
+
92
+ Args:
93
+ field_dicts (list[dict[str, Any]]): List of field dictionaries
94
+
95
+ Returns:
96
+ RedisearchSchema: The schema model
97
+ """
98
+ return cls.model_validate({"fields": field_dicts})
@@ -0,0 +1,16 @@
1
+ from typing import TypeAlias
2
+
3
+ from ._field.numeric import NumericFieldSchema
4
+ from ._field.tag import TagFieldSchema
5
+ from ._field.text import TextFieldSchema
6
+ from ._field.vector.flat import FlatVectorFieldSchema
7
+ from ._field.vector.hnsw import HNSWVectorFieldSchema
8
+
9
+ FieldSchema: TypeAlias = (
10
+ NumericFieldSchema
11
+ | TagFieldSchema
12
+ | TextFieldSchema
13
+ | FlatVectorFieldSchema
14
+ | HNSWVectorFieldSchema
15
+ )
16
+ """Type of the field schema"""
@@ -0,0 +1,47 @@
1
+ from typing import Any
2
+
3
+ from pydantic_settings import BaseSettings, SettingsConfigDict
4
+ from pydantic_settings_manager import SettingsManager
5
+
6
+
7
+ class RedisearchSettings(BaseSettings):
8
+ """
9
+ Redisearch settings
10
+ """
11
+
12
+ model_config = SettingsConfigDict(env_prefix="KIARINA_LIB_REDISEARCH_")
13
+
14
+ key_prefix: str = ""
15
+ """
16
+ Redis key prefix
17
+
18
+ The prefix for keys of documents registered with Redisearch.
19
+ Specify a string ending with a colon. e.g. "myapp:"
20
+ """
21
+
22
+ index_name: str = "default"
23
+ """
24
+ Redisearch index name
25
+
26
+ Only alphanumeric characters, underscores, hyphens, and periods.
27
+ The beginning consists solely of letters.
28
+ """
29
+
30
+ index_schema: list[dict[str, Any]] | None = None
31
+ """
32
+ Redisearch index schema
33
+
34
+ RedisearchSchema.from_field_dicts can be used to
35
+ create the schema from a list of field dictionaries.
36
+ """
37
+
38
+ protect_index_deletion: bool = False
39
+ """
40
+ Protect index deletion
41
+
42
+ When set to True, the delete_index operation is protected,
43
+ preventing the index from being accidentally deleted.
44
+ """
45
+
46
+
47
+ settings_manager = SettingsManager(RedisearchSettings, multi=True)