图片解析应用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

210 lines
5.8 KiB

  1. from typing import List
  2. from redis import DataError
  3. class Field:
  4. """
  5. A class representing a field in a document.
  6. """
  7. NUMERIC = "NUMERIC"
  8. TEXT = "TEXT"
  9. WEIGHT = "WEIGHT"
  10. GEO = "GEO"
  11. TAG = "TAG"
  12. VECTOR = "VECTOR"
  13. SORTABLE = "SORTABLE"
  14. NOINDEX = "NOINDEX"
  15. AS = "AS"
  16. GEOSHAPE = "GEOSHAPE"
  17. INDEX_MISSING = "INDEXMISSING"
  18. INDEX_EMPTY = "INDEXEMPTY"
  19. def __init__(
  20. self,
  21. name: str,
  22. args: List[str] = None,
  23. sortable: bool = False,
  24. no_index: bool = False,
  25. index_missing: bool = False,
  26. index_empty: bool = False,
  27. as_name: str = None,
  28. ):
  29. """
  30. Create a new field object.
  31. Args:
  32. name: The name of the field.
  33. args:
  34. sortable: If `True`, the field will be sortable.
  35. no_index: If `True`, the field will not be indexed.
  36. index_missing: If `True`, it will be possible to search for documents that
  37. have this field missing.
  38. index_empty: If `True`, it will be possible to search for documents that
  39. have this field empty.
  40. as_name: If provided, this alias will be used for the field.
  41. """
  42. if args is None:
  43. args = []
  44. self.name = name
  45. self.args = args
  46. self.args_suffix = list()
  47. self.as_name = as_name
  48. if sortable:
  49. self.args_suffix.append(Field.SORTABLE)
  50. if no_index:
  51. self.args_suffix.append(Field.NOINDEX)
  52. if index_missing:
  53. self.args_suffix.append(Field.INDEX_MISSING)
  54. if index_empty:
  55. self.args_suffix.append(Field.INDEX_EMPTY)
  56. if no_index and not sortable:
  57. raise ValueError("Non-Sortable non-Indexable fields are ignored")
  58. def append_arg(self, value):
  59. self.args.append(value)
  60. def redis_args(self):
  61. args = [self.name]
  62. if self.as_name:
  63. args += [self.AS, self.as_name]
  64. args += self.args
  65. args += self.args_suffix
  66. return args
  67. class TextField(Field):
  68. """
  69. TextField is used to define a text field in a schema definition
  70. """
  71. NOSTEM = "NOSTEM"
  72. PHONETIC = "PHONETIC"
  73. def __init__(
  74. self,
  75. name: str,
  76. weight: float = 1.0,
  77. no_stem: bool = False,
  78. phonetic_matcher: str = None,
  79. withsuffixtrie: bool = False,
  80. **kwargs,
  81. ):
  82. Field.__init__(self, name, args=[Field.TEXT, Field.WEIGHT, weight], **kwargs)
  83. if no_stem:
  84. Field.append_arg(self, self.NOSTEM)
  85. if phonetic_matcher and phonetic_matcher in [
  86. "dm:en",
  87. "dm:fr",
  88. "dm:pt",
  89. "dm:es",
  90. ]:
  91. Field.append_arg(self, self.PHONETIC)
  92. Field.append_arg(self, phonetic_matcher)
  93. if withsuffixtrie:
  94. Field.append_arg(self, "WITHSUFFIXTRIE")
  95. class NumericField(Field):
  96. """
  97. NumericField is used to define a numeric field in a schema definition
  98. """
  99. def __init__(self, name: str, **kwargs):
  100. Field.__init__(self, name, args=[Field.NUMERIC], **kwargs)
  101. class GeoShapeField(Field):
  102. """
  103. GeoShapeField is used to enable within/contain indexing/searching
  104. """
  105. SPHERICAL = "SPHERICAL"
  106. FLAT = "FLAT"
  107. def __init__(self, name: str, coord_system=None, **kwargs):
  108. args = [Field.GEOSHAPE]
  109. if coord_system:
  110. args.append(coord_system)
  111. Field.__init__(self, name, args=args, **kwargs)
  112. class GeoField(Field):
  113. """
  114. GeoField is used to define a geo-indexing field in a schema definition
  115. """
  116. def __init__(self, name: str, **kwargs):
  117. Field.__init__(self, name, args=[Field.GEO], **kwargs)
  118. class TagField(Field):
  119. """
  120. TagField is a tag-indexing field with simpler compression and tokenization.
  121. See http://redisearch.io/Tags/
  122. """
  123. SEPARATOR = "SEPARATOR"
  124. CASESENSITIVE = "CASESENSITIVE"
  125. def __init__(
  126. self,
  127. name: str,
  128. separator: str = ",",
  129. case_sensitive: bool = False,
  130. withsuffixtrie: bool = False,
  131. **kwargs,
  132. ):
  133. args = [Field.TAG, self.SEPARATOR, separator]
  134. if case_sensitive:
  135. args.append(self.CASESENSITIVE)
  136. if withsuffixtrie:
  137. args.append("WITHSUFFIXTRIE")
  138. Field.__init__(self, name, args=args, **kwargs)
  139. class VectorField(Field):
  140. """
  141. Allows vector similarity queries against the value in this attribute.
  142. See https://oss.redis.com/redisearch/Vectors/#vector_fields.
  143. """
  144. def __init__(self, name: str, algorithm: str, attributes: dict, **kwargs):
  145. """
  146. Create Vector Field. Notice that Vector cannot have sortable or no_index tag,
  147. although it's also a Field.
  148. ``name`` is the name of the field.
  149. ``algorithm`` can be "FLAT" or "HNSW".
  150. ``attributes`` each algorithm can have specific attributes. Some of them
  151. are mandatory and some of them are optional. See
  152. https://oss.redis.com/redisearch/master/Vectors/#specific_creation_attributes_per_algorithm
  153. for more information.
  154. """
  155. sort = kwargs.get("sortable", False)
  156. noindex = kwargs.get("no_index", False)
  157. if sort or noindex:
  158. raise DataError("Cannot set 'sortable' or 'no_index' in Vector fields.")
  159. if algorithm.upper() not in ["FLAT", "HNSW"]:
  160. raise DataError(
  161. "Realtime vector indexing supporting 2 Indexing Methods:"
  162. "'FLAT' and 'HNSW'."
  163. )
  164. attr_li = []
  165. for key, value in attributes.items():
  166. attr_li.extend([key, value])
  167. Field.__init__(
  168. self, name, args=[Field.VECTOR, algorithm, len(attr_li), *attr_li], **kwargs
  169. )