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.
- {hermes_client_python-1.8.95 → hermes_client_python-1.8.97}/.gitignore +1 -1
- hermes_client_python-1.8.97/PKG-INFO +279 -0
- hermes_client_python-1.8.97/README.md +255 -0
- {hermes_client_python-1.8.95 → hermes_client_python-1.8.97}/pyproject.toml +2 -1
- {hermes_client_python-1.8.95 → hermes_client_python-1.8.97}/src/hermes_client_python/__init__.py +7 -1
- {hermes_client_python-1.8.95 → hermes_client_python-1.8.97}/src/hermes_client_python/client.py +40 -42
- hermes_client_python-1.8.97/src/hermes_client_python/hermes_pb2.py +152 -0
- hermes_client_python-1.8.97/src/hermes_client_python/hermes_pb2_grpc.py +652 -0
- {hermes_client_python-1.8.95 → hermes_client_python-1.8.97}/src/hermes_client_python/types.py +3 -2
- hermes_client_python-1.8.95/PKG-INFO +0 -264
- hermes_client_python-1.8.95/README.md +0 -240
- hermes_client_python-1.8.95/src/hermes_client_python/hermes_pb2.py +0 -156
- hermes_client_python-1.8.95/src/hermes_client_python/hermes_pb2_grpc.py +0 -617
|
@@ -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.
|
|
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 = [
|
{hermes_client_python-1.8.95 → hermes_client_python-1.8.97}/src/hermes_client_python/__init__.py
RENAMED
|
@@ -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
|
-
|
|
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"
|