nidus 0.40.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.
- nidus-0.40.0/.gitignore +10 -0
- nidus-0.40.0/LICENSE +21 -0
- nidus-0.40.0/PKG-INFO +361 -0
- nidus-0.40.0/README.md +321 -0
- nidus-0.40.0/pyproject.toml +142 -0
- nidus-0.40.0/src/nidus/__init__.py +101 -0
- nidus-0.40.0/src/nidus/_guards.py +59 -0
- nidus-0.40.0/src/nidus/_version.py +15 -0
- nidus-0.40.0/src/nidus/_wire.py +541 -0
- nidus-0.40.0/src/nidus/aio.py +311 -0
- nidus-0.40.0/src/nidus/client.py +395 -0
- nidus-0.40.0/src/nidus/errors.py +58 -0
- nidus-0.40.0/src/nidus/filter.py +102 -0
- nidus-0.40.0/src/nidus/py.typed +0 -0
- nidus-0.40.0/src/nidus/types.py +130 -0
- nidus-0.40.0/src/nidus/values.py +202 -0
- nidus-0.40.0/tests/test_aio.py +447 -0
- nidus-0.40.0/tests/test_client.py +707 -0
- nidus-0.40.0/tests/test_filter.py +160 -0
- nidus-0.40.0/tests/test_integration.py +323 -0
- nidus-0.40.0/tests/test_typing.py +69 -0
- nidus-0.40.0/tests/test_values.py +229 -0
- nidus-0.40.0/tests/test_wire.py +656 -0
nidus-0.40.0/.gitignore
ADDED
nidus-0.40.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 duckedup
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
nidus-0.40.0/PKG-INFO
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nidus
|
|
3
|
+
Version: 0.40.0
|
|
4
|
+
Summary: Python client for nidus — a small, fast vector store. Connects to a local or remote `nidus serve` over HTTP.
|
|
5
|
+
Project-URL: Homepage, https://nidus.duckedup.org
|
|
6
|
+
Project-URL: Documentation, https://nidus.duckedup.org/sdks/python/
|
|
7
|
+
Project-URL: Repository, https://github.com/duckedup/nidus
|
|
8
|
+
Project-URL: Source, https://github.com/duckedup/nidus/tree/main/sdks/python
|
|
9
|
+
Project-URL: Issues, https://github.com/duckedup/nidus/issues
|
|
10
|
+
Author: duckedup
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: client,embeddings,nidus,sdk,semantic-search,similarity-search,vector,vector-database,vector-store
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Topic :: Database
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.9
|
|
29
|
+
Provides-Extra: async
|
|
30
|
+
Requires-Dist: httpx>=0.27; extra == 'async'
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
33
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
34
|
+
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
38
|
+
Requires-Dist: twine>=5.1; extra == 'dev'
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# nidus (Python)
|
|
42
|
+
|
|
43
|
+
The Python client for [nidus](https://nidus.duckedup.org) — a small, fast vector store.
|
|
44
|
+
This package drives a running `nidus serve` instance over HTTP, whether it is on your
|
|
45
|
+
laptop or a remote host.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install nidus # the sync client — pulls ZERO dependencies
|
|
49
|
+
pip install 'nidus[async]' # adds AsyncNidusClient (httpx)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`NidusClient` is built on `urllib.request`, so installing this package brings nothing
|
|
53
|
+
else with it — the same zero-dependency posture the JS SDK gets from the platform
|
|
54
|
+
`fetch`. Only the async client needs a third-party HTTP stack, and it is quarantined
|
|
55
|
+
behind the `async` extra. Python 3.9+.
|
|
56
|
+
|
|
57
|
+
This package is versioned in lockstep with nidus itself: the crate's version is the
|
|
58
|
+
single source of truth, so a given `nidus` release on PyPI is the client for the
|
|
59
|
+
identically-numbered nidus release. Match the two and the wire contract lines up.
|
|
60
|
+
|
|
61
|
+
## Connecting
|
|
62
|
+
|
|
63
|
+
"Local vs remote" is just the base URL — point the client at a local `nidus serve` or
|
|
64
|
+
any reachable host.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import os
|
|
68
|
+
|
|
69
|
+
from nidus import NidusClient
|
|
70
|
+
|
|
71
|
+
# Local
|
|
72
|
+
db = NidusClient("http://127.0.0.1:7700")
|
|
73
|
+
|
|
74
|
+
# Remote, with the bearer token the server was started with (`nidus serve --token`)
|
|
75
|
+
db = NidusClient(
|
|
76
|
+
"https://nidus.internal.example.com",
|
|
77
|
+
token=os.environ["NIDUS_TOKEN"],
|
|
78
|
+
timeout=5.0, # per-request timeout in SECONDS; None (the default) means no timeout
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Both clients work as context managers. Nothing is opened until the first request, and the
|
|
83
|
+
default `urllib` transport is connectionless, so `close()` matters only once a pooled
|
|
84
|
+
transport (below) or the async client is in play — using `with` means you never have to
|
|
85
|
+
remember which case you are in:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
with NidusClient("http://127.0.0.1:7700") as db:
|
|
89
|
+
print(db.health()) # True when the server answers; never raises
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### The async client
|
|
93
|
+
|
|
94
|
+
`AsyncNidusClient` mirrors the sync client method for method, with `async def` and
|
|
95
|
+
`aclose()`. It requires `pip install 'nidus[async]'`; without `httpx` the import fails
|
|
96
|
+
with an `ImportError` that names that fix. Either spelling works:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from nidus.aio import AsyncNidusClient # explicit
|
|
100
|
+
import nidus; nidus.AsyncNidusClient # lazy — resolved on first attribute access
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`import nidus` itself never touches `httpx`, which is what keeps the dependency
|
|
104
|
+
genuinely optional.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
import asyncio
|
|
108
|
+
from nidus import f
|
|
109
|
+
from nidus.aio import AsyncNidusClient
|
|
110
|
+
|
|
111
|
+
async def main():
|
|
112
|
+
async with AsyncNidusClient("http://127.0.0.1:7700") as db:
|
|
113
|
+
await db.create_collection("docs")
|
|
114
|
+
await db.upsert("docs", [{"id": "a", "vector": [0.1, 0.2, 0.3]}])
|
|
115
|
+
hits = await db.search(query=[0.1, 0.2, 0.3], top_k=5, filter=[f.eq("lang", "rust")])
|
|
116
|
+
|
|
117
|
+
asyncio.run(main())
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Upserting and searching
|
|
121
|
+
|
|
122
|
+
`attrs` accept plain Python values — `str`, `int`, `bool`, lists of `str`, and `None` —
|
|
123
|
+
and are normalized to nidus's typed values for you. Results come back with `attrs`
|
|
124
|
+
decoded to plain Python values.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
db.create_collection("docs")
|
|
128
|
+
|
|
129
|
+
db.upsert("docs", [
|
|
130
|
+
{"id": "a", "vector": [0.1, 0.2, 0.3], "attrs": {"lang": "rust", "year": 2024}},
|
|
131
|
+
{"id": "b", "vector": [0.4, 0.5, 0.6], "attrs": {"lang": "go", "year": 2023}},
|
|
132
|
+
# a text-only doc — omit the vector
|
|
133
|
+
{"id": "c", "attrs": {"body": "vector stores are neat"}},
|
|
134
|
+
])
|
|
135
|
+
|
|
136
|
+
for hit in db.search(query=[0.1, 0.2, 0.3], top_k=5):
|
|
137
|
+
print(hit.collection, hit.id, hit.score, hit.attrs.get("lang"))
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`upsert` and `delete` return a count; the search family returns a list of `Hit`
|
|
141
|
+
dataclasses (`collection`, `id`, `score`, `attrs`). `attrs` is a plain `dict`, so reach
|
|
142
|
+
for `.get` unless every record in scope is known to carry the key — a search spans
|
|
143
|
+
whatever the scope holds, and attrs are per-record, not a schema.
|
|
144
|
+
|
|
145
|
+
There is no float attribute type — floats belong in the vector, so passing one raises
|
|
146
|
+
`TypeError` rather than silently truncating. For an explicit type, use the `v.*`
|
|
147
|
+
helpers (`v.str`, `v.int`, `v.bool`, `v.list`, `v.nil`):
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from nidus import v
|
|
151
|
+
|
|
152
|
+
db.upsert("docs", [{"id": "d", "attrs": {"tags": v.list(["a", "b"]), "rank": v.int(7)}}])
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`v.nil()` is the explicit `Null` value — "set, and empty" — which is a different fact
|
|
156
|
+
from an absent key ("not set / not indexed"). The SDK keeps them apart.
|
|
157
|
+
|
|
158
|
+
## Filtering
|
|
159
|
+
|
|
160
|
+
Build an AND-filter with the `f.*` helpers. Each predicate is a positive assertion about
|
|
161
|
+
a **present** attribute, so an absent key matches nothing — including the negative
|
|
162
|
+
predicates (`ne`, `not_in`) and the ranges.
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from nidus import f
|
|
166
|
+
|
|
167
|
+
hits = db.search(
|
|
168
|
+
query=[0.1, 0.2, 0.3],
|
|
169
|
+
top_k=10,
|
|
170
|
+
filter=f.and_(
|
|
171
|
+
f.eq("lang", "rust"),
|
|
172
|
+
f.ge("year", 2020),
|
|
173
|
+
f.in_("status", ["published", "draft"]),
|
|
174
|
+
f.glob("path", "src/*"),
|
|
175
|
+
),
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Predicates: `eq`, `ne`, `glob`, `in_`, `not_in`, `lt`, `le`, `gt`, `ge`, plus `and_`.
|
|
180
|
+
The three trailing underscores are not style — `in` and `and` are reserved words in
|
|
181
|
+
Python, so `f.in_`, `f.not_in`, and `f.and_` are the JS SDK's `f.in`, `f.notIn`, and
|
|
182
|
+
`f.and`. Nothing else deviates.
|
|
183
|
+
|
|
184
|
+
A `Filter` is just a `list` of predicates, AND-combined, so `f.and_(...)` is sugar for
|
|
185
|
+
building that list — `filter=[f.eq("lang", "rust")]` is equally valid.
|
|
186
|
+
|
|
187
|
+
Comparisons are same-type only (int↔int numeric, str↔str lexical, bool↔bool). A range
|
|
188
|
+
predicate against a mismatched type matches nothing, which is the usual reason a filter
|
|
189
|
+
mysteriously returns no rows.
|
|
190
|
+
|
|
191
|
+
## Full-text and hybrid search
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
db.set_fts_schema("docs", ["body"])
|
|
195
|
+
|
|
196
|
+
# BM25 text search over one indexed field
|
|
197
|
+
text_hits = db.text_search(field="body", query="vector store", top_k=10)
|
|
198
|
+
|
|
199
|
+
# Fuse a vector query and a BM25 query via reciprocal rank fusion
|
|
200
|
+
hybrid_hits = db.hybrid_search(
|
|
201
|
+
vector=[0.1, 0.2, 0.3],
|
|
202
|
+
field="body",
|
|
203
|
+
text="vector store",
|
|
204
|
+
top_k=10,
|
|
205
|
+
)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`hybrid_search` takes no `min_score`: its score is a fused RRF rank, not a similarity,
|
|
209
|
+
so there is no meaningful floor to set. `rrf_k` and `candidates` tune the fusion.
|
|
210
|
+
|
|
211
|
+
## Remembering and recalling (text-native)
|
|
212
|
+
|
|
213
|
+
When the server is started with an embedder (`nidus serve --embed-provider …`) you can
|
|
214
|
+
send **text** and let the server embed it — no need to compute vectors client-side.
|
|
215
|
+
`remember` embeds and upserts; `recall` embeds the query and vector-searches.
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
# Embed "the quick brown fox" and store it under id "a"
|
|
219
|
+
db.remember("notes", "a", "the quick brown fox", attrs={"tag": "x"})
|
|
220
|
+
|
|
221
|
+
# Summarize first, then embed the summary (the server also needs --summarize-provider).
|
|
222
|
+
# The stored record additionally carries `nidus.summary` and `nidus.source` attrs.
|
|
223
|
+
db.remember("notes", "b", long_article, mode="summarize")
|
|
224
|
+
|
|
225
|
+
# Embed the query text and search, best first
|
|
226
|
+
hits = db.recall("notes", "quick fox", top_k=5, min_score=0.2, filter=[f.eq("tag", "x")])
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Both raise `NidusError` with status `400` against a server that has **no embedder
|
|
230
|
+
configured** (the message names `--embed-provider`), and `mode="summarize"` without a
|
|
231
|
+
summarizer is likewise a `400`. The client only ever sends text; the embedding always
|
|
232
|
+
happens server-side.
|
|
233
|
+
|
|
234
|
+
## Everything else
|
|
235
|
+
|
|
236
|
+
Every endpoint of the HTTP API has a method:
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
db.collections() # list[str]
|
|
240
|
+
db.stats() # dimension, distance, ANN config, collections, footprint
|
|
241
|
+
db.list(scope=["docs"], filter=[f.eq("lang", "rust")], offset=0, limit=50)
|
|
242
|
+
db.records("docs") # every record, attrs decoded; vector is None for text-only
|
|
243
|
+
db.get_meta("docs"); db.set_meta("docs", {"owner": "search-team"})
|
|
244
|
+
db.delete("docs", ["a"]) # by id
|
|
245
|
+
db.delete_where("docs", f.and_(f.lt("year", 2000)))
|
|
246
|
+
db.flush(); db.compact()
|
|
247
|
+
db.drop_collection("docs")
|
|
248
|
+
db.health() # bool
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Optional arguments all default to `None`, which means "omit the key" so the **server's**
|
|
252
|
+
default applies (`top_k = 10`, `limit = 100`, `rrf_k = 60.0`, `candidates = 100`). Those
|
|
253
|
+
numbers are deliberately not restated in Python, and it is why the defaults are `None`
|
|
254
|
+
rather than a number: `top_k=0` is a legitimate request for zero results, so `0` cannot
|
|
255
|
+
double as "unset".
|
|
256
|
+
|
|
257
|
+
`stats().ann` is `None` when the store does exact brute-force search, rather than an
|
|
258
|
+
`AnnInfo` full of defaults. Likewise `Record.vector` is `None` — never `[]` — for a
|
|
259
|
+
text-only document.
|
|
260
|
+
|
|
261
|
+
## Three things the client refuses to send
|
|
262
|
+
|
|
263
|
+
Python's type system cannot express two mistakes that produce a **well-formed** request the
|
|
264
|
+
server accepts and answers wrongly, so the SDK refuses them at the call site instead:
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
db.delete("docs", "a") # TypeError: a str IS a Sequence[str] — this asked to
|
|
268
|
+
# delete the ids "a"... one character at a time
|
|
269
|
+
db.search(query=vec, scope="docs") # TypeError — same slip, five collections that
|
|
270
|
+
# do not exist, an empty result and a 200
|
|
271
|
+
f.in_("lang", "rust") # TypeError — one predicate value per character
|
|
272
|
+
db.delete_where("docs", []) # ValueError: an empty filter matches EVERYTHING, so this
|
|
273
|
+
# deleted the whole collection; use drop_collection
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
None of these raise anywhere else in the stack: `mypy --strict` accepts all four, and the
|
|
277
|
+
server answers `200`. The list forms (`["a"]`, `["docs"]`, `["rust"]`) are what was meant.
|
|
278
|
+
|
|
279
|
+
Vectors, conversely, are *accepted* more widely than JSON allows: elements are coerced with
|
|
280
|
+
`float()`, so `numpy` arrays (`np.float32` is not a `float` subclass and `json` refuses it),
|
|
281
|
+
torch scalars and `Decimal` all work without a `.tolist()` first.
|
|
282
|
+
|
|
283
|
+
## Bulk ingest: supply a pooled transport
|
|
284
|
+
|
|
285
|
+
The honest cost of a standard-library-only client: `urllib.request` opens a **fresh
|
|
286
|
+
connection per request**. For interactive use that is invisible; for a long run of
|
|
287
|
+
sequential upserts the handshakes are measurable overhead.
|
|
288
|
+
|
|
289
|
+
The escape hatch is `transport=` — a callable
|
|
290
|
+
`(method, url, headers, body, timeout) -> (status, text)`. Hand in one backed by `httpx`
|
|
291
|
+
or `requests` and you get pooling, keep-alive, retries, or instrumentation without the
|
|
292
|
+
SDK taking on a dependency for everyone:
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
import httpx
|
|
296
|
+
from nidus import NidusClient
|
|
297
|
+
|
|
298
|
+
class PooledTransport:
|
|
299
|
+
"""A connection-pooling transport for bulk ingest."""
|
|
300
|
+
|
|
301
|
+
def __init__(self) -> None:
|
|
302
|
+
self._client = httpx.Client()
|
|
303
|
+
|
|
304
|
+
def __call__(self, method, url, headers, body, timeout):
|
|
305
|
+
# A transport RETURNS non-2xx statuses; only a failure to get any response
|
|
306
|
+
# at all should raise. httpx already behaves that way.
|
|
307
|
+
r = self._client.request(method, url, content=body, headers=headers, timeout=timeout)
|
|
308
|
+
return r.status_code, r.text
|
|
309
|
+
|
|
310
|
+
def close(self) -> None:
|
|
311
|
+
# NidusClient.close() calls close() on the transport if it has one, so
|
|
312
|
+
# `with NidusClient(...)` shuts the pool down too.
|
|
313
|
+
self._client.close()
|
|
314
|
+
|
|
315
|
+
with NidusClient("http://127.0.0.1:7700", transport=PooledTransport()) as db:
|
|
316
|
+
for batch in batches:
|
|
317
|
+
db.upsert("docs", batch)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
The same seam is what lets the SDK's own unit tests exercise every endpoint with no
|
|
321
|
+
server and no socket. `AsyncNidusClient` takes the natural equivalent for its own stack —
|
|
322
|
+
`transport=` there is an `httpx.AsyncBaseTransport` (a pre-tuned pool, or an
|
|
323
|
+
`httpx.MockTransport` for tests); it pools by default, so nothing extra is needed for
|
|
324
|
+
bulk ingest.
|
|
325
|
+
|
|
326
|
+
## Errors
|
|
327
|
+
|
|
328
|
+
A failed request raises `NidusError` carrying the HTTP status the server reported, so
|
|
329
|
+
you can tell a client fault from a server fault:
|
|
330
|
+
|
|
331
|
+
```python
|
|
332
|
+
from nidus import NidusError
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
db.upsert("docs", records)
|
|
336
|
+
except NidusError as err:
|
|
337
|
+
if err.is_bad_request: # 400 — e.g. a vector dimension mismatch
|
|
338
|
+
...
|
|
339
|
+
if err.is_locked: # 409 — the writer lock is held by another process
|
|
340
|
+
...
|
|
341
|
+
print(err.status, err.message)
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Also available: `is_read_only` (403), `is_out_of_capacity` (507 — `max_vector_bytes`
|
|
345
|
+
exceeded, or OOM), and `is_transport_error`.
|
|
346
|
+
|
|
347
|
+
A status of `0` is the sentinel for **no response at all** — connection refused, DNS
|
|
348
|
+
failure, or the request exceeded `timeout`. Every nidus SDK uses the same sentinel, so
|
|
349
|
+
"was this even reachable?" is answered identically in all of them.
|
|
350
|
+
|
|
351
|
+
Value errors are raised locally, before any request: a `float` attribute or a
|
|
352
|
+
non-string list element is a `TypeError`, and an integer outside `i64` is a `ValueError`
|
|
353
|
+
(Python's ints are unbounded; the store's `Int` is not).
|
|
354
|
+
|
|
355
|
+
## Documentation
|
|
356
|
+
|
|
357
|
+
Full documentation: <https://nidus.duckedup.org/sdks/python/>
|
|
358
|
+
|
|
359
|
+
## License
|
|
360
|
+
|
|
361
|
+
MIT
|