Skip to content

Commit 1533df3

Browse files
committed
Add initial Python client
1 parent 93b6f53 commit 1533df3

19 files changed

Lines changed: 1627 additions & 0 deletions

‎.gitignore‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,9 @@
77
.TODO
88
/databases
99
.env
10+
__pycache__/
11+
build/
12+
.venv/
13+
venv/
14+
.pytest_cache/
15+
*.egg-info/

‎client/python/README.md‎

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
# VortexDB Python Client
2+
3+
This is the official Python client for **VortexDB**, a vector database exposed via a gRPC API.
4+
5+
The client provides a thin, typed, Pythonic wrapper over the VortexDB gRPC interface, handling:
6+
- connection setup
7+
- authentication
8+
- request/response mapping
9+
- error translation
10+
- resource cleanup
11+
12+
The client is designed to be minimal, explicit, and easy to extend.
13+
14+
---
15+
16+
## Requirements
17+
18+
- Python 3.9+
19+
- A running VortexDB gRPC server
20+
21+
---
22+
23+
## Installation (local / development)
24+
25+
At the moment, the client lives inside the main VortexDB repository.
26+
27+
From `client/python`:
28+
29+
```bash
30+
python -m venv .venv
31+
source .venv/bin/activate
32+
33+
pip install -e .
34+
```
35+
36+
---
37+
38+
## Configuration and Authentication
39+
40+
The client communicates with VortexDB over gRPC and requires:
41+
42+
- gRPC endpoint (host:port)
43+
- API key (maps to `GRPC_ROOT_PASSWORD` on the server)
44+
45+
These can be provided either:
46+
47+
- explicitly when constructing the client, or
48+
- via environment variables
49+
50+
---
51+
52+
## Basic Usage
53+
54+
All examples and methods of using the client are in the `examples` directory, in `*_usage.py` files
55+
A full working example of the basic ways to use the client is available in:
56+
`examples/basic_usage.py`
57+
58+
### Context Manager Support
59+
60+
The client supports usage as a context manager, which automatically closes the underlying gRPC channel connection
61+
Example available in:
62+
```examples/context_manager_usage.py```
63+
64+
---
65+
66+
## Client API
67+
68+
### `VortexDB`
69+
70+
Main client class for interacting with the VortexDB gRPC server.
71+
72+
#### **Constructor**
73+
74+
```
75+
VortexDB(
76+
grpc_url: str | None = None,
77+
api_key: str | None = None,
78+
timeout: float | None = None,
79+
)
80+
```
81+
`grpc_url`: gRPC server address (`host:port`)
82+
`api_key`: API key for authentication
83+
`timeout`: per-request timeout in seconds
84+
85+
---
86+
87+
#### **Insert**
88+
89+
Insert a vector with an associated payload
90+
```
91+
insert(*, vector: DenseVector, payload: Payload) -> str
92+
```
93+
94+
Returns
95+
- `point_id` (UUID string)
96+
97+
Raises
98+
- `TypeError` if `vector` is not a `DenseVector`
99+
- gRPC-mapped errors (see Error Handling)
100+
101+
---
102+
103+
#### **Get**
104+
105+
Fetch a point by its ID
106+
```
107+
get(*, point_id: str) -> Point | None
108+
```
109+
110+
Returns
111+
- `Point` if found
112+
- `None` if the point does not exist
113+
114+
---
115+
116+
#### **Search**
117+
118+
Search for nearest neighbours to a query vector
119+
```
120+
search(
121+
*,
122+
vector: DenseVector,
123+
similarity: Similarity,
124+
limit: int,
125+
) -> list[str]
126+
```
127+
128+
Returns
129+
- List of `point_id` strings
130+
131+
Raises
132+
- `TypeError` if `vector` is not a `DenseVector`
133+
- `InvalidArgumentError` for invalid parameters
134+
135+
---
136+
137+
#### **Delete**
138+
139+
Delete a point by its ID
140+
```
141+
delete(*, point_id: str) -> None
142+
```
143+
144+
Raises
145+
- `NotFoundError` if the point does not exist
146+
147+
---
148+
149+
#### **Close**
150+
151+
Close the underlying gRPC channel
152+
```
153+
close() -> None
154+
```
155+
156+
---
157+
158+
## Models
159+
160+
The client exposes typed models that represent VortexDB concepts and handle
161+
validation and protobuf conversion internally
162+
163+
### `DenseVector`
164+
165+
```
166+
DenseVector(values: list[float] | tuple[float, ...])
167+
```
168+
- Validates numeric input
169+
- Normalizes values to `float`
170+
- Immutable (`frozen=True`)
171+
172+
---
173+
174+
### `Payload`
175+
176+
```
177+
Payload(content_type: ContentType, content:str)
178+
```
179+
Factory Helpers:
180+
- `Payload.text(content: str)`
181+
- `Payload.image(content: str)`
182+
183+
---
184+
185+
### `Point`
186+
187+
```
188+
Point(
189+
id: str,
190+
vector: DenseVector,
191+
payload: Payload,
192+
)
193+
```
194+
Additional `pretty()` method provided to properly format output
195+
All fields are directly accessible:
196+
- `point.id`
197+
- `point.vector`
198+
- `point.payload`
199+
200+
---
201+
202+
### `Similarity`
203+
204+
Enum representing distance functions:
205+
- `EUCLIDEAN`
206+
- `MANHATTAN`
207+
- `HAMMING`
208+
- `COSINE`
209+
210+
---
211+
212+
### `ContentType`
213+
214+
Enum representing payload type:
215+
- `TEXT`
216+
- `IMAGE`
217+
218+
---
219+
220+
## Error Handling
221+
222+
The client maps gRPC status codes to Python exceptions to provide a clean, Pythonic error-handling experience.
223+
All client exceptions inherit from `VortexDBError`
224+
225+
### Exception Mapping
226+
227+
| gRPC Status Code | Python Exception |
228+
| :--- | :--- |
229+
| `UNAUTHENTICATED` | `AuthenticationError` |
230+
| `NOT_FOUND` | `NotFoundError` |
231+
| `INVALID_ARGUMENT` | `InvalidArgumentError` |
232+
| `DEADLINE_EXCEEDED` | `TimeoutError` |
233+
| `UNAVAILABLE` | `ServiceUnavailableError` |
234+
| Any other error | `InternalServerError` |
235+
236+
---
237+
238+
## Testing
239+
240+
Tests are written using **pytest**
241+
Test coverage includes:
242+
- models
243+
- configuration loading
244+
- gRPC connection layer
245+
- client API
246+
247+
Tests live in the `tests/` directory
248+
249+
To run the tests: `pytest -v`
250+
251+
---
252+
253+
## Proto and gRPC Stubs
254+
255+
The gRPC interface is defined using a Protocol Buffers (`.proto`) file, from which Python gRPC stubs are generated.
256+
257+
### Proto
258+
259+
The `.proto` file is kept here for transparency and reproducibility reasons.
260+
261+
**Location:** `../../crates/grpc/proto/vector-db.proto`
262+
263+
264+
Even though the gRPC server is already running and exposes these methods, the client still needs the proto to:
265+
- Generate strongly-typed request / response classes
266+
- Generate the gRPC client stub (`VectorDBStub`)
267+
268+
---
269+
270+
### Generated Python stubs
271+
272+
**Location:** `vortexdb/grpc/`
273+
- `vector_db_pb2.py`
274+
- `vector_db_pb2_grpc.py`
275+
276+
These files are **auto-generated** from `vector-db.proto` and should not be edited manually.
277+
The client internally wraps this stub. End users never interact with it directly.
278+
279+
---
280+
281+
### Regenerating the stubs
282+
283+
Regeneration will be only required if the `.proto` file is changed. For example, if:
284+
- a new RPC is added
285+
- enums are updated
286+
287+
From the client's top-level directory, run:
288+
289+
```bash
290+
python -m grpc_tools.protoc \
291+
-I ../../crates/grpc/proto \
292+
--python_out=vortexdb/grpc \
293+
--grpc_python_out=vortexdb/grpc \
294+
../../crates/grpc/proto/vector-db.proto
295+
```
296+
297+
After running this:
298+
- `vector_db_pb2_grpc.py` and `vector_db_pb2.py` will be updated
299+
- No other client code should need changes
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from vortexdb import VortexDB
2+
from vortexdb import DenseVector, Payload, Similarity # from vortexdb.models
3+
4+
def main():
5+
# Initialize client
6+
db = VortexDB(
7+
grpc_url="localhost:50051",
8+
api_key="my-secret-password",
9+
)
10+
11+
# Insert a vector
12+
point_id = db.insert(
13+
vector=DenseVector([0.1, 0.2, 0.3]),
14+
payload=Payload.text("hello world"),
15+
)
16+
17+
# Get the point
18+
point = db.get(point_id=point_id)
19+
print("Fetched point:", point.pretty())
20+
21+
# Search
22+
results = db.search(
23+
vector=DenseVector([0.1, 0.2, 0.3]),
24+
similarity=Similarity.COSINE,
25+
limit=3,
26+
)
27+
print("Search results:", results)
28+
29+
# Delete
30+
db.delete(point_id=point_id)
31+
32+
# Close connection
33+
db.close()
34+
35+
if __name__ == "__main__":
36+
main()
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from vortexdb import VortexDB, DenseVector, Payload, Similarity
2+
3+
def main():
4+
with VortexDB(
5+
grpc_url="localhost:50051",
6+
api_key="my-secret-password",
7+
) as db:
8+
9+
# Insert a vector
10+
point_id = db.insert(
11+
vector=DenseVector([0.1, 0.2, 0.3]),
12+
payload=Payload.text("hello world"),
13+
)
14+
15+
# Get the point
16+
point = db.get(point_id=point_id)
17+
print("Fetched point:", point.pretty())
18+
19+
# Search
20+
results = db.search(
21+
vector=DenseVector([0.1, 0.2, 0.3]),
22+
similarity=Similarity.COSINE,
23+
limit=3,
24+
)
25+
print("Search results:", results)
26+
27+
# Delete
28+
db.delete(point_id=point_id)
29+
30+
# At this point, the gRPC channel is closed automatically
31+
print("Connection closed")
32+
33+
if __name__ == "__main__":
34+
main()

0 commit comments

Comments
 (0)