corvid-python 0.3.3__cp311-abi3-win_amd64.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.
corvid/__init__.py ADDED
@@ -0,0 +1,50 @@
1
+ """corvid — the Python binding for the corvid embedded database.
2
+
3
+ The whole public surface is implemented natively (the pyo3 crate in
4
+ this repo, built by maturin as ``corvid._native``): the ``Db`` /
5
+ ``Collection`` / ``Query`` classes, the ``field()`` predicate builder,
6
+ and the ``CorvidError`` exception. This package re-exports it; the
7
+ type stubs in ``__init__.pyi`` (shipped with ``py.typed``) carry the
8
+ full typing.
9
+
10
+ Engine semantics, the value mapping, and the error-code table are
11
+ documented in docs/PLAN.md and README.md in the repository.
12
+ """
13
+
14
+ from corvid._native import (
15
+ CorvidError,
16
+ Db,
17
+ ErrorCode,
18
+ FieldRef,
19
+ GeoHit,
20
+ Page,
21
+ Predicate,
22
+ Query,
23
+ Row,
24
+ SchemaField,
25
+ and_,
26
+ field,
27
+ ffi_version,
28
+ not_,
29
+ or_,
30
+ )
31
+
32
+ __all__ = [
33
+ "CorvidError",
34
+ "Db",
35
+ "ErrorCode",
36
+ "FieldRef",
37
+ "GeoHit",
38
+ "Page",
39
+ "Predicate",
40
+ "Query",
41
+ "Row",
42
+ "SchemaField",
43
+ "and_",
44
+ "field",
45
+ "ffi_version",
46
+ "not_",
47
+ "or_",
48
+ ]
49
+
50
+ __version__ = "0.1.0"
corvid/__init__.pyi ADDED
@@ -0,0 +1,395 @@
1
+ """Type stubs for the corvid package — the fully typed public API.
2
+
3
+ The implementations are native (pyo3); this stub is the typing source
4
+ of truth, shipped with ``py.typed``.
5
+
6
+ The value mapping (docs/PLAN.md §4):
7
+
8
+ ================== =============== ===================================
9
+ Python (in) engine Value Python (out)
10
+ ================== =============== ===================================
11
+ ``None`` Null ``None``
12
+ ``bool`` Bool ``bool``
13
+ ``int`` Int (full i64) ``int`` (arbitrary precision)
14
+ ``float`` Float ``float`` (f64 bits preserved —
15
+ NaN payloads, ``-0.0``, ``±inf``)
16
+ ``str`` Text ``str``
17
+ ``bytes``/``bytes`` Bytes ``bytes``
18
+ ``array('f')`` Vector ``array('f')`` (f32-exact)
19
+ ``list``/``tuple`` Array ``list``
20
+ ``dict`` Map ``dict`` (engine key order)
21
+ ================== =============== ===================================
22
+
23
+ Out-of-i64 ints raise ``CorvidError`` code 12 (``ErrorCode.INVALID_ARGUMENT``).
24
+ """
25
+
26
+ from array import array
27
+ from collections.abc import Callable, Sequence
28
+ from typing import Literal, TypeAlias, Union
29
+
30
+ __all__ = [
31
+ "CorvidError",
32
+ "Db",
33
+ "ErrorCode",
34
+ "FieldRef",
35
+ "GeoHit",
36
+ "Page",
37
+ "Predicate",
38
+ "Query",
39
+ "Row",
40
+ "SchemaField",
41
+ "and_",
42
+ "field",
43
+ "ffi_version",
44
+ "not_",
45
+ "or_",
46
+ ]
47
+
48
+ __version__: str
49
+
50
+ #: A document key: `str` (UTF-8) or `bytes` (raw; non-UTF-8 keys come back as `bytes`).
51
+ Key: TypeAlias = Union[str, bytes]
52
+ #: A float32 vector — exactly ``array.array('f', ...)`` (other typecodes are rejected).
53
+ Vector: TypeAlias = array[float]
54
+ #: Any corvid document value (see the table in the module docstring).
55
+ CorvidValue: TypeAlias = Union[
56
+ None,
57
+ bool,
58
+ int,
59
+ float,
60
+ str,
61
+ bytes,
62
+ Vector,
63
+ Sequence["CorvidValue"],
64
+ dict[str, "CorvidValue"],
65
+ ]
66
+ #: All field types a schema may declare.
67
+ FieldType: TypeAlias = Literal["any", "bool", "int", "float", "text", "bytes", "vector", "array", "map"]
68
+ Metric: TypeAlias = Literal["cosine", "dot", "l2"]
69
+ Quantization: TypeAlias = Literal["none", "binary", "scalar"]
70
+
71
+ #: The C-ABI FFI generation this binding covers (docs/FFI.md §1.3; value 1).
72
+ def ffi_version() -> int: ...
73
+
74
+ class CorvidError(Exception):
75
+ """Every engine failure — carries the C-ABI error ``code`` (1..=19,
76
+ see :class:`ErrorCode`) and the engine ``message``."""
77
+
78
+ code: int
79
+ message: str
80
+
81
+ class ErrorCode:
82
+ """The frozen C-ABI error-code table (docs/FFI.md §1.3) — never renumbered."""
83
+
84
+ DATABASE: int
85
+ TRANSACTION: int
86
+ TABLE: int
87
+ STORAGE: int
88
+ COMMIT: int
89
+ SET_DURABILITY: int
90
+ COMPACTION: int
91
+ DECODE: int
92
+ CORRUPT_INDEX: int
93
+ RESERVED_COLLECTION: int
94
+ INVALID_NAME: int
95
+ INVALID_ARGUMENT: int
96
+ INCOMPATIBLE_FORMAT: int
97
+ EMPTY_INDEX_TRAINING: int
98
+ SCHEMA_VIOLATION: int
99
+ INVALID_DUMP: int
100
+ BACKUP_TARGET_EXISTS: int
101
+ IO: int
102
+ BUSY: int
103
+
104
+ class SchemaField:
105
+ """One declared schema field (``Collection.set_schema`` / ``Collection.schema``)."""
106
+
107
+ name: str
108
+ ty: FieldType
109
+ required: bool
110
+ unique: bool
111
+
112
+ def __init__(self, name: str, ty: FieldType, required: bool = False, unique: bool = False) -> None: ...
113
+
114
+ class Row:
115
+ """One query result row (``Query.run``)."""
116
+
117
+ key: Key
118
+ #: RRF-fused rank score (``0.0`` for pure filter/order queries).
119
+ score: float
120
+ #: The full (or projected) stored document.
121
+ document: CorvidValue
122
+
123
+ class Page:
124
+ """One page of keyset-paginated rows (``Collection.page``)."""
125
+
126
+ #: The ``(key, doc)`` rows, in key order.
127
+ rows: list[tuple[Key, CorvidValue]]
128
+ #: The resume cursor (pass as the next call's ``after``), or ``None`` at the end.
129
+ next: Key | None
130
+
131
+ class GeoHit:
132
+ """One geo-search hit (``Collection.geo_within_radius`` / ``geo_within_bbox`` / ``geo_nearest``)."""
133
+
134
+ key: Key
135
+ #: Distance from the query center in km (the ``0.0`` sentinel for bbox searches).
136
+ distance_km: float
137
+ document: CorvidValue
138
+
139
+ class Predicate:
140
+ """An opaque predicate (built by ``field()…`` / ``and_`` / ``or_`` / ``not_``).
141
+ Pass to ``Query.filter()`` or ``Collection.delete_where()``."""
142
+
143
+ class FieldRef:
144
+ """A field-path predicate builder: ``field('a.b').gt(2)`` — the dotted
145
+ path may descend maps and (by integer segment) arrays."""
146
+
147
+ def eq(self, value: CorvidValue) -> Predicate:
148
+ """Field equals ``value`` (engine semantic equality: ``NaN == NaN``,
149
+ ``-0.0 == 0.0``, numeric interop across int/float)."""
150
+
151
+ def ne(self, value: CorvidValue) -> Predicate: ...
152
+ def lt(self, value: CorvidValue) -> Predicate: ...
153
+ def le(self, value: CorvidValue) -> Predicate: ...
154
+ def gt(self, value: CorvidValue) -> Predicate: ...
155
+ def ge(self, value: CorvidValue) -> Predicate: ...
156
+ def exists(self) -> Predicate:
157
+ """The path exists (any value, including ``None``, at the path)."""
158
+
159
+ def in_(self, values: Sequence[CorvidValue]) -> Predicate: ...
160
+ def between(self, low: CorvidValue, high: CorvidValue) -> Predicate: ...
161
+ def starts_with(self, prefix: str) -> Predicate: ...
162
+ def contains(self, substring: str) -> Predicate: ...
163
+ def within_km(self, lat: float, lon: float, radius_km: float) -> Predicate: ...
164
+
165
+ def field(path: str) -> FieldRef:
166
+ """Build a predicate over a (dotted) field path."""
167
+
168
+ def and_(*preds: Predicate) -> Predicate:
169
+ """Logical AND (Python keyword → trailing underscore)."""
170
+
171
+ def or_(*preds: Predicate) -> Predicate:
172
+ """Logical OR."""
173
+
174
+ def not_(pred: Predicate) -> Predicate:
175
+ """Logical NOT."""
176
+
177
+ class Db:
178
+ """A database handle. ``Db('app.redb')`` / ``Db.open(path)`` for a file,
179
+ ``Db()`` / ``Db.open_memory()`` for a private in-memory database."""
180
+
181
+ def __init__(self, path: str | None = None) -> None: ...
182
+
183
+ @classmethod
184
+ def open(cls, path: str) -> Db: ...
185
+ @classmethod
186
+ def open_memory(cls) -> Db: ...
187
+
188
+ def __enter__(self) -> Db: ...
189
+ def __exit__(self, *exc: object) -> None: ...
190
+
191
+ def collection(self, name: str) -> Collection:
192
+ """Acquire a collection handle (lazily created by the engine on first
193
+ write; names are validated at write time)."""
194
+
195
+ def collections(self) -> list[str]:
196
+ """The names of the database's collections, in engine order."""
197
+
198
+ def backup(self, path: str) -> None:
199
+ """Copy the database to ``path`` (which must not already exist)."""
200
+
201
+ def dump_to_path(self, path: str) -> None:
202
+ """Dump the whole database (documents, indexes, schemas, TTLs, edges,
203
+ auto-id counters) to ``path``."""
204
+
205
+ def load_from_path(self, path: str) -> None:
206
+ """Replay a dump file into this database (merging)."""
207
+
208
+ def load_from_path_with_renames(self, path: str, renames: dict[str, str]) -> None:
209
+ """Replay a dump file, renaming collections per ``renames``
210
+ (``{from: to}``; targets validated before the stream is read)."""
211
+
212
+ def compact(self) -> bool:
213
+ """Compact the database file. Requires quiescence: every
214
+ ``Collection``/``Query`` derived from this db must be closed (or have
215
+ executed), otherwise a ``Busy`` ``CorvidError`` (code 19) is raised.
216
+ Returns whether any data was moved out."""
217
+
218
+ def close(self) -> None:
219
+ """Close the handle (idempotent). Derived handles may legitimately
220
+ outlive it — the engine lives until the last handle drops."""
221
+
222
+ class Collection:
223
+ """A collection handle (a context manager; ``close()`` is idempotent)."""
224
+
225
+ name: str
226
+
227
+ def __enter__(self) -> Collection: ...
228
+ def __exit__(self, *exc: object) -> None: ...
229
+ def __len__(self) -> int: ...
230
+
231
+ # -- mutations -----------------------------------------------------------
232
+ def insert(self, key: Key, doc: CorvidValue) -> None:
233
+ """Insert (replace) ``doc`` at ``key``."""
234
+
235
+ def insert_many(self, entries: Sequence[tuple[Key, CorvidValue]]) -> None:
236
+ """Bulk atomic insert (``put_many``): one transaction; a violating
237
+ pair rolls the whole batch back."""
238
+
239
+ def insert_auto(self, doc: CorvidValue) -> Key:
240
+ """Insert with an engine-generated key (20-digit, strictly monotonic
241
+ per collection); returns the key."""
242
+
243
+ def update(
244
+ self,
245
+ key: Key,
246
+ fn: Callable[[CorvidValue | None], CorvidValue | None],
247
+ ) -> None:
248
+ """Read-modify-write: ``fn`` receives the current document (or ``None``
249
+ when absent) and returns the new document — ``None`` to delete. A
250
+ raising callback aborts with code 12 and writes nothing. ``fn`` must
251
+ NOT call methods on this same Collection (non-reentrant handle lock)."""
252
+
253
+ def patch(self, key: Key, patch: CorvidValue) -> None:
254
+ """Merge the top-level fields of ``patch`` into the document at ``key``
255
+ (creating it if absent)."""
256
+
257
+ def compare_and_set(
258
+ self, key: Key, expected: CorvidValue | None, replacement: CorvidValue | None
259
+ ) -> bool:
260
+ """Atomically write ``replacement`` only if the current value equals
261
+ ``expected`` (``None`` = must be absent; ``replacement=None`` deletes
262
+ on match). Equality is the engine's semantic equality. Returns whether
263
+ the write was applied."""
264
+
265
+ def delete(self, key: Key) -> bool: ...
266
+ def delete_where(self, pred: Predicate) -> int: ...
267
+ def delete_batch(self, keys: Sequence[Key]) -> int: ...
268
+
269
+ # -- TTL -----------------------------------------------------------------
270
+ def insert_with_ttl(self, key: Key, doc: CorvidValue, expires_at: int) -> None:
271
+ """Insert with an expiry instant (``expires_at``, epoch units of your choosing)."""
272
+
273
+ def set_ttl(self, key: Key, expires_at: int) -> None: ...
274
+ def get_ttl(self, key: Key) -> int | None: ...
275
+ def purge_expired(self, now: int) -> int: ...
276
+
277
+ # -- reads ---------------------------------------------------------------
278
+ def get(self, key: Key) -> CorvidValue | None: ...
279
+ def scan(self) -> list[tuple[Key, CorvidValue]]: ...
280
+ def scan_each(self, cb: Callable[[Key, CorvidValue], bool]) -> int:
281
+ """Stream with a callback ``fn(key, doc) -> bool`` — a falsy return
282
+ stops the walk early (not an error). Returns the rows visited. The
283
+ callback must NOT call methods on this same Collection."""
284
+
285
+ def page(self, after: Key | None = None, limit: int = 10) -> Page: ...
286
+ def is_empty(self) -> bool: ...
287
+
288
+ # -- indexes ---------------------------------------------------------------
289
+ def create_scalar_index(self, field: str) -> None: ...
290
+ def create_compound_index(self, fields: Sequence[str]) -> None: ...
291
+ def phrase_search(self, field: str, phrase: str, k: int) -> list[Row]:
292
+ """DIRECT positional phrase search (engine v0.3.0; no query builder):
293
+ consecutive, in-order analyzed tokens; stop words collapse out of
294
+ adjacency. Most relevant first, ties by key, up to ``k`` rows;
295
+ ``Row.score`` is the BM25 phrase sum. ``k == 0`` answers ``[]``.
296
+ """
297
+ ...
298
+ def create_text_index(self, field: str) -> None: ...
299
+ def create_text_index_ondisk(self, field: str) -> None: ...
300
+ def create_geo_index(self, field: str) -> None: ...
301
+ def create_vector_index(self, field: str, metric: Metric) -> None: ...
302
+ def create_vector_index_quantized(self, field: str, metric: Metric, quant: Quantization) -> None: ...
303
+ def create_vector_index_ondisk(self, field: str, metric: Metric) -> None: ...
304
+ def create_vector_index_ondisk_quantized(self, field: str, metric: Metric, quant: Quantization) -> None: ...
305
+ def create_vector_index_pq(self, field: str, metric: Metric, m: int, k: int) -> None:
306
+ """In-memory product-quantized HNSW index (``dim % m == 0`` required)."""
307
+
308
+ def create_vector_index_ondisk_pq(self, field: str, metric: Metric, m: int, k: int) -> None: ...
309
+
310
+ # -- schema ----------------------------------------------------------------
311
+ def set_schema(self, fields: Sequence[SchemaField]) -> None:
312
+ """Declare the collection's schema; replaces any previous one."""
313
+
314
+ def schema(self) -> list[SchemaField] | None: ...
315
+
316
+ # -- graph -----------------------------------------------------------------
317
+ # The native ``from`` parameter is a Python keyword — the stub spells it
318
+ # ``src``; pass edge endpoints positionally.
319
+ def link(self, src: Key, relation: str, to: Key) -> None: ...
320
+ def link_weighted(self, src: Key, relation: str, to: Key, weight: float) -> None: ...
321
+ def unlink(self, src: Key, relation: str, to: Key) -> bool: ...
322
+ def neighbors(self, src: Key, relation: str) -> list[Key]: ...
323
+ def in_neighbors(self, to: Key, relation: str) -> list[Key]: ...
324
+ def neighbors_weighted(self, src: Key, relation: str) -> list[tuple[Key, float]]: ...
325
+ def traverse(self, start: Key, relation: str, hops: int) -> list[Key]:
326
+ """BFS ``hops`` out over ``relation`` (cycle-safe)."""
327
+
328
+ # -- geo ---------------------------------------------------------------------
329
+ def geo_within_radius(self, field: str, lat: float, lon: float, radius_km: float) -> list[GeoHit]: ...
330
+ def geo_within_bbox(
331
+ self, field: str, min_lat: float, min_lon: float, max_lat: float, max_lon: float
332
+ ) -> list[GeoHit]: ...
333
+ def geo_nearest(self, field: str, lat: float, lon: float, k: int) -> list[GeoHit]: ...
334
+
335
+ # -- queries -------------------------------------------------------------------
336
+ def query(self) -> Query:
337
+ """Begin a fluent query over this collection (one execution per builder)."""
338
+
339
+ def close(self) -> None:
340
+ """Release the handle (idempotent); also runs on GC."""
341
+
342
+ class Query:
343
+ """The fluent query builder (mirrors the engine's ``QueryBuilder``).
344
+ Fluent setters return the same builder — ``q.filter(...).vector(...).run()``.
345
+ The terminal ops (``run`` and every aggregation) consume the builder;
346
+ ``close()`` abandons it without executing."""
347
+
348
+ def __enter__(self) -> Query: ...
349
+ def __exit__(self, *exc: object) -> None: ...
350
+
351
+ def filter(self, pred: Predicate) -> Query:
352
+ """Restrict to documents matching ``pred`` (multiple filters AND together)."""
353
+
354
+ def vector(self, field: str, query: Vector, k: int, metric: Metric = "cosine") -> Query:
355
+ """Add a vector source (``query`` an ``array('f')``) contributing up to ``k`` candidates."""
356
+
357
+ def text(self, field: str, query: str, k: int) -> Query:
358
+ """Add a BM25 text source contributing up to ``k`` candidates."""
359
+
360
+ def fuse_rrf(self, k: float) -> Query:
361
+ """Set the Reciprocal Rank Fusion constant (default 60; validated at execution)."""
362
+
363
+ def rerank_mmr(self, lambda_: float) -> Query:
364
+ """Rerank fused candidates for diversity (``lambda`` in ``[0, 1]``)."""
365
+
366
+ def approx(self) -> Query:
367
+ """Prefer index-backed approximate execution where available."""
368
+
369
+ def limit(self, n: int) -> Query: ...
370
+ def offset(self, n: int) -> Query: ...
371
+ def order_by(self, field: str, descending: bool = False) -> Query: ...
372
+ def select(self, fields: Sequence[str]) -> Query:
373
+ """Project results to the named top-level fields."""
374
+
375
+ def run(self) -> list[Row]:
376
+ """Execute; rows as :class:`Row` objects (score ``0.0`` for pure
377
+ filter/order queries). Consumes the builder."""
378
+
379
+ def count(self) -> int:
380
+ """Count matching documents (sources/ranking/limit ignored). Consumes the builder."""
381
+
382
+ def count_distinct(self, field: str) -> int: ...
383
+ def sum(self, field: str) -> float: ...
384
+ def avg(self, field: str) -> float | None: ...
385
+ def min(self, field: str) -> CorvidValue | None: ...
386
+ def max(self, field: str) -> CorvidValue | None: ...
387
+ def group_count(self, field: str) -> dict[str, int]:
388
+ """Group counts in the engine's ascending order. Group keys are the
389
+ engine's formatting (text bare, int/float type-tagged ``i:1``/``f:0.5``)."""
390
+
391
+ def group_sum(self, group_field: str, value_field: str) -> dict[str, float]: ...
392
+ def group_avg(self, group_field: str, value_field: str) -> dict[str, float]: ...
393
+
394
+ def close(self) -> None:
395
+ """Abandon the builder without executing."""
corvid/_native.pyd ADDED
Binary file
corvid/py.typed ADDED
File without changes
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.4
2
+ Name: corvid-python
3
+ Version: 0.3.3
4
+ Classifier: Development Status :: 3 - Alpha
5
+ Classifier: Intended Audience :: Developers
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Operating System :: MacOS :: MacOS X
8
+ Classifier: Operating System :: Microsoft :: Windows
9
+ Classifier: Operating System :: POSIX :: Linux
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Programming Language :: Rust
16
+ Classifier: Topic :: Database
17
+ Classifier: Topic :: Database :: Database Engines/Servers
18
+ License-File: LICENSE
19
+ Summary: Python binding for the corvid embedded database — the engine compiled in via pyo3, exposed as idiomatic OOP
20
+ Keywords: database,embedded,vector,search,ai
21
+ Author: corvid-db
22
+ License-Expression: MIT
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
25
+
26
+ # corvid-python
27
+
28
+ Python binding for [corvid](https://github.com/corvid-db/corvid) — an
29
+ embedded database with typed values, vector/text/hybrid search, graph
30
+ edges, geo, TTL, and schemas. The engine is compiled in (a Rust pyo3
31
+ crate pinned to an exact corvid release tag) and exposed as idiomatic
32
+ synchronous OOP: `Db`, `Collection`, a fluent `Query` builder, and
33
+ `field()` predicates. No SQL, no JSON, no serialization on the data
34
+ path — values map natively (see the value mapping below).
35
+
36
+ Its correctness story is the engine's **golden suite**: the same
37
+ 267-line fixture files the C ABI smoke harness runs are replayed
38
+ against this binding's public API on every CI run
39
+ (`tests/test_golden.py`).
40
+
41
+ ## Install
42
+
43
+ Pending first publish: the package is **not on PyPI yet** — everything
44
+ is prepared (maturin wheel config, one abi3 wheel per platform), and
45
+ publishing waits on the first release tag (docs/PLAN.md §6). Until
46
+ then, build from source — Python 3.11–3.14 (the floor is 3.11; CI
47
+ exercises 3.14/3.13/3.12/3.11 on every wheel platform), Rust ≥ 1.88,
48
+ and a C toolchain:
49
+
50
+ ```sh
51
+ pip install maturin
52
+ maturin develop --release # into the active venv
53
+ ```
54
+
55
+ The wheel is abi3 (cp311), so one wheel per platform covers every
56
+ Python ≥ 3.11. Planned platform matrix: `linux-x64` /
57
+ `linux-arm64` / `macos-arm64` / `windows-x64`.
58
+
59
+ ## Usage
60
+
61
+ ```python
62
+ from array import array
63
+
64
+ from corvid import Db, field
65
+
66
+ db = Db.open("app.redb") # or Db.open_memory()
67
+ docs = db.collection("docs")
68
+
69
+ docs.insert("p1", {
70
+ "title": "rust embedded database",
71
+ "kind": "doc",
72
+ "v": array("f", [1.0, 0.0]),
73
+ })
74
+
75
+ # hybrid retrieval: filter + vector + BM25, fused (RRF) + reranked (MMR)
76
+ rows = (
77
+ docs.query()
78
+ .filter(field("kind").eq("doc"))
79
+ .vector("v", array("f", [1.0, 0.0]), 10, "cosine")
80
+ .text("title", "rust database", 10)
81
+ .fuse_rrf(60)
82
+ .rerank_mmr(1.0)
83
+ .limit(5)
84
+ .run()
85
+ ) # [Row(key, score, document), ...]
86
+
87
+ for row in rows:
88
+ print(row.key, row.score, row.document["title"])
89
+
90
+ # predicates everywhere (queries and deletes)
91
+ docs.delete_where(field("kind").eq("draft"))
92
+
93
+ # scalar/compound/text/geo/vector indexes (incl. quantized + PQ + on-disk)
94
+ docs.create_vector_index("v", "cosine")
95
+
96
+ # TTL, graph, geo, schema, CAS, bulk writes, dump/backup/compact …
97
+ docs.close()
98
+ db.close()
99
+ ```
100
+
101
+ Every failure raises a native `CorvidError` with the engine error
102
+ `code` (the C ABI's frozen 1–19 table, exported as `ErrorCode`) and
103
+ the engine `message`. Type stubs ship in-package (`py.typed`) — the
104
+ public API is fully typed.
105
+
106
+ ## Examples
107
+
108
+ Six runnable programs in [`examples/`](examples/) — one per concept,
109
+ deterministic output, executed on every CI leg:
110
+
111
+ | File | What it shows |
112
+ | --- | --- |
113
+ | `quickstart.py` | open, insert 3 docs, kNN vector query, print |
114
+ | `hybrid.py` | filter + vector + BM25, RRF fusion, MMR rerank, limit |
115
+ | `vector_index.py` | in-memory / on-disk / binary-quantized HNSW vs exact, reopen |
116
+ | `text_search.py` | BM25 ranking, English + CJK (bigram segmentation) |
117
+ | `graph.py` | link/neighbors/traverse + the delete cascade |
118
+ | `geo.py` | radius / bbox / nearest-k over real coordinates (haversine km) |
119
+
120
+ ```sh
121
+ maturin develop && python examples/hybrid.py
122
+ ```
123
+
124
+ ## Value mapping
125
+
126
+ | Python | engine |
127
+ | --- | --- |
128
+ | `None`, `bool`, `str` | Null / Bool / Text |
129
+ | `int` | Int (full i64 — out-of-range ints raise code 12) |
130
+ | `float` | Float |
131
+ | `bytes` / `bytearray` | Bytes |
132
+ | `array('f')` | Vector (other typecodes are rejected) |
133
+ | `list` / `tuple` | Array |
134
+ | `dict` (str keys) | Map |
135
+
136
+ Reading back: Int → `int` (arbitrary precision — no ±2^53 boundary,
137
+ unlike the JS binding's number/BigInt split), Float → `float` with
138
+ **f64 bits preserved exactly** — NaN payloads, `-0.0`, and `±inf` all
139
+ round-trip bit-exactly (CPython floats are unboxed C doubles; pyo3
140
+ copies them by value — the fidelity corner where V8 canonicalizes NaN
141
+ payloads at the N-API boundary; Python has no such caveat). Vector →
142
+ `array('f')` (f32-exact both directions), Map → `dict` in the
143
+ engine's key order. Keys are `str` (UTF-8) or `bytes` (non-UTF-8 keys
144
+ come back as `bytes`).
145
+
146
+ Python marks the Int/Float distinction natively (`2` is an int, `2.0`
147
+ a float), so the mapping is a clean bijection — there is no
148
+ Int/Float collapse and no typed-float escape hatch (the JS binding
149
+ needs `CorvidFloat` for CAS/unique/group-key corners).
150
+
151
+ ## Surface manifest (docs/SURFACE.tsv)
152
+
153
+ Every construct of the engine's public surface (the radar-enforced list the
154
+ engine publishes as `scripts/bindings/surface.tsv` at each release tag) is
155
+ resolved in `docs/SURFACE.tsv`: the Python API exposing it plus the test that
156
+ proves it (golden fixture line references), or `N/A` + reason where the v1
157
+ binding deliberately does not expose it. `scripts/surface-gate.sh` fails CI
158
+ when a line is unresolved, a cell is empty, or the N/A count drifts from the
159
+ committed baseline — so an engine pin bump that changes the surface lands in
160
+ this gate, not in a user's bug report.
161
+
162
+ ## Development
163
+
164
+ ```sh
165
+ python -m venv .venv && source .venv/bin/activate
166
+ pip install maturin pytest
167
+ maturin develop # build the native extension
168
+ pytest tests # the golden suite (267 fixture lines)
169
+ cargo fmt --check # + cargo clippy --all-targets -- -D warnings
170
+ ```
171
+
172
+ The plan — architecture ruling (engine compiled in via pyo3 vs
173
+ Python-side ctypes/cffi FFI), the full OOP surface, the value
174
+ contract, and follow-up tasks — is [docs/PLAN.md](docs/PLAN.md).
175
+
176
+ ## License
177
+
178
+ MIT.
179
+
@@ -0,0 +1,9 @@
1
+ corvid/__init__.py,sha256=7GN4PgxgstZE-uBnVXX_zlauzknsFvLvttzhDnROf_A,1072
2
+ corvid/__init__.pyi,sha256=EqE3xLo_P-F_mD8KkmfYtUdgKqcVAxa4NBeHyJA0OAk,16667
3
+ corvid/_native.pyd,sha256=dtIM56wr3KCX_x1ZTrXB4EMFAZsKGWZIeQ0p0uNi7Yc,2947072
4
+ corvid/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ corvid_python-0.3.3.dist-info/METADATA,sha256=lBqjr3vCmVt00o9ysulzPwAWxyu-C2uvI9ywNhMBvlE,6771
6
+ corvid_python-0.3.3.dist-info/WHEEL,sha256=I37eTNpE2wbBLxuGAyG7p6lFvJQiIjnLN05rBA0wN9g,96
7
+ corvid_python-0.3.3.dist-info/licenses/LICENSE,sha256=l-CaqWP1EJRlSkbP5KHYC_y4J5WY5xuL1dup-QxrXnY,1083
8
+ corvid_python-0.3.3.dist-info/sboms/corvid-python.cyclonedx.json,sha256=0n592dUBcqtDg3cdga17Pxcv-XWa9D6Nm3f3qLplOdw,22961
9
+ corvid_python-0.3.3.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.15.0)
3
+ Root-Is-Purelib: false
4
+ Tag: cp311-abi3-win_amd64
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rocky
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.