hermes-client-python 1.8.95__tar.gz → 1.8.97__tar.gz

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.
@@ -21,7 +21,7 @@ Thumbs.db
21
21
  # Node.js
22
22
  node_modules/
23
23
  hermes-web/dist/
24
- hermes-web/dist-model-lab/
24
+ hermes-model-lab/dist/
25
25
 
26
26
  # Python
27
27
  __pycache__/
@@ -0,0 +1,279 @@
1
+ Metadata-Version: 2.4
2
+ Name: hermes-client-python
3
+ Version: 1.8.97
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 Python client
26
+
27
+ Async Python client for the
28
+ [Hermes](https://github.com/SpaceFrontiers/hermes) gRPC search server.
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ pip install hermes-client-python
34
+ ```
35
+
36
+ Python 3.10 or newer is required.
37
+
38
+ ## Quick start
39
+
40
+ ```python
41
+ import asyncio
42
+
43
+ from hermes_client_python import HermesClient
44
+
45
+
46
+ async def main():
47
+ async with HermesClient("localhost:50051") as client:
48
+ await client.create_index(
49
+ "articles",
50
+ """
51
+ index articles {
52
+ field title: text<simple> [indexed, stored]
53
+ field body: text<simple> [indexed, stored]
54
+ }
55
+ """,
56
+ )
57
+
58
+ indexed, error_count, errors = await client.index_documents(
59
+ "articles",
60
+ [
61
+ {"title": "Hello World", "body": "First article"},
62
+ {"title": "Hermes Search", "body": "Fast retrieval"},
63
+ ],
64
+ )
65
+ if error_count:
66
+ raise RuntimeError(errors)
67
+ print(f"Indexed {indexed} documents")
68
+
69
+ await client.commit("articles")
70
+
71
+ results = await client.search(
72
+ "articles",
73
+ query={"match": {"field": "title", "text": "hello"}},
74
+ fields_to_load=["title", "body"],
75
+ )
76
+ for hit in results.hits:
77
+ print(hit.address, hit.score, hit.fields)
78
+
79
+ if results.hits:
80
+ document = await client.get_document("articles", results.hits[0].address)
81
+ print(document.fields if document else "document not found")
82
+
83
+ await client.delete_index("articles")
84
+
85
+
86
+ asyncio.run(main())
87
+ ```
88
+
89
+ The context manager calls `connect()` and `close()` automatically. For manual
90
+ lifecycle management:
91
+
92
+ ```python
93
+ client = HermesClient("localhost:50051")
94
+ await client.connect()
95
+ try:
96
+ ...
97
+ finally:
98
+ await client.close()
99
+ ```
100
+
101
+ ## Index management
102
+
103
+ ```python
104
+ await client.create_index("articles", schema_sdl)
105
+ names = await client.list_indexes()
106
+ info = await client.get_index_info("articles")
107
+ print(info.num_docs, info.num_segments, info.vector_stats)
108
+
109
+ await client.force_merge("articles")
110
+ await client.reorder("articles")
111
+ await client.retrain_vector_index("articles")
112
+ await client.delete_index("articles")
113
+ ```
114
+
115
+ `commit()` is required before newly indexed documents become searchable.
116
+
117
+ ### Batch and streaming indexing
118
+
119
+ ```python
120
+ indexed, error_count, errors = await client.index_documents(
121
+ "articles",
122
+ [
123
+ {"title": "One", "tags": ["search", "rust"]},
124
+ {"title": "Two", "tags": ["python"]},
125
+ ],
126
+ )
127
+
128
+
129
+ async def documents():
130
+ for number in range(10_000):
131
+ yield {"title": f"Document {number}"}
132
+
133
+
134
+ streamed, stream_errors = await client.index_documents_stream("articles", documents())
135
+ ```
136
+
137
+ Repeated list values become repeated field entries. Flat numeric lists are
138
+ dense vectors; lists of `(dimension, weight)` pairs are sparse vectors.
139
+
140
+ ## Searching
141
+
142
+ Every search takes one `query` object whose single key matches a Hermes query
143
+ variant:
144
+
145
+ ```python
146
+ # Exact term
147
+ await client.search(
148
+ "articles",
149
+ query={"term": {"field": "title", "term": "hermes"}},
150
+ )
151
+
152
+ # Tokenized full-text match
153
+ await client.search(
154
+ "articles",
155
+ query={"match": {"field": "body", "text": "fast retrieval"}},
156
+ )
157
+
158
+ # Recursive boolean query
159
+ await client.search(
160
+ "articles",
161
+ query={
162
+ "boolean": {
163
+ "must": [{"match": {"field": "body", "text": "retrieval"}}],
164
+ "must_not": [{"term": {"field": "title", "term": "draft"}}],
165
+ }
166
+ },
167
+ )
168
+
169
+ # Dense vector query and optional reranking
170
+ await client.search(
171
+ "articles",
172
+ query={
173
+ "dense_vector": {
174
+ "field": "embedding",
175
+ "vector": [0.1, 0.2, 0.3],
176
+ "nprobe": 16,
177
+ }
178
+ },
179
+ reranker={"field": "embedding", "vector": [0.1, 0.2, 0.3]},
180
+ candidate_limit=20,
181
+ limit=10,
182
+ fields_to_load=["title"],
183
+ )
184
+
185
+ # Hybrid union fusion
186
+ await client.search(
187
+ "articles",
188
+ query={
189
+ "fusion": {
190
+ "method": "rrf",
191
+ "rrf_k": 60,
192
+ "queries": [
193
+ {
194
+ "query": {
195
+ "sparse_vector": {
196
+ "field": "sparse_embedding",
197
+ "indices": [1, 5],
198
+ "values": [0.8, 0.2],
199
+ }
200
+ },
201
+ "weight": 1.0,
202
+ },
203
+ {
204
+ "query": {
205
+ "dense_vector": {
206
+ "field": "embedding",
207
+ "vector": [0.1, 0.2, 0.3],
208
+ }
209
+ },
210
+ "weight": 1.0,
211
+ },
212
+ ],
213
+ }
214
+ },
215
+ )
216
+ ```
217
+
218
+ Other supported variants are `binary_dense_vector`, `boost`, `range`,
219
+ `prefix`, and `all`. Search results expose the full `DocAddress` needed by
220
+ `get_document()`:
221
+
222
+ ```python
223
+ hit = results.hits[0]
224
+ document = await client.get_document("articles", hit.address)
225
+ ```
226
+
227
+ ## Deadlines and errors
228
+
229
+ Every RPC accepts an optional `timeout` in seconds. A per-call value overrides
230
+ the client default:
231
+
232
+ ```python
233
+ client = HermesClient("localhost:50051", default_timeout=5.0)
234
+ results = await client.search(
235
+ "articles",
236
+ query={"all": {}},
237
+ timeout=0.5,
238
+ )
239
+ await client.force_merge("articles", timeout=3600)
240
+ ```
241
+
242
+ gRPC failures raise `grpc.RpcError` (normally
243
+ `grpc.aio.AioRpcError`). `get_document()` is the exception: it returns `None`
244
+ for `NOT_FOUND`.
245
+
246
+ ```python
247
+ import grpc
248
+
249
+ try:
250
+ await client.search("missing", query={"all": {}})
251
+ except grpc.RpcError as error:
252
+ if error.code() == grpc.StatusCode.NOT_FOUND:
253
+ print("index not found")
254
+ else:
255
+ raise
256
+ ```
257
+
258
+ ## Development
259
+
260
+ From `hermes-client-python`:
261
+
262
+ ```bash
263
+ uv sync --group dev --group test
264
+ uv run ruff check .
265
+ uv run ruff format --check .
266
+ uv run pytest tests/test_client_unit.py
267
+ ```
268
+
269
+ The remaining tests are integration tests and expect a debug
270
+ `target/debug/hermes-server` binary. Regenerate checked-in protobuf stubs after
271
+ changing `hermes-proto/hermes.proto`:
272
+
273
+ ```bash
274
+ uv run --group dev python generate_proto.py
275
+ ```
276
+
277
+ ## License
278
+
279
+ MIT
@@ -0,0 +1,255 @@
1
+ # Hermes Python client
2
+
3
+ Async Python client for the
4
+ [Hermes](https://github.com/SpaceFrontiers/hermes) gRPC search server.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ pip install hermes-client-python
10
+ ```
11
+
12
+ Python 3.10 or newer is required.
13
+
14
+ ## Quick start
15
+
16
+ ```python
17
+ import asyncio
18
+
19
+ from hermes_client_python import HermesClient
20
+
21
+
22
+ async def main():
23
+ async with HermesClient("localhost:50051") as client:
24
+ await client.create_index(
25
+ "articles",
26
+ """
27
+ index articles {
28
+ field title: text<simple> [indexed, stored]
29
+ field body: text<simple> [indexed, stored]
30
+ }
31
+ """,
32
+ )
33
+
34
+ indexed, error_count, errors = await client.index_documents(
35
+ "articles",
36
+ [
37
+ {"title": "Hello World", "body": "First article"},
38
+ {"title": "Hermes Search", "body": "Fast retrieval"},
39
+ ],
40
+ )
41
+ if error_count:
42
+ raise RuntimeError(errors)
43
+ print(f"Indexed {indexed} documents")
44
+
45
+ await client.commit("articles")
46
+
47
+ results = await client.search(
48
+ "articles",
49
+ query={"match": {"field": "title", "text": "hello"}},
50
+ fields_to_load=["title", "body"],
51
+ )
52
+ for hit in results.hits:
53
+ print(hit.address, hit.score, hit.fields)
54
+
55
+ if results.hits:
56
+ document = await client.get_document("articles", results.hits[0].address)
57
+ print(document.fields if document else "document not found")
58
+
59
+ await client.delete_index("articles")
60
+
61
+
62
+ asyncio.run(main())
63
+ ```
64
+
65
+ The context manager calls `connect()` and `close()` automatically. For manual
66
+ lifecycle management:
67
+
68
+ ```python
69
+ client = HermesClient("localhost:50051")
70
+ await client.connect()
71
+ try:
72
+ ...
73
+ finally:
74
+ await client.close()
75
+ ```
76
+
77
+ ## Index management
78
+
79
+ ```python
80
+ await client.create_index("articles", schema_sdl)
81
+ names = await client.list_indexes()
82
+ info = await client.get_index_info("articles")
83
+ print(info.num_docs, info.num_segments, info.vector_stats)
84
+
85
+ await client.force_merge("articles")
86
+ await client.reorder("articles")
87
+ await client.retrain_vector_index("articles")
88
+ await client.delete_index("articles")
89
+ ```
90
+
91
+ `commit()` is required before newly indexed documents become searchable.
92
+
93
+ ### Batch and streaming indexing
94
+
95
+ ```python
96
+ indexed, error_count, errors = await client.index_documents(
97
+ "articles",
98
+ [
99
+ {"title": "One", "tags": ["search", "rust"]},
100
+ {"title": "Two", "tags": ["python"]},
101
+ ],
102
+ )
103
+
104
+
105
+ async def documents():
106
+ for number in range(10_000):
107
+ yield {"title": f"Document {number}"}
108
+
109
+
110
+ streamed, stream_errors = await client.index_documents_stream("articles", documents())
111
+ ```
112
+
113
+ Repeated list values become repeated field entries. Flat numeric lists are
114
+ dense vectors; lists of `(dimension, weight)` pairs are sparse vectors.
115
+
116
+ ## Searching
117
+
118
+ Every search takes one `query` object whose single key matches a Hermes query
119
+ variant:
120
+
121
+ ```python
122
+ # Exact term
123
+ await client.search(
124
+ "articles",
125
+ query={"term": {"field": "title", "term": "hermes"}},
126
+ )
127
+
128
+ # Tokenized full-text match
129
+ await client.search(
130
+ "articles",
131
+ query={"match": {"field": "body", "text": "fast retrieval"}},
132
+ )
133
+
134
+ # Recursive boolean query
135
+ await client.search(
136
+ "articles",
137
+ query={
138
+ "boolean": {
139
+ "must": [{"match": {"field": "body", "text": "retrieval"}}],
140
+ "must_not": [{"term": {"field": "title", "term": "draft"}}],
141
+ }
142
+ },
143
+ )
144
+
145
+ # Dense vector query and optional reranking
146
+ await client.search(
147
+ "articles",
148
+ query={
149
+ "dense_vector": {
150
+ "field": "embedding",
151
+ "vector": [0.1, 0.2, 0.3],
152
+ "nprobe": 16,
153
+ }
154
+ },
155
+ reranker={"field": "embedding", "vector": [0.1, 0.2, 0.3]},
156
+ candidate_limit=20,
157
+ limit=10,
158
+ fields_to_load=["title"],
159
+ )
160
+
161
+ # Hybrid union fusion
162
+ await client.search(
163
+ "articles",
164
+ query={
165
+ "fusion": {
166
+ "method": "rrf",
167
+ "rrf_k": 60,
168
+ "queries": [
169
+ {
170
+ "query": {
171
+ "sparse_vector": {
172
+ "field": "sparse_embedding",
173
+ "indices": [1, 5],
174
+ "values": [0.8, 0.2],
175
+ }
176
+ },
177
+ "weight": 1.0,
178
+ },
179
+ {
180
+ "query": {
181
+ "dense_vector": {
182
+ "field": "embedding",
183
+ "vector": [0.1, 0.2, 0.3],
184
+ }
185
+ },
186
+ "weight": 1.0,
187
+ },
188
+ ],
189
+ }
190
+ },
191
+ )
192
+ ```
193
+
194
+ Other supported variants are `binary_dense_vector`, `boost`, `range`,
195
+ `prefix`, and `all`. Search results expose the full `DocAddress` needed by
196
+ `get_document()`:
197
+
198
+ ```python
199
+ hit = results.hits[0]
200
+ document = await client.get_document("articles", hit.address)
201
+ ```
202
+
203
+ ## Deadlines and errors
204
+
205
+ Every RPC accepts an optional `timeout` in seconds. A per-call value overrides
206
+ the client default:
207
+
208
+ ```python
209
+ client = HermesClient("localhost:50051", default_timeout=5.0)
210
+ results = await client.search(
211
+ "articles",
212
+ query={"all": {}},
213
+ timeout=0.5,
214
+ )
215
+ await client.force_merge("articles", timeout=3600)
216
+ ```
217
+
218
+ gRPC failures raise `grpc.RpcError` (normally
219
+ `grpc.aio.AioRpcError`). `get_document()` is the exception: it returns `None`
220
+ for `NOT_FOUND`.
221
+
222
+ ```python
223
+ import grpc
224
+
225
+ try:
226
+ await client.search("missing", query={"all": {}})
227
+ except grpc.RpcError as error:
228
+ if error.code() == grpc.StatusCode.NOT_FOUND:
229
+ print("index not found")
230
+ else:
231
+ raise
232
+ ```
233
+
234
+ ## Development
235
+
236
+ From `hermes-client-python`:
237
+
238
+ ```bash
239
+ uv sync --group dev --group test
240
+ uv run ruff check .
241
+ uv run ruff format --check .
242
+ uv run pytest tests/test_client_unit.py
243
+ ```
244
+
245
+ The remaining tests are integration tests and expect a debug
246
+ `target/debug/hermes-server` binary. Regenerate checked-in protobuf stubs after
247
+ changing `hermes-proto/hermes.proto`:
248
+
249
+ ```bash
250
+ uv run --group dev python generate_proto.py
251
+ ```
252
+
253
+ ## License
254
+
255
+ MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hermes-client-python"
7
- version = "1.8.95"
7
+ version = "1.8.97"
8
8
  description = "Async Python client for Hermes search server"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -37,6 +37,7 @@ Repository = "https://github.com/SpaceFrontiers/hermes"
37
37
  [dependency-groups]
38
38
  dev = [
39
39
  "grpcio-tools>=1.76.0",
40
+ "ruff==0.16.0",
40
41
  ]
41
42
 
42
43
  test = [
@@ -1,5 +1,7 @@
1
1
  """Async Python client for Hermes search server."""
2
2
 
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
3
5
  from .client import HermesClient
4
6
  from .types import (
5
7
  AllQuery,
@@ -46,4 +48,8 @@ __all__ = [
46
48
  "VectorFieldStats",
47
49
  ]
48
50
 
49
- __version__ = "1.0.2"
51
+ try:
52
+ __version__ = version("hermes-client-python")
53
+ except PackageNotFoundError:
54
+ # Source-only imports (without an installed wheel/editable distribution).
55
+ __version__ = "0.0.0+unknown"