ekoDB Python Client
High-performance Python client for ekoDB, built with Rust for speed and safety.
This package wraps the ekodb_client Rust library using PyO3 to provide a
native Python interface.
Features
- ✅ Fast: Built with Rust, leveraging the same client library as the Rust SDK
- ✅ Type-safe: Strong typing with Python type hints
- ✅ Async/await: Full async support using Python's asyncio
- ✅ Easy to use: Pythonic API that feels natural
- ✅ Complete: All ekoDB features supported
- ✅ Query Builder - Fluent API for complex queries with operators, sorting, and pagination
- ✅ Search - Full-text search, fuzzy search, and field-specific search with scoring
- ✅ Schema Management - Define and enforce data schemas with validation
- ✅ Join Operations - Single and multi-collection joins with queries
- ✅ Rate limiting with automatic retry (429, 503, network errors)
- ✅ Rate limit tracking (
X-RateLimit-*headers) - ✅ Configurable retry behavior
- ✅ Retry-After header support
Installation
pip install ekodb-client
Or install from source:
cd ekodb-py
pip install maturin
maturin develop
Quick Start
import asyncio
from ekodb_client import Client, RateLimitError
async def main():
# Create client with configuration
client = Client.new(
"http://localhost:8080",
"your-api-key",
should_retry=True, # Enable automatic retries (default: True)
max_retries=3, # Maximum retry attempts (default: 3)
timeout_secs=30 # Request timeout in seconds (default: 30)
)
try:
# Insert a document
record = await client.insert("users", {
"name": "John Doe",
"age": 30,
"email": "john@example.com",
"active": True
})
print(f"Inserted: {record['id']}")
# Find by ID
user = await client.find_by_id("users", record["id"])
print(f"Found: {user}")
# Find with query
results = await client.find("users", limit=10)
print(f"Found {len(results)} users")
# Update
updated = await client.update("users", record["id"], {
"age": 31
})
print(f"Updated: {updated}")
# Delete
await client.delete("users", record["id"])
print("Deleted")
except RateLimitError as e:
print(f"Rate limited! Retry after {e.retry_after_secs} seconds")
asyncio.run(main())
Usage Examples
Query Builder
from ekodb_client import Client, QueryBuilder
async def main():
client = Client.new("http://localhost:8080", "your-api-key")
# Simple query with operators
query = QueryBuilder() \
.eq("status", "active") \
.gte("age", 18) \
.lt("age", 65) \
.limit(10) \
.build()
results = await client.find("users", query)
# Complex query with sorting and pagination
query = QueryBuilder() \
.in_array("status", ["active", "pending"]) \
.contains("email", "@example.com") \
.sort_desc("created_at") \
.skip(20) \
.limit(10) \
.build()
results = await client.find("users", query)
Search Operations
# Basic text search
search_query = {
"query": "programming",
"min_score": 0.1,
"limit": 10
}
results = await client.search("articles", search_query)
for result in results["results"]:
print(f"Score: {result['score']:.4f} - {result['record']['title']}")
# Search with field weights
search_query = {
"query": "rust database",
"fields": ["title", "description"],
"weights": {"title": 2.0},
"limit": 5
}
results = await client.search("articles", search_query)
Schema Management
# Create a collection with schema
schema = {
"fields": {
"name": {
"field_type": "String",
"required": True,
"regex": "^[a-zA-Z ]+$"
},
"email": {
"field_type": "String",
"required": True,
"unique": True
},
"age": {
"field_type": "Integer",
"min": 0,
"max": 150
}
}
}
await client.create_collection("users", schema)
# Get collection schema
schema = await client.get_schema("users")
Join Operations
# Single collection join
query = {
"join": {
"collections": ["departments"],
"local_field": "department_id",
"foreign_field": "id",
"as_field": "department"
},
"limit": 10
}
results = await client.find("users", query)
# Multi-collection join
query = {
"join": [
{
"collections": ["departments"],
"local_field": "department_id",
"foreign_field": "id",
"as_field": "department"
},
{
"collections": ["profiles"],
"local_field": "id",
"foreign_field": "id",
"as_field": "profile"
}
],
"limit": 10
}
results = await client.find("users", query)
API Reference
Client
Client.new(base_url: str, api_key: str, should_retry: bool = True, max_retries: int = 3, timeout_secs: int = 30) -> Client
Create a new ekoDB client.
Parameters:
base_url: The base URL of the ekoDB serverapi_key: Your API keyshould_retry: Enable automatic retries (default: True)max_retries: Maximum number of retry attempts (default: 3)timeout_secs: Request timeout in seconds (default: 30)
Returns:
- A new
Clientinstance
RateLimitInfo
Rate limit information is automatically tracked and logged by the client. The
client will automatically retry on rate limit errors using the server's
Retry-After header.
Properties
limit: int- Maximum requests allowed per windowremaining: int- Requests remaining in current windowreset: int- Unix timestamp when the rate limit resets
Methods
is_near_limit() -> bool- Check if approaching rate limit (<10% remaining)is_exceeded() -> bool- Check if the rate limit has been exceededremaining_percentage() -> float- Get the percentage of requests remaining
RateLimitError
Exception raised when rate limit is exceeded (if retries are disabled or exhausted).
Properties
retry_after_secs: int- Number of seconds to wait before retrying
await client.insert(collection: str, record: dict) -> dict
Insert a document into a collection.
Parameters:
collection: The collection namerecord: A dictionary representing the document
Returns:
- The inserted document with ID
await client.find_by_id(collection: str, id: str) -> dict
Find a document by ID.
Parameters:
collection: The collection nameid: The document ID
Returns:
- The found document
await client.find_by_id_with_projection(collection: str, id: str, select_fields: Optional[list] = None, exclude_fields: Optional[list] = None) -> dict
Find a document by ID, returning only the projected fields.
Parameters:
collection: The collection nameid: The document IDselect_fields: Optional list of fields to includeexclude_fields: Optional list of fields to exclude
Returns:
- The found document with field projection applied
await client.find(collection: str, limit: Optional[int] = None) -> List[dict]
Find documents in a collection.
Parameters:
collection: The collection namelimit: Optional limit on number of results
Returns:
- List of matching documents
await client.update(collection: str, id: str, updates: dict) -> dict
Update a document.
Parameters:
collection: The collection nameid: The document IDupdates: Dictionary of fields to update
Returns:
- The updated document
await client.delete(collection: str, id: str) -> None
Delete a document.
Parameters:
collection: The collection nameid: The document ID
await client.list_collections() -> List[str]
List all collections.
Returns:
- List of collection names
await client.delete_collection(collection: str) -> None
Delete a collection.
Parameters:
collection: The collection name to delete
await client.search(collection: str, query: dict) -> dict
Perform full-text search on a collection.
Parameters:
collection: The collection namequery: Search query dictionary with fields likequery,fields,weights,min_score,limit
Returns:
- Search results with scores and matched records
await client.create_collection(collection: str, schema: dict) -> None
Create a collection with a schema.
Parameters:
collection: The collection nameschema: Schema definition dictionary
await client.get_schema(collection: str) -> dict
Get the schema for a collection.
Parameters:
collection: The collection name
Returns:
- Schema definition dictionary
await client.get_collection(collection: str) -> dict
Get collection metadata including schema.
Parameters:
collection: The collection name
Returns:
- Collection metadata dictionary
await client.collection_exists(collection: str) -> bool
Check if a collection exists.
Parameters:
collection: The collection name
Returns:
- True if the collection exists, False otherwise
await client.count_documents(collection: str) -> int
Count documents in a collection.
Parameters:
collection: The collection name
Returns:
- Number of documents in the collection
Chat Models
await client.get_chat_models() -> dict
Get all available chat models organized by provider.
Returns:
- Dictionary mapping provider names (
openai,anthropic,perplexity,gemini) to lists of model names, plus aproviderskey: a dictionary of per-provider status (statusone ofok,not_configured,auth_failed,permission_denied,billing,rate_limited,unavailable,unreachable,request_error;verified;http_status;message;model_count) so a rejected key is distinguishable from a missing one
await client.get_chat_model(provider: str) -> list
Get models for a specific provider.
Parameters:
provider: The provider name (e.g., "openai", "anthropic")
Returns:
- List of model names for the provider
User Functions
await client.save_user_function(user_function: dict) -> str
Create a new user function.
Parameters:
user_function: Dictionary containing label, name, parameters, functions, etc.
Returns:
- The ID of the created user function
await client.get_user_function(label: str) -> dict
Get a user function by its label.
Parameters:
label: The user function label
Returns:
- User function definition dictionary
await client.list_user_functions(tags: Optional[list] = None) -> list
List all user functions, optionally filtered by tags.
Parameters:
tags: Optional list of tags to filter by
Returns:
- List of user function dictionaries
await client.update_user_function(label: str, user_function: dict) -> None
Update an existing user function.
Parameters:
label: The user function labeluser_function: Updated user function definition
await client.delete_user_function(label: str) -> None
Delete a user function by its label.
Parameters:
label: The user function label
Key-Value
await client.kv_set(key: str, value: dict, ttl: Optional[str] = None) -> None
Set a key-value pair, optionally with an expiration.
Parameters:
key: The keyvalue: A dictionary value to storettl: Optional expiration (e.g."60s")
await client.kv_get(key: str) -> Optional[dict]
Get a value by key.
Parameters:
key: The key
Returns:
- The stored value, or
Noneif the key does not exist
await client.kv_delete(key: str) -> None
Delete a key.
Parameters:
key: The key
await client.kv_clear() -> None
Clear the entire KV store (all keys in the namespace).
Collections
await client.list_user_collections() -> List[str]
List collections, excluding internal chat/system collections.
Returns:
- List of user collection names
Transactions
Buffered, read-your-writes transactions. Operations issued with a
transaction_id kwarg (accepted on insert, find, find_by_id, update,
delete) are staged and applied atomically at commit.
await client.begin_transaction(isolation_level: str = "ReadCommitted") -> str
Start a transaction and return its id.
await client.commit_transaction(transaction_id: str) -> None
Apply the staged writes. May raise a retryable conflict (HTTP 409).
await client.rollback_transaction(transaction_id: str) -> None
Discard the staged writes.
await client.create_savepoint(transaction_id: str, name: str) -> None
Create a savepoint within the transaction.
await client.rollback_to_savepoint(transaction_id: str, name: str) -> None
Roll the transaction back to a savepoint.
await client.release_savepoint(transaction_id: str, name: str) -> None
Release (forget) a savepoint.
Examples
See the examples directory for complete working examples:
client_simple_crud.py- Basic CRUD operationsclient_query_builder.py- Complex queries with QueryBuilderclient_search.py- Full-text search operationsclient_schema.py- Schema managementclient_joins.py- Join operationsclient_batch_operations.py- Batch operationsclient_kv_operations.py- Key-value operationsclient_chat_models.py- Chat models APIclient_user_functions.py- User functions API- And more...
Development
Building
# Install maturin
pip install maturin
# Build and install in development mode
maturin develop
# Build release wheel
maturin build --release
Testing
# Run Python tests
pytest
# Run with coverage
pytest --cov=ekodb
Goals, Tasks, and Agents
import asyncio
from ekodb_client import Client
async def main():
client = Client.new("http://localhost:8080", "your-api-key")
# Goals
goal = await client.goal_create({"title": "Migrate data", "status": "active"})
goals = await client.goal_list()
await client.goal_complete("goal-id", {"summary": "Done"})
# Tasks
task = await client.task_create({"title": "Backup", "schedule": "0 0 * * *"})
await client.task_start("task-id")
# Agents
agent = await client.agent_create({"name": "processor", "model": "gpt-4.1"})
asyncio.run(main())
Schedules
# Create a schedule
sched = await client.create_schedule({"name": "nightly", "cron": "0 2 * * *"})
# Pause a schedule
await client.pause_schedule("sched-id")
WebSocket Operations
ws = await client.websocket("ws://localhost:8080")
# Full CRUD over WebSocket (14 methods)
result = await ws.ws_insert("users", {"name": "Alice", "email": "a@b.com"})
results = await ws.ws_query("users", filter={"field": "status", "operator": "Eq", "value": "active"})
user = await ws.ws_find_by_id("users", "record-id")
await ws.ws_update("users", "record-id", {"name": "Updated"})
await ws.ws_delete("users", "record-id")
# Batch operations
await ws.ws_batch_insert("logs", [{"msg": "a"}, {"msg": "b"}])
await ws.ws_batch_update("logs", [("id1", {"msg": "x"}), ("id2", {"msg": "y"})])
await ws.ws_batch_delete("logs", ["id1", "id2"])
# Search + collection management
hits = await ws.ws_text_search("docs", "python async", limit=10)
collections = await ws.ws_list_collections()
await ws.ws_create_collection("new_coll")
# Atomic field actions
await ws.ws_update_with_action("counters", "views", "increment", "count", 1)
# Subscriptions + chat
await ws.ws_unsubscribe("users")
await ws.cancel_chat("chat-id")
WebSocket Chat Streaming
stream = await ws.chat_send(chat_id, "What is the capital of France?")
async for event in stream:
if event.type == "chunk":
print(event.content, end="")
elif event.type == "end":
print(f"\nDone (context: {event.context_window} tokens)")
elif event.type == "tool_call":
print(f"[Tool] {event.tool_name}")
await ws.send_tool_result(
chat_id, event.call_id, True, {"result": "done"}
)
elif event.type == "error":
print(f"Error: {event.error}")
License
MIT
Links
Metadata
Release files for ekodb-client 0.26.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ekodb_client-0.26.2.tar.gz | 264.3 kB | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ekodb_client-0.26.2-cp38-abi3-manylinux_2_28_aarch64.whl | CPython 3.8 | abi3 | Linux glibc 2.28+ ARM64 | Details |
| ekodb_client-0.26.2-cp38-abi3-macosx_11_0_arm64.whl | CPython 3.8 | abi3 | macOS 11.0+ ARM64 | Details |
Total release size: 13.2 MB
Release files / ekodb_client-0.26.2.tar.gz
| Download URL | ekodb_client-0.26.2.tar.gz |
|---|---|
| Size | 264.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
82f873d84b3d86588ea5b14c9d0ba7a9effbdefa7a2300bf3db216280bef3dc3
|
|
BLAKE2b-256 checksum How to use checksums |
2a2b0a1383b90e5f469c05764e603624657219087696858383fa5def92cb2c0c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.3
|
Release files / ekodb_client-0.26.2-cp38-abi3-manylinux_2_28_aarch64.whl
| Download URL | ekodb_client-0.26.2-cp38-abi3-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 6.8 MB |
| Tags | CPython 3.8 Linux glibc 2.28+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
84ee6655a3ce3675288d8c294f367eda47ee54fb7c9fb46db342896d24533ec4
|
|
BLAKE2b-256 checksum How to use checksums |
8169cd98e1fab97af19ec66c2674b2532b6d2244c73776aa8deff9788b327a40
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.3
|
Release files / ekodb_client-0.26.2-cp38-abi3-macosx_11_0_arm64.whl
| Download URL | ekodb_client-0.26.2-cp38-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 6.1 MB |
| Tags | CPython 3.8 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
8d01ae1c335d0784eeef6cc5b680682ae33b738e96f9d3229c61a31db3d90059
|
|
BLAKE2b-256 checksum How to use checksums |
92db804cd030505356780c40848cef89021c3bafd25291c21333028462cd67f6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.3
|