hermes-client-python 1.0.4__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.
- hermes_client_python/__init__.py +14 -0
- hermes_client_python/client.py +403 -0
- hermes_client_python/hermes_pb2.py +106 -0
- hermes_client_python/hermes_pb2_grpc.py +511 -0
- hermes_client_python/types.py +48 -0
- hermes_client_python-1.0.4.dist-info/METADATA +232 -0
- hermes_client_python-1.0.4.dist-info/RECORD +8 -0
- hermes_client_python-1.0.4.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Async Python client for Hermes search server."""
|
|
2
|
+
|
|
3
|
+
from .client import HermesClient
|
|
4
|
+
from .types import Document, IndexInfo, SearchHit, SearchResponse
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"HermesClient",
|
|
8
|
+
"Document",
|
|
9
|
+
"SearchHit",
|
|
10
|
+
"SearchResponse",
|
|
11
|
+
"IndexInfo",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
__version__ = "1.0.2"
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"""Async Hermes client implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from collections.abc import AsyncIterator
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import grpc
|
|
10
|
+
from grpc import aio
|
|
11
|
+
|
|
12
|
+
from . import hermes_pb2 as pb
|
|
13
|
+
from . import hermes_pb2_grpc as pb_grpc
|
|
14
|
+
from .types import Document, IndexInfo, SearchHit, SearchResponse
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class HermesClient:
|
|
18
|
+
"""Async client for Hermes search server.
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
async with HermesClient("localhost:50051") as client:
|
|
22
|
+
# Create index
|
|
23
|
+
await client.create_index("articles", '''
|
|
24
|
+
index articles {
|
|
25
|
+
title: text indexed stored
|
|
26
|
+
body: text indexed stored
|
|
27
|
+
}
|
|
28
|
+
''')
|
|
29
|
+
|
|
30
|
+
# Index documents
|
|
31
|
+
await client.index_documents("articles", [
|
|
32
|
+
{"title": "Hello", "body": "World"},
|
|
33
|
+
{"title": "Foo", "body": "Bar"},
|
|
34
|
+
])
|
|
35
|
+
await client.commit("articles")
|
|
36
|
+
|
|
37
|
+
# Search
|
|
38
|
+
results = await client.search("articles", term=("title", "hello"))
|
|
39
|
+
for hit in results.hits:
|
|
40
|
+
print(hit.doc_id, hit.score)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, address: str = "localhost:50051"):
|
|
44
|
+
"""Initialize client.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
address: Server address in format "host:port"
|
|
48
|
+
"""
|
|
49
|
+
self.address = address
|
|
50
|
+
self._channel: aio.Channel | None = None
|
|
51
|
+
self._index_stub: pb_grpc.IndexServiceStub | None = None
|
|
52
|
+
self._search_stub: pb_grpc.SearchServiceStub | None = None
|
|
53
|
+
|
|
54
|
+
async def connect(self) -> None:
|
|
55
|
+
"""Connect to the server."""
|
|
56
|
+
self._channel = aio.insecure_channel(self.address)
|
|
57
|
+
self._index_stub = pb_grpc.IndexServiceStub(self._channel)
|
|
58
|
+
self._search_stub = pb_grpc.SearchServiceStub(self._channel)
|
|
59
|
+
|
|
60
|
+
async def close(self) -> None:
|
|
61
|
+
"""Close the connection."""
|
|
62
|
+
if self._channel:
|
|
63
|
+
await self._channel.close()
|
|
64
|
+
self._channel = None
|
|
65
|
+
self._index_stub = None
|
|
66
|
+
self._search_stub = None
|
|
67
|
+
|
|
68
|
+
async def __aenter__(self) -> HermesClient:
|
|
69
|
+
await self.connect()
|
|
70
|
+
return self
|
|
71
|
+
|
|
72
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
73
|
+
await self.close()
|
|
74
|
+
|
|
75
|
+
def _ensure_connected(self) -> None:
|
|
76
|
+
if self._index_stub is None or self._search_stub is None:
|
|
77
|
+
raise RuntimeError(
|
|
78
|
+
"Client not connected. Use 'async with' or call connect() first."
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# =========================================================================
|
|
82
|
+
# Index Management
|
|
83
|
+
# =========================================================================
|
|
84
|
+
|
|
85
|
+
async def create_index(self, index_name: str, schema: str) -> bool:
|
|
86
|
+
"""Create a new index.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
index_name: Name of the index
|
|
90
|
+
schema: Schema definition in SDL or JSON format
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
True if successful
|
|
94
|
+
|
|
95
|
+
Example SDL schema:
|
|
96
|
+
index myindex {
|
|
97
|
+
title: text indexed stored
|
|
98
|
+
body: text indexed stored
|
|
99
|
+
score: f64 stored
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Example JSON schema:
|
|
103
|
+
{
|
|
104
|
+
"fields": [
|
|
105
|
+
{"name": "title", "type": "text", "indexed": true, "stored": true}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
"""
|
|
109
|
+
self._ensure_connected()
|
|
110
|
+
request = pb.CreateIndexRequest(index_name=index_name, schema=schema)
|
|
111
|
+
response = await self._index_stub.CreateIndex(request)
|
|
112
|
+
return response.success
|
|
113
|
+
|
|
114
|
+
async def delete_index(self, index_name: str) -> bool:
|
|
115
|
+
"""Delete an index.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
index_name: Name of the index to delete
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
True if successful
|
|
122
|
+
"""
|
|
123
|
+
self._ensure_connected()
|
|
124
|
+
request = pb.DeleteIndexRequest(index_name=index_name)
|
|
125
|
+
response = await self._index_stub.DeleteIndex(request)
|
|
126
|
+
return response.success
|
|
127
|
+
|
|
128
|
+
async def get_index_info(self, index_name: str) -> IndexInfo:
|
|
129
|
+
"""Get information about an index.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
index_name: Name of the index
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
IndexInfo with document count, segments, and schema
|
|
136
|
+
"""
|
|
137
|
+
self._ensure_connected()
|
|
138
|
+
request = pb.GetIndexInfoRequest(index_name=index_name)
|
|
139
|
+
response = await self._search_stub.GetIndexInfo(request)
|
|
140
|
+
return IndexInfo(
|
|
141
|
+
index_name=response.index_name,
|
|
142
|
+
num_docs=response.num_docs,
|
|
143
|
+
num_segments=response.num_segments,
|
|
144
|
+
schema=response.schema,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# =========================================================================
|
|
148
|
+
# Document Indexing
|
|
149
|
+
# =========================================================================
|
|
150
|
+
|
|
151
|
+
async def index_documents(
|
|
152
|
+
self, index_name: str, documents: list[dict[str, Any]]
|
|
153
|
+
) -> tuple[int, int]:
|
|
154
|
+
"""Index multiple documents in batch.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
index_name: Name of the index
|
|
158
|
+
documents: List of documents (dicts with field names as keys)
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Tuple of (indexed_count, error_count)
|
|
162
|
+
"""
|
|
163
|
+
self._ensure_connected()
|
|
164
|
+
|
|
165
|
+
named_docs = []
|
|
166
|
+
for doc in documents:
|
|
167
|
+
fields = {k: _to_field_value(v) for k, v in doc.items()}
|
|
168
|
+
named_docs.append(pb.NamedDocument(fields=fields))
|
|
169
|
+
|
|
170
|
+
request = pb.BatchIndexDocumentsRequest(
|
|
171
|
+
index_name=index_name, documents=named_docs
|
|
172
|
+
)
|
|
173
|
+
response = await self._index_stub.BatchIndexDocuments(request)
|
|
174
|
+
return response.indexed_count, response.error_count
|
|
175
|
+
|
|
176
|
+
async def index_document(self, index_name: str, document: dict[str, Any]) -> None:
|
|
177
|
+
"""Index a single document.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
index_name: Name of the index
|
|
181
|
+
document: Document as dict with field names as keys
|
|
182
|
+
"""
|
|
183
|
+
await self.index_documents(index_name, [document])
|
|
184
|
+
|
|
185
|
+
async def index_documents_stream(
|
|
186
|
+
self, index_name: str, documents: AsyncIterator[dict[str, Any]]
|
|
187
|
+
) -> int:
|
|
188
|
+
"""Stream documents for indexing.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
index_name: Name of the index
|
|
192
|
+
documents: Async iterator of documents
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Number of indexed documents
|
|
196
|
+
"""
|
|
197
|
+
self._ensure_connected()
|
|
198
|
+
|
|
199
|
+
async def request_iterator():
|
|
200
|
+
async for doc in documents:
|
|
201
|
+
fields = {k: _to_field_value(v) for k, v in doc.items()}
|
|
202
|
+
yield pb.IndexDocumentRequest(index_name=index_name, fields=fields)
|
|
203
|
+
|
|
204
|
+
response = await self._index_stub.IndexDocuments(request_iterator())
|
|
205
|
+
return response.indexed_count
|
|
206
|
+
|
|
207
|
+
async def commit(self, index_name: str) -> int:
|
|
208
|
+
"""Commit pending changes.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
index_name: Name of the index
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Total number of documents in the index
|
|
215
|
+
"""
|
|
216
|
+
self._ensure_connected()
|
|
217
|
+
request = pb.CommitRequest(index_name=index_name)
|
|
218
|
+
response = await self._index_stub.Commit(request)
|
|
219
|
+
return response.num_docs
|
|
220
|
+
|
|
221
|
+
async def force_merge(self, index_name: str) -> int:
|
|
222
|
+
"""Force merge all segments.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
index_name: Name of the index
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Number of segments after merge
|
|
229
|
+
"""
|
|
230
|
+
self._ensure_connected()
|
|
231
|
+
request = pb.ForceMergeRequest(index_name=index_name)
|
|
232
|
+
response = await self._index_stub.ForceMerge(request)
|
|
233
|
+
return response.num_segments
|
|
234
|
+
|
|
235
|
+
# =========================================================================
|
|
236
|
+
# Search
|
|
237
|
+
# =========================================================================
|
|
238
|
+
|
|
239
|
+
async def search(
|
|
240
|
+
self,
|
|
241
|
+
index_name: str,
|
|
242
|
+
*,
|
|
243
|
+
term: tuple[str, str] | None = None,
|
|
244
|
+
boolean: dict[str, list[tuple[str, str]]] | None = None,
|
|
245
|
+
limit: int = 10,
|
|
246
|
+
offset: int = 0,
|
|
247
|
+
fields_to_load: list[str] | None = None,
|
|
248
|
+
) -> SearchResponse:
|
|
249
|
+
"""Search for documents.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
index_name: Name of the index
|
|
253
|
+
term: Term query as (field, term) tuple
|
|
254
|
+
boolean: Boolean query with "must", "should", "must_not" keys
|
|
255
|
+
limit: Maximum number of results
|
|
256
|
+
offset: Offset for pagination
|
|
257
|
+
fields_to_load: List of fields to include in results
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
SearchResponse with hits
|
|
261
|
+
|
|
262
|
+
Examples:
|
|
263
|
+
# Term query
|
|
264
|
+
results = await client.search("articles", term=("title", "hello"))
|
|
265
|
+
|
|
266
|
+
# Boolean query
|
|
267
|
+
results = await client.search("articles", boolean={
|
|
268
|
+
"must": [("title", "hello")],
|
|
269
|
+
"should": [("body", "world")],
|
|
270
|
+
})
|
|
271
|
+
"""
|
|
272
|
+
self._ensure_connected()
|
|
273
|
+
|
|
274
|
+
query = _build_query(term=term, boolean=boolean)
|
|
275
|
+
|
|
276
|
+
request = pb.SearchRequest(
|
|
277
|
+
index_name=index_name,
|
|
278
|
+
query=query,
|
|
279
|
+
limit=limit,
|
|
280
|
+
offset=offset,
|
|
281
|
+
fields_to_load=fields_to_load or [],
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
response = await self._search_stub.Search(request)
|
|
285
|
+
|
|
286
|
+
hits = [
|
|
287
|
+
SearchHit(
|
|
288
|
+
doc_id=hit.doc_id,
|
|
289
|
+
score=hit.score,
|
|
290
|
+
fields={k: _from_field_value(v) for k, v in hit.fields.items()},
|
|
291
|
+
)
|
|
292
|
+
for hit in response.hits
|
|
293
|
+
]
|
|
294
|
+
|
|
295
|
+
return SearchResponse(
|
|
296
|
+
hits=hits,
|
|
297
|
+
total_hits=response.total_hits,
|
|
298
|
+
took_ms=response.took_ms,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
async def get_document(self, index_name: str, doc_id: int) -> Document | None:
|
|
302
|
+
"""Get a document by ID.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
index_name: Name of the index
|
|
306
|
+
doc_id: Document ID
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Document or None if not found
|
|
310
|
+
"""
|
|
311
|
+
self._ensure_connected()
|
|
312
|
+
request = pb.GetDocumentRequest(index_name=index_name, doc_id=doc_id)
|
|
313
|
+
try:
|
|
314
|
+
response = await self._search_stub.GetDocument(request)
|
|
315
|
+
fields = {k: _from_field_value(v) for k, v in response.fields.items()}
|
|
316
|
+
return Document(fields=fields)
|
|
317
|
+
except grpc.RpcError as e:
|
|
318
|
+
if e.code() == grpc.StatusCode.NOT_FOUND:
|
|
319
|
+
return None
|
|
320
|
+
raise
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
# =============================================================================
|
|
324
|
+
# Helper functions
|
|
325
|
+
# =============================================================================
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _to_field_value(value: Any) -> pb.FieldValue:
|
|
329
|
+
"""Convert Python value to protobuf FieldValue."""
|
|
330
|
+
if isinstance(value, str):
|
|
331
|
+
return pb.FieldValue(text=value)
|
|
332
|
+
elif isinstance(value, bool):
|
|
333
|
+
return pb.FieldValue(u64=1 if value else 0)
|
|
334
|
+
elif isinstance(value, int):
|
|
335
|
+
if value >= 0:
|
|
336
|
+
return pb.FieldValue(u64=value)
|
|
337
|
+
else:
|
|
338
|
+
return pb.FieldValue(i64=value)
|
|
339
|
+
elif isinstance(value, float):
|
|
340
|
+
return pb.FieldValue(f64=value)
|
|
341
|
+
elif isinstance(value, bytes):
|
|
342
|
+
return pb.FieldValue(bytes_value=value)
|
|
343
|
+
elif isinstance(value, (list, dict)):
|
|
344
|
+
# Assume JSON for complex types
|
|
345
|
+
return pb.FieldValue(json_value=json.dumps(value))
|
|
346
|
+
else:
|
|
347
|
+
return pb.FieldValue(text=str(value))
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _from_field_value(fv: pb.FieldValue) -> Any:
|
|
351
|
+
"""Convert protobuf FieldValue to Python value."""
|
|
352
|
+
which = fv.WhichOneof("value")
|
|
353
|
+
if which == "text":
|
|
354
|
+
return fv.text
|
|
355
|
+
elif which == "u64":
|
|
356
|
+
return fv.u64
|
|
357
|
+
elif which == "i64":
|
|
358
|
+
return fv.i64
|
|
359
|
+
elif which == "f64":
|
|
360
|
+
return fv.f64
|
|
361
|
+
elif which == "bytes_value":
|
|
362
|
+
return fv.bytes_value
|
|
363
|
+
elif which == "json_value":
|
|
364
|
+
return json.loads(fv.json_value)
|
|
365
|
+
elif which == "sparse_vector":
|
|
366
|
+
return {
|
|
367
|
+
"indices": list(fv.sparse_vector.indices),
|
|
368
|
+
"values": list(fv.sparse_vector.values),
|
|
369
|
+
}
|
|
370
|
+
elif which == "dense_vector":
|
|
371
|
+
return list(fv.dense_vector.values)
|
|
372
|
+
return None
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _build_query(
|
|
376
|
+
*,
|
|
377
|
+
term: tuple[str, str] | None = None,
|
|
378
|
+
boolean: dict[str, list[tuple[str, str]]] | None = None,
|
|
379
|
+
) -> pb.Query:
|
|
380
|
+
"""Build a protobuf Query from parameters."""
|
|
381
|
+
if term is not None:
|
|
382
|
+
field, value = term
|
|
383
|
+
return pb.Query(term=pb.TermQuery(field=field, term=value))
|
|
384
|
+
|
|
385
|
+
if boolean is not None:
|
|
386
|
+
must = [
|
|
387
|
+
pb.Query(term=pb.TermQuery(field=f, term=t))
|
|
388
|
+
for f, t in boolean.get("must", [])
|
|
389
|
+
]
|
|
390
|
+
should = [
|
|
391
|
+
pb.Query(term=pb.TermQuery(field=f, term=t))
|
|
392
|
+
for f, t in boolean.get("should", [])
|
|
393
|
+
]
|
|
394
|
+
must_not = [
|
|
395
|
+
pb.Query(term=pb.TermQuery(field=f, term=t))
|
|
396
|
+
for f, t in boolean.get("must_not", [])
|
|
397
|
+
]
|
|
398
|
+
return pb.Query(
|
|
399
|
+
boolean=pb.BooleanQuery(must=must, should=should, must_not=must_not)
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
# Default: match all (empty boolean query)
|
|
403
|
+
return pb.Query(boolean=pb.BooleanQuery())
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
2
|
+
# NO CHECKED-IN PROTOBUF GENCODE
|
|
3
|
+
# source: hermes.proto
|
|
4
|
+
# Protobuf Python Version: 6.31.1
|
|
5
|
+
"""Generated protocol buffer code."""
|
|
6
|
+
|
|
7
|
+
from google.protobuf import descriptor as _descriptor
|
|
8
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
9
|
+
from google.protobuf import runtime_version as _runtime_version
|
|
10
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
11
|
+
from google.protobuf.internal import builder as _builder
|
|
12
|
+
|
|
13
|
+
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
14
|
+
_runtime_version.Domain.PUBLIC, 6, 31, 1, "", "hermes.proto"
|
|
15
|
+
)
|
|
16
|
+
# @@protoc_insertion_point(imports)
|
|
17
|
+
|
|
18
|
+
_sym_db = _symbol_database.Default()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
|
|
22
|
+
b'\n\x0chermes.proto\x12\x06hermes"\xa2\x01\n\x05Query\x12!\n\x04term\x18\x01 \x01(\x0b\x32\x11.hermes.TermQueryH\x00\x12\'\n\x07\x62oolean\x18\x02 \x01(\x0b\x32\x14.hermes.BooleanQueryH\x00\x12#\n\x05\x62oost\x18\x03 \x01(\x0b\x32\x12.hermes.BoostQueryH\x00\x12\x1f\n\x03\x61ll\x18\x04 \x01(\x0b\x32\x10.hermes.AllQueryH\x00\x42\x07\n\x05query"(\n\tTermQuery\x12\r\n\x05\x66ield\x18\x01 \x01(\t\x12\x0c\n\x04term\x18\x02 \x01(\t"k\n\x0c\x42ooleanQuery\x12\x1b\n\x04must\x18\x01 \x03(\x0b\x32\r.hermes.Query\x12\x1d\n\x06should\x18\x02 \x03(\x0b\x32\r.hermes.Query\x12\x1f\n\x08must_not\x18\x03 \x03(\x0b\x32\r.hermes.Query"9\n\nBoostQuery\x12\x1c\n\x05query\x18\x01 \x01(\x0b\x32\r.hermes.Query\x12\r\n\x05\x62oost\x18\x02 \x01(\x02"\n\n\x08\x41llQuery"x\n\rSearchRequest\x12\x12\n\nindex_name\x18\x01 \x01(\t\x12\x1c\n\x05query\x18\x02 \x01(\x0b\x32\r.hermes.Query\x12\r\n\x05limit\x18\x03 \x01(\r\x12\x0e\n\x06offset\x18\x04 \x01(\r\x12\x16\n\x0e\x66ields_to_load\x18\x05 \x03(\t"\x9c\x01\n\tSearchHit\x12\x0e\n\x06\x64oc_id\x18\x01 \x01(\r\x12\r\n\x05score\x18\x02 \x01(\x02\x12-\n\x06\x66ields\x18\x03 \x03(\x0b\x32\x1d.hermes.SearchHit.FieldsEntry\x1a\x41\n\x0b\x46ieldsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.hermes.FieldValue:\x02\x38\x01"\xdb\x01\n\nFieldValue\x12\x0e\n\x04text\x18\x01 \x01(\tH\x00\x12\r\n\x03u64\x18\x02 \x01(\x04H\x00\x12\r\n\x03i64\x18\x03 \x01(\x03H\x00\x12\r\n\x03\x66\x36\x34\x18\x04 \x01(\x01H\x00\x12\x15\n\x0b\x62ytes_value\x18\x05 \x01(\x0cH\x00\x12-\n\rsparse_vector\x18\x06 \x01(\x0b\x32\x14.hermes.SparseVectorH\x00\x12+\n\x0c\x64\x65nse_vector\x18\x07 \x01(\x0b\x32\x13.hermes.DenseVectorH\x00\x12\x14\n\njson_value\x18\x08 \x01(\tH\x00\x42\x07\n\x05value"/\n\x0cSparseVector\x12\x0f\n\x07indices\x18\x01 \x03(\r\x12\x0e\n\x06values\x18\x02 \x03(\x02"\x1d\n\x0b\x44\x65nseVector\x12\x0e\n\x06values\x18\x01 \x03(\x02"V\n\x0eSearchResponse\x12\x1f\n\x04hits\x18\x01 \x03(\x0b\x32\x11.hermes.SearchHit\x12\x12\n\ntotal_hits\x18\x02 \x01(\r\x12\x0f\n\x07took_ms\x18\x03 \x01(\x04"8\n\x12GetDocumentRequest\x12\x12\n\nindex_name\x18\x01 \x01(\t\x12\x0e\n\x06\x64oc_id\x18\x02 \x01(\r"\x91\x01\n\x13GetDocumentResponse\x12\x37\n\x06\x66ields\x18\x01 \x03(\x0b\x32\'.hermes.GetDocumentResponse.FieldsEntry\x1a\x41\n\x0b\x46ieldsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.hermes.FieldValue:\x02\x38\x01")\n\x13GetIndexInfoRequest\x12\x12\n\nindex_name\x18\x01 \x01(\t"b\n\x14GetIndexInfoResponse\x12\x12\n\nindex_name\x18\x01 \x01(\t\x12\x10\n\x08num_docs\x18\x02 \x01(\r\x12\x14\n\x0cnum_segments\x18\x03 \x01(\r\x12\x0e\n\x06schema\x18\x04 \x01(\t"8\n\x12\x43reateIndexRequest\x12\x12\n\nindex_name\x18\x01 \x01(\t\x12\x0e\n\x06schema\x18\x02 \x01(\t"&\n\x13\x43reateIndexResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08"\x85\x01\n\rNamedDocument\x12\x31\n\x06\x66ields\x18\x01 \x03(\x0b\x32!.hermes.NamedDocument.FieldsEntry\x1a\x41\n\x0b\x46ieldsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.hermes.FieldValue:\x02\x38\x01"Z\n\x1a\x42\x61tchIndexDocumentsRequest\x12\x12\n\nindex_name\x18\x01 \x01(\t\x12(\n\tdocuments\x18\x02 \x03(\x0b\x32\x15.hermes.NamedDocument"I\n\x1b\x42\x61tchIndexDocumentsResponse\x12\x15\n\rindexed_count\x18\x01 \x01(\r\x12\x13\n\x0b\x65rror_count\x18\x02 \x01(\r"\xa7\x01\n\x14IndexDocumentRequest\x12\x12\n\nindex_name\x18\x01 \x01(\t\x12\x38\n\x06\x66ields\x18\x02 \x03(\x0b\x32(.hermes.IndexDocumentRequest.FieldsEntry\x1a\x41\n\x0b\x46ieldsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.hermes.FieldValue:\x02\x38\x01"/\n\x16IndexDocumentsResponse\x12\x15\n\rindexed_count\x18\x01 \x01(\r"#\n\rCommitRequest\x12\x12\n\nindex_name\x18\x01 \x01(\t"3\n\x0e\x43ommitResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x10\n\x08num_docs\x18\x02 \x01(\r"\'\n\x11\x46orceMergeRequest\x12\x12\n\nindex_name\x18\x01 \x01(\t";\n\x12\x46orceMergeResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x14\n\x0cnum_segments\x18\x02 \x01(\r"(\n\x12\x44\x65leteIndexRequest\x12\x12\n\nindex_name\x18\x01 \x01(\t"&\n\x13\x44\x65leteIndexResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x32\xdb\x01\n\rSearchService\x12\x37\n\x06Search\x12\x15.hermes.SearchRequest\x1a\x16.hermes.SearchResponse\x12\x46\n\x0bGetDocument\x12\x1a.hermes.GetDocumentRequest\x1a\x1b.hermes.GetDocumentResponse\x12I\n\x0cGetIndexInfo\x12\x1b.hermes.GetIndexInfoRequest\x1a\x1c.hermes.GetIndexInfoResponse2\xce\x03\n\x0cIndexService\x12\x46\n\x0b\x43reateIndex\x12\x1a.hermes.CreateIndexRequest\x1a\x1b.hermes.CreateIndexResponse\x12P\n\x0eIndexDocuments\x12\x1c.hermes.IndexDocumentRequest\x1a\x1e.hermes.IndexDocumentsResponse(\x01\x12^\n\x13\x42\x61tchIndexDocuments\x12".hermes.BatchIndexDocumentsRequest\x1a#.hermes.BatchIndexDocumentsResponse\x12\x37\n\x06\x43ommit\x12\x15.hermes.CommitRequest\x1a\x16.hermes.CommitResponse\x12\x43\n\nForceMerge\x12\x19.hermes.ForceMergeRequest\x1a\x1a.hermes.ForceMergeResponse\x12\x46\n\x0b\x44\x65leteIndex\x12\x1a.hermes.DeleteIndexRequest\x1a\x1b.hermes.DeleteIndexResponseb\x06proto3'
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
_globals = globals()
|
|
26
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
27
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "hermes_pb2", _globals)
|
|
28
|
+
if not _descriptor._USE_C_DESCRIPTORS:
|
|
29
|
+
DESCRIPTOR._loaded_options = None
|
|
30
|
+
_globals["_SEARCHHIT_FIELDSENTRY"]._loaded_options = None
|
|
31
|
+
_globals["_SEARCHHIT_FIELDSENTRY"]._serialized_options = b"8\001"
|
|
32
|
+
_globals["_GETDOCUMENTRESPONSE_FIELDSENTRY"]._loaded_options = None
|
|
33
|
+
_globals["_GETDOCUMENTRESPONSE_FIELDSENTRY"]._serialized_options = b"8\001"
|
|
34
|
+
_globals["_NAMEDDOCUMENT_FIELDSENTRY"]._loaded_options = None
|
|
35
|
+
_globals["_NAMEDDOCUMENT_FIELDSENTRY"]._serialized_options = b"8\001"
|
|
36
|
+
_globals["_INDEXDOCUMENTREQUEST_FIELDSENTRY"]._loaded_options = None
|
|
37
|
+
_globals["_INDEXDOCUMENTREQUEST_FIELDSENTRY"]._serialized_options = b"8\001"
|
|
38
|
+
_globals["_QUERY"]._serialized_start = 25
|
|
39
|
+
_globals["_QUERY"]._serialized_end = 187
|
|
40
|
+
_globals["_TERMQUERY"]._serialized_start = 189
|
|
41
|
+
_globals["_TERMQUERY"]._serialized_end = 229
|
|
42
|
+
_globals["_BOOLEANQUERY"]._serialized_start = 231
|
|
43
|
+
_globals["_BOOLEANQUERY"]._serialized_end = 338
|
|
44
|
+
_globals["_BOOSTQUERY"]._serialized_start = 340
|
|
45
|
+
_globals["_BOOSTQUERY"]._serialized_end = 397
|
|
46
|
+
_globals["_ALLQUERY"]._serialized_start = 399
|
|
47
|
+
_globals["_ALLQUERY"]._serialized_end = 409
|
|
48
|
+
_globals["_SEARCHREQUEST"]._serialized_start = 411
|
|
49
|
+
_globals["_SEARCHREQUEST"]._serialized_end = 531
|
|
50
|
+
_globals["_SEARCHHIT"]._serialized_start = 534
|
|
51
|
+
_globals["_SEARCHHIT"]._serialized_end = 690
|
|
52
|
+
_globals["_SEARCHHIT_FIELDSENTRY"]._serialized_start = 625
|
|
53
|
+
_globals["_SEARCHHIT_FIELDSENTRY"]._serialized_end = 690
|
|
54
|
+
_globals["_FIELDVALUE"]._serialized_start = 693
|
|
55
|
+
_globals["_FIELDVALUE"]._serialized_end = 912
|
|
56
|
+
_globals["_SPARSEVECTOR"]._serialized_start = 914
|
|
57
|
+
_globals["_SPARSEVECTOR"]._serialized_end = 961
|
|
58
|
+
_globals["_DENSEVECTOR"]._serialized_start = 963
|
|
59
|
+
_globals["_DENSEVECTOR"]._serialized_end = 992
|
|
60
|
+
_globals["_SEARCHRESPONSE"]._serialized_start = 994
|
|
61
|
+
_globals["_SEARCHRESPONSE"]._serialized_end = 1080
|
|
62
|
+
_globals["_GETDOCUMENTREQUEST"]._serialized_start = 1082
|
|
63
|
+
_globals["_GETDOCUMENTREQUEST"]._serialized_end = 1138
|
|
64
|
+
_globals["_GETDOCUMENTRESPONSE"]._serialized_start = 1141
|
|
65
|
+
_globals["_GETDOCUMENTRESPONSE"]._serialized_end = 1286
|
|
66
|
+
_globals["_GETDOCUMENTRESPONSE_FIELDSENTRY"]._serialized_start = 625
|
|
67
|
+
_globals["_GETDOCUMENTRESPONSE_FIELDSENTRY"]._serialized_end = 690
|
|
68
|
+
_globals["_GETINDEXINFOREQUEST"]._serialized_start = 1288
|
|
69
|
+
_globals["_GETINDEXINFOREQUEST"]._serialized_end = 1329
|
|
70
|
+
_globals["_GETINDEXINFORESPONSE"]._serialized_start = 1331
|
|
71
|
+
_globals["_GETINDEXINFORESPONSE"]._serialized_end = 1429
|
|
72
|
+
_globals["_CREATEINDEXREQUEST"]._serialized_start = 1431
|
|
73
|
+
_globals["_CREATEINDEXREQUEST"]._serialized_end = 1487
|
|
74
|
+
_globals["_CREATEINDEXRESPONSE"]._serialized_start = 1489
|
|
75
|
+
_globals["_CREATEINDEXRESPONSE"]._serialized_end = 1527
|
|
76
|
+
_globals["_NAMEDDOCUMENT"]._serialized_start = 1530
|
|
77
|
+
_globals["_NAMEDDOCUMENT"]._serialized_end = 1663
|
|
78
|
+
_globals["_NAMEDDOCUMENT_FIELDSENTRY"]._serialized_start = 625
|
|
79
|
+
_globals["_NAMEDDOCUMENT_FIELDSENTRY"]._serialized_end = 690
|
|
80
|
+
_globals["_BATCHINDEXDOCUMENTSREQUEST"]._serialized_start = 1665
|
|
81
|
+
_globals["_BATCHINDEXDOCUMENTSREQUEST"]._serialized_end = 1755
|
|
82
|
+
_globals["_BATCHINDEXDOCUMENTSRESPONSE"]._serialized_start = 1757
|
|
83
|
+
_globals["_BATCHINDEXDOCUMENTSRESPONSE"]._serialized_end = 1830
|
|
84
|
+
_globals["_INDEXDOCUMENTREQUEST"]._serialized_start = 1833
|
|
85
|
+
_globals["_INDEXDOCUMENTREQUEST"]._serialized_end = 2000
|
|
86
|
+
_globals["_INDEXDOCUMENTREQUEST_FIELDSENTRY"]._serialized_start = 625
|
|
87
|
+
_globals["_INDEXDOCUMENTREQUEST_FIELDSENTRY"]._serialized_end = 690
|
|
88
|
+
_globals["_INDEXDOCUMENTSRESPONSE"]._serialized_start = 2002
|
|
89
|
+
_globals["_INDEXDOCUMENTSRESPONSE"]._serialized_end = 2049
|
|
90
|
+
_globals["_COMMITREQUEST"]._serialized_start = 2051
|
|
91
|
+
_globals["_COMMITREQUEST"]._serialized_end = 2086
|
|
92
|
+
_globals["_COMMITRESPONSE"]._serialized_start = 2088
|
|
93
|
+
_globals["_COMMITRESPONSE"]._serialized_end = 2139
|
|
94
|
+
_globals["_FORCEMERGEREQUEST"]._serialized_start = 2141
|
|
95
|
+
_globals["_FORCEMERGEREQUEST"]._serialized_end = 2180
|
|
96
|
+
_globals["_FORCEMERGERESPONSE"]._serialized_start = 2182
|
|
97
|
+
_globals["_FORCEMERGERESPONSE"]._serialized_end = 2241
|
|
98
|
+
_globals["_DELETEINDEXREQUEST"]._serialized_start = 2243
|
|
99
|
+
_globals["_DELETEINDEXREQUEST"]._serialized_end = 2283
|
|
100
|
+
_globals["_DELETEINDEXRESPONSE"]._serialized_start = 2285
|
|
101
|
+
_globals["_DELETEINDEXRESPONSE"]._serialized_end = 2323
|
|
102
|
+
_globals["_SEARCHSERVICE"]._serialized_start = 2326
|
|
103
|
+
_globals["_SEARCHSERVICE"]._serialized_end = 2545
|
|
104
|
+
_globals["_INDEXSERVICE"]._serialized_start = 2548
|
|
105
|
+
_globals["_INDEXSERVICE"]._serialized_end = 3010
|
|
106
|
+
# @@protoc_insertion_point(module_scope)
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
|
2
|
+
"""Client and server classes corresponding to protobuf-defined services."""
|
|
3
|
+
|
|
4
|
+
import grpc
|
|
5
|
+
|
|
6
|
+
from . import hermes_pb2 as hermes__pb2
|
|
7
|
+
|
|
8
|
+
GRPC_GENERATED_VERSION = "1.76.0"
|
|
9
|
+
GRPC_VERSION = grpc.__version__
|
|
10
|
+
_version_not_supported = False
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from grpc._utilities import first_version_is_lower
|
|
14
|
+
|
|
15
|
+
_version_not_supported = first_version_is_lower(
|
|
16
|
+
GRPC_VERSION, GRPC_GENERATED_VERSION
|
|
17
|
+
)
|
|
18
|
+
except ImportError:
|
|
19
|
+
_version_not_supported = True
|
|
20
|
+
|
|
21
|
+
if _version_not_supported:
|
|
22
|
+
raise RuntimeError(
|
|
23
|
+
f"The grpc package installed is at version {GRPC_VERSION},"
|
|
24
|
+
+ " but the generated code in hermes_pb2_grpc.py depends on"
|
|
25
|
+
+ f" grpcio>={GRPC_GENERATED_VERSION}."
|
|
26
|
+
+ f" Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}"
|
|
27
|
+
+ f" or downgrade your generated code using grpcio-tools<={GRPC_VERSION}."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SearchServiceStub:
|
|
32
|
+
"""Search service"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, channel):
|
|
35
|
+
"""Constructor.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
channel: A grpc.Channel.
|
|
39
|
+
"""
|
|
40
|
+
self.Search = channel.unary_unary(
|
|
41
|
+
"/hermes.SearchService/Search",
|
|
42
|
+
request_serializer=hermes__pb2.SearchRequest.SerializeToString,
|
|
43
|
+
response_deserializer=hermes__pb2.SearchResponse.FromString,
|
|
44
|
+
_registered_method=True,
|
|
45
|
+
)
|
|
46
|
+
self.GetDocument = channel.unary_unary(
|
|
47
|
+
"/hermes.SearchService/GetDocument",
|
|
48
|
+
request_serializer=hermes__pb2.GetDocumentRequest.SerializeToString,
|
|
49
|
+
response_deserializer=hermes__pb2.GetDocumentResponse.FromString,
|
|
50
|
+
_registered_method=True,
|
|
51
|
+
)
|
|
52
|
+
self.GetIndexInfo = channel.unary_unary(
|
|
53
|
+
"/hermes.SearchService/GetIndexInfo",
|
|
54
|
+
request_serializer=hermes__pb2.GetIndexInfoRequest.SerializeToString,
|
|
55
|
+
response_deserializer=hermes__pb2.GetIndexInfoResponse.FromString,
|
|
56
|
+
_registered_method=True,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class SearchServiceServicer:
|
|
61
|
+
"""Search service"""
|
|
62
|
+
|
|
63
|
+
def Search(self, request, context):
|
|
64
|
+
"""Search for documents"""
|
|
65
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
66
|
+
context.set_details("Method not implemented!")
|
|
67
|
+
raise NotImplementedError("Method not implemented!")
|
|
68
|
+
|
|
69
|
+
def GetDocument(self, request, context):
|
|
70
|
+
"""Get document by ID"""
|
|
71
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
72
|
+
context.set_details("Method not implemented!")
|
|
73
|
+
raise NotImplementedError("Method not implemented!")
|
|
74
|
+
|
|
75
|
+
def GetIndexInfo(self, request, context):
|
|
76
|
+
"""Get index info"""
|
|
77
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
78
|
+
context.set_details("Method not implemented!")
|
|
79
|
+
raise NotImplementedError("Method not implemented!")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def add_SearchServiceServicer_to_server(servicer, server):
|
|
83
|
+
rpc_method_handlers = {
|
|
84
|
+
"Search": grpc.unary_unary_rpc_method_handler(
|
|
85
|
+
servicer.Search,
|
|
86
|
+
request_deserializer=hermes__pb2.SearchRequest.FromString,
|
|
87
|
+
response_serializer=hermes__pb2.SearchResponse.SerializeToString,
|
|
88
|
+
),
|
|
89
|
+
"GetDocument": grpc.unary_unary_rpc_method_handler(
|
|
90
|
+
servicer.GetDocument,
|
|
91
|
+
request_deserializer=hermes__pb2.GetDocumentRequest.FromString,
|
|
92
|
+
response_serializer=hermes__pb2.GetDocumentResponse.SerializeToString,
|
|
93
|
+
),
|
|
94
|
+
"GetIndexInfo": grpc.unary_unary_rpc_method_handler(
|
|
95
|
+
servicer.GetIndexInfo,
|
|
96
|
+
request_deserializer=hermes__pb2.GetIndexInfoRequest.FromString,
|
|
97
|
+
response_serializer=hermes__pb2.GetIndexInfoResponse.SerializeToString,
|
|
98
|
+
),
|
|
99
|
+
}
|
|
100
|
+
generic_handler = grpc.method_handlers_generic_handler(
|
|
101
|
+
"hermes.SearchService", rpc_method_handlers
|
|
102
|
+
)
|
|
103
|
+
server.add_generic_rpc_handlers((generic_handler,))
|
|
104
|
+
server.add_registered_method_handlers("hermes.SearchService", rpc_method_handlers)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# This class is part of an EXPERIMENTAL API.
|
|
108
|
+
class SearchService:
|
|
109
|
+
"""Search service"""
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def Search(
|
|
113
|
+
request,
|
|
114
|
+
target,
|
|
115
|
+
options=(),
|
|
116
|
+
channel_credentials=None,
|
|
117
|
+
call_credentials=None,
|
|
118
|
+
insecure=False,
|
|
119
|
+
compression=None,
|
|
120
|
+
wait_for_ready=None,
|
|
121
|
+
timeout=None,
|
|
122
|
+
metadata=None,
|
|
123
|
+
):
|
|
124
|
+
return grpc.experimental.unary_unary(
|
|
125
|
+
request,
|
|
126
|
+
target,
|
|
127
|
+
"/hermes.SearchService/Search",
|
|
128
|
+
hermes__pb2.SearchRequest.SerializeToString,
|
|
129
|
+
hermes__pb2.SearchResponse.FromString,
|
|
130
|
+
options,
|
|
131
|
+
channel_credentials,
|
|
132
|
+
insecure,
|
|
133
|
+
call_credentials,
|
|
134
|
+
compression,
|
|
135
|
+
wait_for_ready,
|
|
136
|
+
timeout,
|
|
137
|
+
metadata,
|
|
138
|
+
_registered_method=True,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def GetDocument(
|
|
143
|
+
request,
|
|
144
|
+
target,
|
|
145
|
+
options=(),
|
|
146
|
+
channel_credentials=None,
|
|
147
|
+
call_credentials=None,
|
|
148
|
+
insecure=False,
|
|
149
|
+
compression=None,
|
|
150
|
+
wait_for_ready=None,
|
|
151
|
+
timeout=None,
|
|
152
|
+
metadata=None,
|
|
153
|
+
):
|
|
154
|
+
return grpc.experimental.unary_unary(
|
|
155
|
+
request,
|
|
156
|
+
target,
|
|
157
|
+
"/hermes.SearchService/GetDocument",
|
|
158
|
+
hermes__pb2.GetDocumentRequest.SerializeToString,
|
|
159
|
+
hermes__pb2.GetDocumentResponse.FromString,
|
|
160
|
+
options,
|
|
161
|
+
channel_credentials,
|
|
162
|
+
insecure,
|
|
163
|
+
call_credentials,
|
|
164
|
+
compression,
|
|
165
|
+
wait_for_ready,
|
|
166
|
+
timeout,
|
|
167
|
+
metadata,
|
|
168
|
+
_registered_method=True,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
@staticmethod
|
|
172
|
+
def GetIndexInfo(
|
|
173
|
+
request,
|
|
174
|
+
target,
|
|
175
|
+
options=(),
|
|
176
|
+
channel_credentials=None,
|
|
177
|
+
call_credentials=None,
|
|
178
|
+
insecure=False,
|
|
179
|
+
compression=None,
|
|
180
|
+
wait_for_ready=None,
|
|
181
|
+
timeout=None,
|
|
182
|
+
metadata=None,
|
|
183
|
+
):
|
|
184
|
+
return grpc.experimental.unary_unary(
|
|
185
|
+
request,
|
|
186
|
+
target,
|
|
187
|
+
"/hermes.SearchService/GetIndexInfo",
|
|
188
|
+
hermes__pb2.GetIndexInfoRequest.SerializeToString,
|
|
189
|
+
hermes__pb2.GetIndexInfoResponse.FromString,
|
|
190
|
+
options,
|
|
191
|
+
channel_credentials,
|
|
192
|
+
insecure,
|
|
193
|
+
call_credentials,
|
|
194
|
+
compression,
|
|
195
|
+
wait_for_ready,
|
|
196
|
+
timeout,
|
|
197
|
+
metadata,
|
|
198
|
+
_registered_method=True,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class IndexServiceStub:
|
|
203
|
+
"""Index service"""
|
|
204
|
+
|
|
205
|
+
def __init__(self, channel):
|
|
206
|
+
"""Constructor.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
channel: A grpc.Channel.
|
|
210
|
+
"""
|
|
211
|
+
self.CreateIndex = channel.unary_unary(
|
|
212
|
+
"/hermes.IndexService/CreateIndex",
|
|
213
|
+
request_serializer=hermes__pb2.CreateIndexRequest.SerializeToString,
|
|
214
|
+
response_deserializer=hermes__pb2.CreateIndexResponse.FromString,
|
|
215
|
+
_registered_method=True,
|
|
216
|
+
)
|
|
217
|
+
self.IndexDocuments = channel.stream_unary(
|
|
218
|
+
"/hermes.IndexService/IndexDocuments",
|
|
219
|
+
request_serializer=hermes__pb2.IndexDocumentRequest.SerializeToString,
|
|
220
|
+
response_deserializer=hermes__pb2.IndexDocumentsResponse.FromString,
|
|
221
|
+
_registered_method=True,
|
|
222
|
+
)
|
|
223
|
+
self.BatchIndexDocuments = channel.unary_unary(
|
|
224
|
+
"/hermes.IndexService/BatchIndexDocuments",
|
|
225
|
+
request_serializer=hermes__pb2.BatchIndexDocumentsRequest.SerializeToString,
|
|
226
|
+
response_deserializer=hermes__pb2.BatchIndexDocumentsResponse.FromString,
|
|
227
|
+
_registered_method=True,
|
|
228
|
+
)
|
|
229
|
+
self.Commit = channel.unary_unary(
|
|
230
|
+
"/hermes.IndexService/Commit",
|
|
231
|
+
request_serializer=hermes__pb2.CommitRequest.SerializeToString,
|
|
232
|
+
response_deserializer=hermes__pb2.CommitResponse.FromString,
|
|
233
|
+
_registered_method=True,
|
|
234
|
+
)
|
|
235
|
+
self.ForceMerge = channel.unary_unary(
|
|
236
|
+
"/hermes.IndexService/ForceMerge",
|
|
237
|
+
request_serializer=hermes__pb2.ForceMergeRequest.SerializeToString,
|
|
238
|
+
response_deserializer=hermes__pb2.ForceMergeResponse.FromString,
|
|
239
|
+
_registered_method=True,
|
|
240
|
+
)
|
|
241
|
+
self.DeleteIndex = channel.unary_unary(
|
|
242
|
+
"/hermes.IndexService/DeleteIndex",
|
|
243
|
+
request_serializer=hermes__pb2.DeleteIndexRequest.SerializeToString,
|
|
244
|
+
response_deserializer=hermes__pb2.DeleteIndexResponse.FromString,
|
|
245
|
+
_registered_method=True,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class IndexServiceServicer:
|
|
250
|
+
"""Index service"""
|
|
251
|
+
|
|
252
|
+
def CreateIndex(self, request, context):
|
|
253
|
+
"""Create a new index (supports both structured schema and SDL string)"""
|
|
254
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
255
|
+
context.set_details("Method not implemented!")
|
|
256
|
+
raise NotImplementedError("Method not implemented!")
|
|
257
|
+
|
|
258
|
+
def IndexDocuments(self, request_iterator, context):
|
|
259
|
+
"""Add documents to index (streaming)"""
|
|
260
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
261
|
+
context.set_details("Method not implemented!")
|
|
262
|
+
raise NotImplementedError("Method not implemented!")
|
|
263
|
+
|
|
264
|
+
def BatchIndexDocuments(self, request, context):
|
|
265
|
+
"""Add documents in batch"""
|
|
266
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
267
|
+
context.set_details("Method not implemented!")
|
|
268
|
+
raise NotImplementedError("Method not implemented!")
|
|
269
|
+
|
|
270
|
+
def Commit(self, request, context):
|
|
271
|
+
"""Commit pending changes"""
|
|
272
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
273
|
+
context.set_details("Method not implemented!")
|
|
274
|
+
raise NotImplementedError("Method not implemented!")
|
|
275
|
+
|
|
276
|
+
def ForceMerge(self, request, context):
|
|
277
|
+
"""Force merge segments"""
|
|
278
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
279
|
+
context.set_details("Method not implemented!")
|
|
280
|
+
raise NotImplementedError("Method not implemented!")
|
|
281
|
+
|
|
282
|
+
def DeleteIndex(self, request, context):
|
|
283
|
+
"""Delete an index"""
|
|
284
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
285
|
+
context.set_details("Method not implemented!")
|
|
286
|
+
raise NotImplementedError("Method not implemented!")
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def add_IndexServiceServicer_to_server(servicer, server):
|
|
290
|
+
rpc_method_handlers = {
|
|
291
|
+
"CreateIndex": grpc.unary_unary_rpc_method_handler(
|
|
292
|
+
servicer.CreateIndex,
|
|
293
|
+
request_deserializer=hermes__pb2.CreateIndexRequest.FromString,
|
|
294
|
+
response_serializer=hermes__pb2.CreateIndexResponse.SerializeToString,
|
|
295
|
+
),
|
|
296
|
+
"IndexDocuments": grpc.stream_unary_rpc_method_handler(
|
|
297
|
+
servicer.IndexDocuments,
|
|
298
|
+
request_deserializer=hermes__pb2.IndexDocumentRequest.FromString,
|
|
299
|
+
response_serializer=hermes__pb2.IndexDocumentsResponse.SerializeToString,
|
|
300
|
+
),
|
|
301
|
+
"BatchIndexDocuments": grpc.unary_unary_rpc_method_handler(
|
|
302
|
+
servicer.BatchIndexDocuments,
|
|
303
|
+
request_deserializer=hermes__pb2.BatchIndexDocumentsRequest.FromString,
|
|
304
|
+
response_serializer=hermes__pb2.BatchIndexDocumentsResponse.SerializeToString,
|
|
305
|
+
),
|
|
306
|
+
"Commit": grpc.unary_unary_rpc_method_handler(
|
|
307
|
+
servicer.Commit,
|
|
308
|
+
request_deserializer=hermes__pb2.CommitRequest.FromString,
|
|
309
|
+
response_serializer=hermes__pb2.CommitResponse.SerializeToString,
|
|
310
|
+
),
|
|
311
|
+
"ForceMerge": grpc.unary_unary_rpc_method_handler(
|
|
312
|
+
servicer.ForceMerge,
|
|
313
|
+
request_deserializer=hermes__pb2.ForceMergeRequest.FromString,
|
|
314
|
+
response_serializer=hermes__pb2.ForceMergeResponse.SerializeToString,
|
|
315
|
+
),
|
|
316
|
+
"DeleteIndex": grpc.unary_unary_rpc_method_handler(
|
|
317
|
+
servicer.DeleteIndex,
|
|
318
|
+
request_deserializer=hermes__pb2.DeleteIndexRequest.FromString,
|
|
319
|
+
response_serializer=hermes__pb2.DeleteIndexResponse.SerializeToString,
|
|
320
|
+
),
|
|
321
|
+
}
|
|
322
|
+
generic_handler = grpc.method_handlers_generic_handler(
|
|
323
|
+
"hermes.IndexService", rpc_method_handlers
|
|
324
|
+
)
|
|
325
|
+
server.add_generic_rpc_handlers((generic_handler,))
|
|
326
|
+
server.add_registered_method_handlers("hermes.IndexService", rpc_method_handlers)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
# This class is part of an EXPERIMENTAL API.
|
|
330
|
+
class IndexService:
|
|
331
|
+
"""Index service"""
|
|
332
|
+
|
|
333
|
+
@staticmethod
|
|
334
|
+
def CreateIndex(
|
|
335
|
+
request,
|
|
336
|
+
target,
|
|
337
|
+
options=(),
|
|
338
|
+
channel_credentials=None,
|
|
339
|
+
call_credentials=None,
|
|
340
|
+
insecure=False,
|
|
341
|
+
compression=None,
|
|
342
|
+
wait_for_ready=None,
|
|
343
|
+
timeout=None,
|
|
344
|
+
metadata=None,
|
|
345
|
+
):
|
|
346
|
+
return grpc.experimental.unary_unary(
|
|
347
|
+
request,
|
|
348
|
+
target,
|
|
349
|
+
"/hermes.IndexService/CreateIndex",
|
|
350
|
+
hermes__pb2.CreateIndexRequest.SerializeToString,
|
|
351
|
+
hermes__pb2.CreateIndexResponse.FromString,
|
|
352
|
+
options,
|
|
353
|
+
channel_credentials,
|
|
354
|
+
insecure,
|
|
355
|
+
call_credentials,
|
|
356
|
+
compression,
|
|
357
|
+
wait_for_ready,
|
|
358
|
+
timeout,
|
|
359
|
+
metadata,
|
|
360
|
+
_registered_method=True,
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
@staticmethod
|
|
364
|
+
def IndexDocuments(
|
|
365
|
+
request_iterator,
|
|
366
|
+
target,
|
|
367
|
+
options=(),
|
|
368
|
+
channel_credentials=None,
|
|
369
|
+
call_credentials=None,
|
|
370
|
+
insecure=False,
|
|
371
|
+
compression=None,
|
|
372
|
+
wait_for_ready=None,
|
|
373
|
+
timeout=None,
|
|
374
|
+
metadata=None,
|
|
375
|
+
):
|
|
376
|
+
return grpc.experimental.stream_unary(
|
|
377
|
+
request_iterator,
|
|
378
|
+
target,
|
|
379
|
+
"/hermes.IndexService/IndexDocuments",
|
|
380
|
+
hermes__pb2.IndexDocumentRequest.SerializeToString,
|
|
381
|
+
hermes__pb2.IndexDocumentsResponse.FromString,
|
|
382
|
+
options,
|
|
383
|
+
channel_credentials,
|
|
384
|
+
insecure,
|
|
385
|
+
call_credentials,
|
|
386
|
+
compression,
|
|
387
|
+
wait_for_ready,
|
|
388
|
+
timeout,
|
|
389
|
+
metadata,
|
|
390
|
+
_registered_method=True,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
@staticmethod
|
|
394
|
+
def BatchIndexDocuments(
|
|
395
|
+
request,
|
|
396
|
+
target,
|
|
397
|
+
options=(),
|
|
398
|
+
channel_credentials=None,
|
|
399
|
+
call_credentials=None,
|
|
400
|
+
insecure=False,
|
|
401
|
+
compression=None,
|
|
402
|
+
wait_for_ready=None,
|
|
403
|
+
timeout=None,
|
|
404
|
+
metadata=None,
|
|
405
|
+
):
|
|
406
|
+
return grpc.experimental.unary_unary(
|
|
407
|
+
request,
|
|
408
|
+
target,
|
|
409
|
+
"/hermes.IndexService/BatchIndexDocuments",
|
|
410
|
+
hermes__pb2.BatchIndexDocumentsRequest.SerializeToString,
|
|
411
|
+
hermes__pb2.BatchIndexDocumentsResponse.FromString,
|
|
412
|
+
options,
|
|
413
|
+
channel_credentials,
|
|
414
|
+
insecure,
|
|
415
|
+
call_credentials,
|
|
416
|
+
compression,
|
|
417
|
+
wait_for_ready,
|
|
418
|
+
timeout,
|
|
419
|
+
metadata,
|
|
420
|
+
_registered_method=True,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
@staticmethod
|
|
424
|
+
def Commit(
|
|
425
|
+
request,
|
|
426
|
+
target,
|
|
427
|
+
options=(),
|
|
428
|
+
channel_credentials=None,
|
|
429
|
+
call_credentials=None,
|
|
430
|
+
insecure=False,
|
|
431
|
+
compression=None,
|
|
432
|
+
wait_for_ready=None,
|
|
433
|
+
timeout=None,
|
|
434
|
+
metadata=None,
|
|
435
|
+
):
|
|
436
|
+
return grpc.experimental.unary_unary(
|
|
437
|
+
request,
|
|
438
|
+
target,
|
|
439
|
+
"/hermes.IndexService/Commit",
|
|
440
|
+
hermes__pb2.CommitRequest.SerializeToString,
|
|
441
|
+
hermes__pb2.CommitResponse.FromString,
|
|
442
|
+
options,
|
|
443
|
+
channel_credentials,
|
|
444
|
+
insecure,
|
|
445
|
+
call_credentials,
|
|
446
|
+
compression,
|
|
447
|
+
wait_for_ready,
|
|
448
|
+
timeout,
|
|
449
|
+
metadata,
|
|
450
|
+
_registered_method=True,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
@staticmethod
|
|
454
|
+
def ForceMerge(
|
|
455
|
+
request,
|
|
456
|
+
target,
|
|
457
|
+
options=(),
|
|
458
|
+
channel_credentials=None,
|
|
459
|
+
call_credentials=None,
|
|
460
|
+
insecure=False,
|
|
461
|
+
compression=None,
|
|
462
|
+
wait_for_ready=None,
|
|
463
|
+
timeout=None,
|
|
464
|
+
metadata=None,
|
|
465
|
+
):
|
|
466
|
+
return grpc.experimental.unary_unary(
|
|
467
|
+
request,
|
|
468
|
+
target,
|
|
469
|
+
"/hermes.IndexService/ForceMerge",
|
|
470
|
+
hermes__pb2.ForceMergeRequest.SerializeToString,
|
|
471
|
+
hermes__pb2.ForceMergeResponse.FromString,
|
|
472
|
+
options,
|
|
473
|
+
channel_credentials,
|
|
474
|
+
insecure,
|
|
475
|
+
call_credentials,
|
|
476
|
+
compression,
|
|
477
|
+
wait_for_ready,
|
|
478
|
+
timeout,
|
|
479
|
+
metadata,
|
|
480
|
+
_registered_method=True,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
@staticmethod
|
|
484
|
+
def DeleteIndex(
|
|
485
|
+
request,
|
|
486
|
+
target,
|
|
487
|
+
options=(),
|
|
488
|
+
channel_credentials=None,
|
|
489
|
+
call_credentials=None,
|
|
490
|
+
insecure=False,
|
|
491
|
+
compression=None,
|
|
492
|
+
wait_for_ready=None,
|
|
493
|
+
timeout=None,
|
|
494
|
+
metadata=None,
|
|
495
|
+
):
|
|
496
|
+
return grpc.experimental.unary_unary(
|
|
497
|
+
request,
|
|
498
|
+
target,
|
|
499
|
+
"/hermes.IndexService/DeleteIndex",
|
|
500
|
+
hermes__pb2.DeleteIndexRequest.SerializeToString,
|
|
501
|
+
hermes__pb2.DeleteIndexResponse.FromString,
|
|
502
|
+
options,
|
|
503
|
+
channel_credentials,
|
|
504
|
+
insecure,
|
|
505
|
+
call_credentials,
|
|
506
|
+
compression,
|
|
507
|
+
wait_for_ready,
|
|
508
|
+
timeout,
|
|
509
|
+
metadata,
|
|
510
|
+
_registered_method=True,
|
|
511
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Type definitions for Hermes client."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Document:
|
|
9
|
+
"""A document with field values."""
|
|
10
|
+
|
|
11
|
+
fields: dict[str, Any] = field(default_factory=dict)
|
|
12
|
+
|
|
13
|
+
def __getitem__(self, key: str) -> Any:
|
|
14
|
+
return self.fields[key]
|
|
15
|
+
|
|
16
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
17
|
+
self.fields[key] = value
|
|
18
|
+
|
|
19
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
20
|
+
return self.fields.get(key, default)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class SearchHit:
|
|
25
|
+
"""A single search result."""
|
|
26
|
+
|
|
27
|
+
doc_id: int
|
|
28
|
+
score: float
|
|
29
|
+
fields: dict[str, Any] = field(default_factory=dict)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class SearchResponse:
|
|
34
|
+
"""Search response with hits and metadata."""
|
|
35
|
+
|
|
36
|
+
hits: list[SearchHit]
|
|
37
|
+
total_hits: int
|
|
38
|
+
took_ms: int
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class IndexInfo:
|
|
43
|
+
"""Information about an index."""
|
|
44
|
+
|
|
45
|
+
index_name: str
|
|
46
|
+
num_docs: int
|
|
47
|
+
num_segments: int
|
|
48
|
+
schema: str
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hermes-client-python
|
|
3
|
+
Version: 1.0.4
|
|
4
|
+
Summary: Async Python client for Hermes search server
|
|
5
|
+
Project-URL: Homepage, https://github.com/SpaceFrontiers/hermes
|
|
6
|
+
Project-URL: Repository, https://github.com/SpaceFrontiers/hermes
|
|
7
|
+
Author: izihawa
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: async,full-text-search,grpc,search
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Database :: Database Engines/Servers
|
|
19
|
+
Classifier: Topic :: Text Processing :: Indexing
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: grpcio>=1.76.0
|
|
22
|
+
Requires-Dist: protobuf>=6.33.4
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Hermes Client
|
|
26
|
+
|
|
27
|
+
Async Python client for [Hermes](https://github.com/SpaceFrontiers/hermes) search server.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install hermes-client-python
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import asyncio
|
|
39
|
+
from hermes_client_python import HermesClient
|
|
40
|
+
|
|
41
|
+
async def main():
|
|
42
|
+
async with HermesClient("localhost:50051") as client:
|
|
43
|
+
# Create index with SDL schema
|
|
44
|
+
await client.create_index("articles", '''
|
|
45
|
+
index articles {
|
|
46
|
+
title: text indexed stored
|
|
47
|
+
body: text indexed stored
|
|
48
|
+
score: f64 stored
|
|
49
|
+
}
|
|
50
|
+
''')
|
|
51
|
+
|
|
52
|
+
# Index documents
|
|
53
|
+
await client.index_documents("articles", [
|
|
54
|
+
{"title": "Hello World", "body": "First article", "score": 1.5},
|
|
55
|
+
{"title": "Goodbye World", "body": "Last article", "score": 2.0},
|
|
56
|
+
])
|
|
57
|
+
|
|
58
|
+
# Commit changes
|
|
59
|
+
await client.commit("articles")
|
|
60
|
+
|
|
61
|
+
# Search
|
|
62
|
+
results = await client.search("articles", term=("title", "hello"), limit=10)
|
|
63
|
+
for hit in results.hits:
|
|
64
|
+
print(f"Doc {hit.doc_id}: score={hit.score}, fields={hit.fields}")
|
|
65
|
+
|
|
66
|
+
# Get document by ID
|
|
67
|
+
doc = await client.get_document("articles", 0)
|
|
68
|
+
print(doc.fields)
|
|
69
|
+
|
|
70
|
+
# Delete index
|
|
71
|
+
await client.delete_index("articles")
|
|
72
|
+
|
|
73
|
+
asyncio.run(main())
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## API Reference
|
|
77
|
+
|
|
78
|
+
### HermesClient
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
client = HermesClient(address="localhost:50051")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### Connection
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
# Using context manager (recommended)
|
|
88
|
+
async with HermesClient("localhost:50051") as client:
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
# Manual connection
|
|
92
|
+
client = HermesClient("localhost:50051")
|
|
93
|
+
await client.connect()
|
|
94
|
+
# ... use client ...
|
|
95
|
+
await client.close()
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### Index Management
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
# Create index with SDL schema
|
|
102
|
+
await client.create_index("myindex", '''
|
|
103
|
+
index myindex {
|
|
104
|
+
title: text indexed stored
|
|
105
|
+
body: text indexed stored
|
|
106
|
+
}
|
|
107
|
+
''')
|
|
108
|
+
|
|
109
|
+
# Create index with JSON schema
|
|
110
|
+
await client.create_index("myindex", '''
|
|
111
|
+
{
|
|
112
|
+
"fields": [
|
|
113
|
+
{"name": "title", "type": "text", "indexed": true, "stored": true},
|
|
114
|
+
{"name": "body", "type": "text", "indexed": true, "stored": true}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
''')
|
|
118
|
+
|
|
119
|
+
# Get index info
|
|
120
|
+
info = await client.get_index_info("myindex")
|
|
121
|
+
print(f"Documents: {info.num_docs}, Segments: {info.num_segments}")
|
|
122
|
+
|
|
123
|
+
# Delete index
|
|
124
|
+
await client.delete_index("myindex")
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### Document Indexing
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
# Index multiple documents (batch)
|
|
131
|
+
indexed, errors = await client.index_documents("myindex", [
|
|
132
|
+
{"title": "Doc 1", "body": "Content 1"},
|
|
133
|
+
{"title": "Doc 2", "body": "Content 2"},
|
|
134
|
+
])
|
|
135
|
+
|
|
136
|
+
# Index single document
|
|
137
|
+
await client.index_document("myindex", {"title": "Doc", "body": "Content"})
|
|
138
|
+
|
|
139
|
+
# Stream documents (for large datasets)
|
|
140
|
+
async def doc_generator():
|
|
141
|
+
for i in range(10000):
|
|
142
|
+
yield {"title": f"Doc {i}", "body": f"Content {i}"}
|
|
143
|
+
|
|
144
|
+
count = await client.index_documents_stream("myindex", doc_generator())
|
|
145
|
+
|
|
146
|
+
# Commit changes (required to make documents searchable)
|
|
147
|
+
num_docs = await client.commit("myindex")
|
|
148
|
+
|
|
149
|
+
# Force merge segments (for optimization)
|
|
150
|
+
num_segments = await client.force_merge("myindex")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### Searching
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
# Term query
|
|
157
|
+
results = await client.search("myindex", term=("title", "hello"), limit=10)
|
|
158
|
+
|
|
159
|
+
# Boolean query
|
|
160
|
+
results = await client.search("myindex", boolean={
|
|
161
|
+
"must": [("title", "hello")],
|
|
162
|
+
"should": [("body", "world")],
|
|
163
|
+
"must_not": [("title", "spam")],
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
# With pagination
|
|
167
|
+
results = await client.search("myindex", term=("title", "hello"), limit=10, offset=20)
|
|
168
|
+
|
|
169
|
+
# With field loading
|
|
170
|
+
results = await client.search(
|
|
171
|
+
"myindex",
|
|
172
|
+
term=("title", "hello"),
|
|
173
|
+
fields_to_load=["title", "body"]
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Access results
|
|
177
|
+
for hit in results.hits:
|
|
178
|
+
print(f"Doc {hit.doc_id}: {hit.score}")
|
|
179
|
+
print(f" Title: {hit.fields.get('title')}")
|
|
180
|
+
|
|
181
|
+
print(f"Total hits: {results.total_hits}")
|
|
182
|
+
print(f"Took: {results.took_ms}ms")
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
#### Document Retrieval
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
# Get document by ID
|
|
189
|
+
doc = await client.get_document("myindex", doc_id=42)
|
|
190
|
+
if doc:
|
|
191
|
+
print(doc.fields["title"])
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Field Types
|
|
195
|
+
|
|
196
|
+
| Type | Python Type | Description |
|
|
197
|
+
| --------------- | --------------- | ------------------------------------- |
|
|
198
|
+
| `text` | `str` | Full-text searchable string |
|
|
199
|
+
| `u64` | `int` (>= 0) | Unsigned 64-bit integer |
|
|
200
|
+
| `i64` | `int` | Signed 64-bit integer |
|
|
201
|
+
| `f64` | `float` | 64-bit floating point |
|
|
202
|
+
| `bytes` | `bytes` | Binary data |
|
|
203
|
+
| `json` | `dict` / `list` | JSON object (auto-serialized) |
|
|
204
|
+
| `dense_vector` | `list[float]` | Dense vector for semantic search |
|
|
205
|
+
| `sparse_vector` | `dict` | Sparse vector with indices and values |
|
|
206
|
+
|
|
207
|
+
## Error Handling
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
import grpc
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
await client.search("nonexistent", term=("field", "value"))
|
|
214
|
+
except grpc.RpcError as e:
|
|
215
|
+
if e.code() == grpc.StatusCode.NOT_FOUND:
|
|
216
|
+
print("Index not found")
|
|
217
|
+
else:
|
|
218
|
+
raise
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Development
|
|
222
|
+
|
|
223
|
+
Generate protobuf stubs:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
pip install grpcio-tools
|
|
227
|
+
python generate_proto.py
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
MIT
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
hermes_client_python/__init__.py,sha256=qUaFnTSVr4fSOXfRHJIu8ur5IJEChfIJPOqTC0MiIMw,282
|
|
2
|
+
hermes_client_python/client.py,sha256=E12sJL7d7pih2kamiQXarvmdRESqN5WgKVVek1HF6xQ,12850
|
|
3
|
+
hermes_client_python/hermes_pb2.py,sha256=6mPuThsTsivqjEBtT7Zi4G3z4X3x3RbIzTkJex_bYdo,10672
|
|
4
|
+
hermes_client_python/hermes_pb2_grpc.py,sha256=Oehwc9-0IlluummU8rJJ-ftbEK9AK2ZOBJyj7MnymkQ,16863
|
|
5
|
+
hermes_client_python/types.py,sha256=e6_amiC4F9AkOSzAB9woAfLf9gooN9Y-rzJShrR9YEo,954
|
|
6
|
+
hermes_client_python-1.0.4.dist-info/METADATA,sha256=KLT944lRtT_grd1Yj1gVqfAMwJ6iOAafwgK1YxO_dnQ,6120
|
|
7
|
+
hermes_client_python-1.0.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
8
|
+
hermes_client_python-1.0.4.dist-info/RECORD,,
|