summa-client-python 2.0.0__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.
@@ -0,0 +1,55 @@
1
+ # Build artifacts
2
+ target/
3
+ /dist/
4
+
5
+ # Local agent scratch and reproducible review/benchmark evidence
6
+ /.context/
7
+
8
+ # Data files (generated indexes, processed data)
9
+ /data/
10
+ *.zst
11
+ dump.*
12
+
13
+ # IDE
14
+ .idea/
15
+ .vscode/
16
+ *.swp
17
+ *.swo
18
+ .windsurf/
19
+
20
+ # OS
21
+ .DS_Store
22
+ Thumbs.db
23
+
24
+ # Node.js
25
+ node_modules/
26
+ summa-web/dist/
27
+ summa-web/dist-model-lab/
28
+ summa-model-lab/dist/
29
+
30
+ # Python
31
+ __pycache__/
32
+ *.pyc
33
+ *.pyo
34
+ *.pyd
35
+ .venv/
36
+ venv/
37
+ *.egg-info/
38
+ summa-python/target/
39
+
40
+ # Rust
41
+ *.rs.bk
42
+ Cargo.lock.bak
43
+
44
+ # WASM build output (regenerated on build)
45
+ summa-wasm/pkg/
46
+
47
+ # Maturin
48
+ summa-python/target/
49
+
50
+ # Environment
51
+ .env
52
+ .env.local
53
+
54
+ # Local raw benchmark evidence; reports and hash manifests remain versioned
55
+ /docs/benchmark-results/**/*.zip
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.5
2
+ Name: summa-client-python
3
+ Version: 2.0.0
4
+ Summary: Async Python client for Summa search server
5
+ Project-URL: Homepage, https://github.com/SpaceFrontiers/summa
6
+ Project-URL: Repository, https://github.com/SpaceFrontiers/summa
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
+ # Summa Python client
26
+
27
+ Async Python client for the
28
+ [Summa](https://github.com/SpaceFrontiers/summa) gRPC search server.
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ pip install summa-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 summa_client_python import SummaClient
44
+
45
+
46
+ async def main():
47
+ async with SummaClient("localhost:50051") as client:
48
+ await client.create_index(
49
+ "articles",
50
+ """
51
+ index articles {
52
+ field id: text<raw> [primary, stored]
53
+ field title: text<simple> [indexed, stored]
54
+ field body: text<simple> [indexed, stored]
55
+ }
56
+ """,
57
+ )
58
+
59
+ indexed, error_count, errors = await client.index_documents(
60
+ "articles",
61
+ [
62
+ {"id": "1", "title": "Hello World", "body": "First article"},
63
+ {"id": "2", "title": "Summa Search", "body": "Fast retrieval"},
64
+ ],
65
+ )
66
+ if error_count:
67
+ raise RuntimeError(errors)
68
+ print(f"Indexed {indexed} documents")
69
+
70
+ await client.commit("articles")
71
+
72
+ results = await client.search(
73
+ "articles",
74
+ query={"match": {"field": "title", "text": "hello"}},
75
+ fields_to_load=["title", "body"],
76
+ )
77
+ for hit in results.hits:
78
+ print(hit.address, hit.score, hit.fields)
79
+
80
+ if results.hits:
81
+ document = await client.get_document("articles", results.hits[0].address)
82
+ print(document.fields if document else "document not found")
83
+
84
+
85
+ asyncio.run(main())
86
+ ```
87
+
88
+ The context manager connects and closes the channel. Manual callers use
89
+ `await client.connect()` / `await client.close()`.
90
+
91
+ ## Index management
92
+
93
+ ```python
94
+ await client.list_indexes()
95
+ info = await client.get_index_info("articles")
96
+ await client.reorder("articles")
97
+ await client.retrain_vector_index("articles")
98
+ ```
99
+
100
+ `index_documents` returns `(indexed_count, error_count, errors)`;
101
+ `index_documents_stream` takes an async iterable and returns counts. Inspect
102
+ errors, then `commit()` to publish accepted work. Repeated values are lists;
103
+ flat numeric lists are dense vectors, `(dimension, weight)` pairs are sparse
104
+ vectors. See [client types](src/summa_client_python/types.py).
105
+
106
+ ### Delete and upsert documents
107
+
108
+ With a text primary key, delete by exact key or supply a complete replacement:
109
+
110
+ ```python
111
+ await client.delete_document("articles", "2")
112
+ await client.upsert_document("articles", {"id": "1", "title": "Updated title"})
113
+ await client.commit("articles")
114
+ ```
115
+
116
+ `delete_documents` / `upsert_documents` return `DocumentMutationResult` with
117
+ `accepted_count` and `errors: [{index, error}]`. Single-item helpers raise on
118
+ rejection. Missing deletes succeed; upserts insert missing keys. Staged rows can
119
+ be replaced/deleted again before commit; the latest accepted version wins.
120
+ Optional [content hashes](../docs/content-deduplication.md) skip unchanged writes.
121
+
122
+ Limits: 100,000 deletion keys / 8 MiB key bytes; 1,000 replacements / 32 MiB
123
+ encoded protobuf, or one replacement / 200 MiB including the request envelope.
124
+ Broker commits are atomic per partition. See [mutation semantics](../docs/row-deletion.md).
125
+
126
+ ### Compact deleted rows
127
+
128
+ ```python
129
+ await client.force_merge("articles") # Retain tombstones.
130
+ await client.force_merge("articles", compact=True) # Physically remove deleted rows.
131
+ ```
132
+
133
+ Compaction handles singleton segments and may change addresses and BM25 scores.
134
+ Index info exposes `num_docs`, `physical_num_docs`, `num_deleted_docs`, and
135
+ `deleted_ratio`. Use primary keys for durable identity.
136
+
137
+ ## Searching
138
+
139
+ `search(index_name, query=..., limit=10, fields_to_load=[...])` accepts one query
140
+ variant: `term`, `match`, `phrase`, `boolean`, `sparse_vector`, `dense_vector`,
141
+ `binary_dense_vector`, `boost`, `range`, `prefix`, `all`, or `fusion`.
142
+ See [query types](src/summa_client_python/types.py) and the
143
+ [wire contract](../summa-proto/summa.proto) for options.
144
+
145
+ ```python
146
+ results = await client.search(
147
+ "articles",
148
+ query={
149
+ "boolean": {
150
+ "must": [{"match": {"field": "title", "text": "search"}}],
151
+ "must_not": [{"term": {"field": "title", "term": "draft"}}],
152
+ }
153
+ },
154
+ fields_to_load=["title"],
155
+ )
156
+ ```
157
+
158
+ `get_document(index_name, hit.address)` uses the full segment/document address
159
+ and returns `None` on `NOT_FOUND`.
160
+
161
+ ## Ranking diagnostics and recall traces
162
+
163
+ Search options `include_rrf_scores=True` and `tracing=True` default to false.
164
+ RRF diagnostics describe organic branch nominations; traces retain bounded
165
+ candidates and query trees, including hits outside the final page. Neither
166
+ changes retrieval depth or ranking. Oversized exports fail explicitly.
167
+
168
+ For named branches, use `l1={"formula": "0.2 * title + 0.8 * body + 3 * rrf"}`.
169
+ The formula is the only L1 scoring interface; old coefficient fields are removed.
170
+ See [candidate scoring](../docs/candidate-rescoring.md) for backfill, passage
171
+ selection, expression limits, capability versions, and distributed behavior.
172
+
173
+ ## Deadlines and errors
174
+
175
+ RPCs accept `timeout` in seconds, overriding the constructor's `default_timeout`.
176
+ gRPC failures raise `grpc.aio.AioRpcError`, except document `NOT_FOUND` as above.
177
+ An expired mutation may already be staged, and an accepted commit continues
178
+ after disconnection. Resolve the outcome before retrying replacements.
179
+
180
+ ## Development
181
+
182
+ From this directory:
183
+
184
+ ```bash
185
+ uv sync --group dev --group test
186
+ uv run ruff check .
187
+ uv run ruff format --check .
188
+ uv run pytest tests/test_client_unit.py
189
+ uv run --group dev python generate_proto.py
190
+ ```
191
+
192
+ Integration tests require `target/debug/summa-server`. Regenerate bindings after
193
+ [protocol changes](../summa-proto/README.md#regeneration-and-validation).
@@ -0,0 +1,169 @@
1
+ # Summa Python client
2
+
3
+ Async Python client for the
4
+ [Summa](https://github.com/SpaceFrontiers/summa) gRPC search server.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ pip install summa-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 summa_client_python import SummaClient
20
+
21
+
22
+ async def main():
23
+ async with SummaClient("localhost:50051") as client:
24
+ await client.create_index(
25
+ "articles",
26
+ """
27
+ index articles {
28
+ field id: text<raw> [primary, stored]
29
+ field title: text<simple> [indexed, stored]
30
+ field body: text<simple> [indexed, stored]
31
+ }
32
+ """,
33
+ )
34
+
35
+ indexed, error_count, errors = await client.index_documents(
36
+ "articles",
37
+ [
38
+ {"id": "1", "title": "Hello World", "body": "First article"},
39
+ {"id": "2", "title": "Summa Search", "body": "Fast retrieval"},
40
+ ],
41
+ )
42
+ if error_count:
43
+ raise RuntimeError(errors)
44
+ print(f"Indexed {indexed} documents")
45
+
46
+ await client.commit("articles")
47
+
48
+ results = await client.search(
49
+ "articles",
50
+ query={"match": {"field": "title", "text": "hello"}},
51
+ fields_to_load=["title", "body"],
52
+ )
53
+ for hit in results.hits:
54
+ print(hit.address, hit.score, hit.fields)
55
+
56
+ if results.hits:
57
+ document = await client.get_document("articles", results.hits[0].address)
58
+ print(document.fields if document else "document not found")
59
+
60
+
61
+ asyncio.run(main())
62
+ ```
63
+
64
+ The context manager connects and closes the channel. Manual callers use
65
+ `await client.connect()` / `await client.close()`.
66
+
67
+ ## Index management
68
+
69
+ ```python
70
+ await client.list_indexes()
71
+ info = await client.get_index_info("articles")
72
+ await client.reorder("articles")
73
+ await client.retrain_vector_index("articles")
74
+ ```
75
+
76
+ `index_documents` returns `(indexed_count, error_count, errors)`;
77
+ `index_documents_stream` takes an async iterable and returns counts. Inspect
78
+ errors, then `commit()` to publish accepted work. Repeated values are lists;
79
+ flat numeric lists are dense vectors, `(dimension, weight)` pairs are sparse
80
+ vectors. See [client types](src/summa_client_python/types.py).
81
+
82
+ ### Delete and upsert documents
83
+
84
+ With a text primary key, delete by exact key or supply a complete replacement:
85
+
86
+ ```python
87
+ await client.delete_document("articles", "2")
88
+ await client.upsert_document("articles", {"id": "1", "title": "Updated title"})
89
+ await client.commit("articles")
90
+ ```
91
+
92
+ `delete_documents` / `upsert_documents` return `DocumentMutationResult` with
93
+ `accepted_count` and `errors: [{index, error}]`. Single-item helpers raise on
94
+ rejection. Missing deletes succeed; upserts insert missing keys. Staged rows can
95
+ be replaced/deleted again before commit; the latest accepted version wins.
96
+ Optional [content hashes](../docs/content-deduplication.md) skip unchanged writes.
97
+
98
+ Limits: 100,000 deletion keys / 8 MiB key bytes; 1,000 replacements / 32 MiB
99
+ encoded protobuf, or one replacement / 200 MiB including the request envelope.
100
+ Broker commits are atomic per partition. See [mutation semantics](../docs/row-deletion.md).
101
+
102
+ ### Compact deleted rows
103
+
104
+ ```python
105
+ await client.force_merge("articles") # Retain tombstones.
106
+ await client.force_merge("articles", compact=True) # Physically remove deleted rows.
107
+ ```
108
+
109
+ Compaction handles singleton segments and may change addresses and BM25 scores.
110
+ Index info exposes `num_docs`, `physical_num_docs`, `num_deleted_docs`, and
111
+ `deleted_ratio`. Use primary keys for durable identity.
112
+
113
+ ## Searching
114
+
115
+ `search(index_name, query=..., limit=10, fields_to_load=[...])` accepts one query
116
+ variant: `term`, `match`, `phrase`, `boolean`, `sparse_vector`, `dense_vector`,
117
+ `binary_dense_vector`, `boost`, `range`, `prefix`, `all`, or `fusion`.
118
+ See [query types](src/summa_client_python/types.py) and the
119
+ [wire contract](../summa-proto/summa.proto) for options.
120
+
121
+ ```python
122
+ results = await client.search(
123
+ "articles",
124
+ query={
125
+ "boolean": {
126
+ "must": [{"match": {"field": "title", "text": "search"}}],
127
+ "must_not": [{"term": {"field": "title", "term": "draft"}}],
128
+ }
129
+ },
130
+ fields_to_load=["title"],
131
+ )
132
+ ```
133
+
134
+ `get_document(index_name, hit.address)` uses the full segment/document address
135
+ and returns `None` on `NOT_FOUND`.
136
+
137
+ ## Ranking diagnostics and recall traces
138
+
139
+ Search options `include_rrf_scores=True` and `tracing=True` default to false.
140
+ RRF diagnostics describe organic branch nominations; traces retain bounded
141
+ candidates and query trees, including hits outside the final page. Neither
142
+ changes retrieval depth or ranking. Oversized exports fail explicitly.
143
+
144
+ For named branches, use `l1={"formula": "0.2 * title + 0.8 * body + 3 * rrf"}`.
145
+ The formula is the only L1 scoring interface; old coefficient fields are removed.
146
+ See [candidate scoring](../docs/candidate-rescoring.md) for backfill, passage
147
+ selection, expression limits, capability versions, and distributed behavior.
148
+
149
+ ## Deadlines and errors
150
+
151
+ RPCs accept `timeout` in seconds, overriding the constructor's `default_timeout`.
152
+ gRPC failures raise `grpc.aio.AioRpcError`, except document `NOT_FOUND` as above.
153
+ An expired mutation may already be staged, and an accepted commit continues
154
+ after disconnection. Resolve the outcome before retrying replacements.
155
+
156
+ ## Development
157
+
158
+ From this directory:
159
+
160
+ ```bash
161
+ uv sync --group dev --group test
162
+ uv run ruff check .
163
+ uv run ruff format --check .
164
+ uv run pytest tests/test_client_unit.py
165
+ uv run --group dev python generate_proto.py
166
+ ```
167
+
168
+ Integration tests require `target/debug/summa-server`. Regenerate bindings after
169
+ [protocol changes](../summa-proto/README.md#regeneration-and-validation).
@@ -0,0 +1,54 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "summa-client-python"
7
+ version = "2.0.0"
8
+ description = "Async Python client for Summa search server"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ { name = "izihawa" }
14
+ ]
15
+ keywords = ["search", "full-text-search", "grpc", "async"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Database :: Database Engines/Servers",
26
+ "Topic :: Text Processing :: Indexing",
27
+ ]
28
+ dependencies = [
29
+ "grpcio>=1.76.0",
30
+ "protobuf>=6.33.4",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/SpaceFrontiers/summa"
35
+ Repository = "https://github.com/SpaceFrontiers/summa"
36
+
37
+ [dependency-groups]
38
+ dev = [
39
+ "grpcio-tools>=1.76.0",
40
+ "ruff==0.16.0",
41
+ ]
42
+
43
+ test = [
44
+ "pytest>=8.0.0",
45
+ "pytest-asyncio>=0.24.0",
46
+ ]
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["src/summa_client_python"]
50
+
51
+ [tool.hatch.build.targets.sdist]
52
+ include = [
53
+ "/src",
54
+ ]
@@ -0,0 +1,75 @@
1
+ """Async Python client for Summa search server."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ from .client import SummaClient
6
+ from .types import (
7
+ AllQuery,
8
+ BinaryDenseVectorQuery,
9
+ BooleanQuery,
10
+ BoostQuery,
11
+ CandidateScores,
12
+ Combiner,
13
+ DenseVectorQuery,
14
+ DocAddress,
15
+ Document,
16
+ DocumentMutationError,
17
+ DocumentMutationResult,
18
+ FusionCandidate,
19
+ FusionCandidateList,
20
+ IndexInfo,
21
+ MatchQuery,
22
+ OrdinalScore,
23
+ PassageScores,
24
+ QueryTrace,
25
+ RangeQuery,
26
+ Reranker,
27
+ RrfContribution,
28
+ SearchHit,
29
+ SearchResponse,
30
+ SearchTimings,
31
+ SearchTrace,
32
+ ShardSearchTrace,
33
+ SparseVectorQuery,
34
+ TermQuery,
35
+ VectorFieldStats,
36
+ )
37
+
38
+ __all__ = [
39
+ "SummaClient",
40
+ "AllQuery",
41
+ "BinaryDenseVectorQuery",
42
+ "BooleanQuery",
43
+ "BoostQuery",
44
+ "Combiner",
45
+ "CandidateScores",
46
+ "FusionCandidate",
47
+ "FusionCandidateList",
48
+ "PassageScores",
49
+ "RrfContribution",
50
+ "QueryTrace",
51
+ "SearchTrace",
52
+ "ShardSearchTrace",
53
+ "DenseVectorQuery",
54
+ "DocAddress",
55
+ "Document",
56
+ "DocumentMutationResult",
57
+ "DocumentMutationError",
58
+ "IndexInfo",
59
+ "MatchQuery",
60
+ "OrdinalScore",
61
+ "RangeQuery",
62
+ "Reranker",
63
+ "SearchHit",
64
+ "SearchResponse",
65
+ "SearchTimings",
66
+ "SparseVectorQuery",
67
+ "TermQuery",
68
+ "VectorFieldStats",
69
+ ]
70
+
71
+ try:
72
+ __version__ = version("summa-client-python")
73
+ except PackageNotFoundError:
74
+ # Source-only imports (without an installed wheel/editable distribution).
75
+ __version__ = "0.0.0+unknown"