KenobiX
High-Performance Minimal Document Database • SQLite3-Powered • One Dependency (cattrs)
KenobiX is a document database with proper SQLite3 JSON optimization, delivering faster searches and faster updates compared to basic implementations.
Based on KenobiDB by Harrison Erd, enhanced with generated column indexes and optimized concurrency. ("KenobiX" = "Kenobi + indeX").
Why KenobiX?
from kenobix import KenobiX
db = KenobiX('app.db', indexed_fields=['user_id', 'email', 'status'])
users = db.search('email', 'alice@example.com')
db.update('user_id', 123, {'status': 'active'})
Features
- ODM Relationships - ForeignKey, RelatedSet, and ManyToMany support for managing relationships between models
- Multi-Collection Support - Organize data into separate collections (like MongoDB or SQL tables)
- Full ACID Transactions - Context manager API with savepoints for nested transactions
- Automatic Index Usage - Queries automatically use indexes when available, fall back to json_extract
- VIRTUAL Generated Columns - Minimal storage overhead (~7-20% depending on document complexity)
- Thread-Safe - No RLock on reads, SQLite handles concurrency with WAL mode
- MongoDB-like API - Familiar insert/search/update operations
- Optional ODM Layer - Type-safe dataclass-based models with per-model collections
- Cursor Pagination - Efficient pagination for large datasets
- Query Analysis - Built-in
explain() for optimization
- Zero Runtime Dependencies - Only Python stdlib (cattrs optional for ODM)
- Command-Line Interface - Inspect and dump databases from the terminal
- Built-in Web UI - Browser-based database explorer with search, dark mode, and customizable views
Command-Line Interface
KenobiX includes a CLI tool for database inspection and data export:
kenobix dump -d myapp.db
kenobix dump -d myapp.db -t users -o users.json
kenobix info -d myapp.db
kenobix info -d myapp.db -t users
kenobix info -d myapp.db -vv
Database specification (in order of precedence):
-d/--database option: kenobix dump -d myapp.db
- Environment variable:
KENOBIX_DATABASE=myapp.db kenobix dump
- Auto-detection: single
.db file in current directory
Options work before or after command:
kenobix -d myapp.db dump -t users
kenobix dump -d myapp.db -t users
Available commands:
dump | Export database contents as JSON |
info | Display database information |
Common options:
-d, --database | Path to database file |
-v, --verbose | Increase verbosity (repeatable: -v, -vv) |
-q, --quiet | Suppress non-essential output |
Dump-specific options:
-o, --output | Write to file instead of stdout |
-t, --table | Dump only specified table |
--compact | Output minified JSON |
Info-specific options:
-t, --table | Show detailed info with pseudo-schema for specified table |
Example: Single table info with pseudo-schema
$ kenobix info -d myapp.db -t users
Database: myapp.db
Table: users
Records: 1,234
Indexed fields: email, name
Pseudo-schema (inferred from 100 records):
active: boolean (95% present)
age: integer (80% present)
email: string [indexed]
metadata: object (15% present)
name: string [indexed]
tags: array (30% present)
Web UI
KenobiX includes an optional read-only web interface for exploring database contents in your browser.
Installation
pip install kenobix[webui]
Quick Start
kenobix serve -d myapp.db
Features
- Collection Browser - View all collections with document counts
- Tabular Display - Smart column selection with type-aware formatting
- Document Search - Search across all collections or within specific ones
- Dark Mode - Toggle between light and dark themes
- JSON Export - Copy documents to clipboard
- Customizable - Configure columns, labels, and formatters via
kenobix.toml
Configuration
Create a kenobix.toml file next to your database to customize the UI:
[webui]
theme = "dark"
per_page = 25
[webui.collections.users]
display_name = "User Accounts"
columns = ["_id", "name", "email", "created_at"]
labels = { name = "Full Name", created_at = "Joined" }
format = { created_at = "date" }
[webui.collections.orders]
format = { total = "currency:USD", status = "badge" }
CLI Options
kenobix serve [options]
Options:
-d, --database PATH Database file path
--host HOST Bind address (default: 127.0.0.1)
--port PORT Port number (default: 8000)
--no-browser Don't open browser automatically
--no-config Ignore kenobix.toml configuration
--validate-config Validate config and exit
See Web UI Guide and Configuration Reference for complete documentation.
Performance Benchmarks
Real-world measurements on a 10,000 document dataset:
| Exact search | 6.52ms | 0.009ms | 724x faster |
| Update 100 docs | 1.29s | 15.55ms | 83x faster |
| Range-like queries | 2.96ms | 0.52ms | 5.7x faster |
Document complexity matters: More complex documents see even greater benefits (up to 665x for very complex documents).
See benchmarks/ for detailed performance analysis.
ACID Compliance
KenobiX provides full ACID transaction support backed by SQLite's proven transaction engine:
- ✅ Atomicity - All-or-nothing execution with automatic rollback on errors
- ✅ Consistency - Data integrity maintained across all operations
- ✅ Isolation - Read Committed isolation prevents dirty reads
- ✅ Durability - Committed data persists through crashes (WAL mode)
25/25 comprehensive ACID tests passing (100%) - See ACID Compliance for proof.
with db.transaction():
db.update('account_id', 'A1', {'balance': 900})
db.update('account_id', 'A2', {'balance': 1100})
Documentation
Installation
pip install kenobix
pip install kenobix[odm]
pip install kenobix[webui]
pip install kenobix[all]
Or install from source:
git clone https://github.com/yourusername/kenobix
cd kenobix
pip install -e .
Quick Start
from kenobix import KenobiX
db = KenobiX('myapp.db', indexed_fields=['user_id', 'email', 'status'])
db.insert({'user_id': 1, 'email': 'alice@example.com', 'status': 'active'})
db.insert_many([
{'user_id': 2, 'email': 'bob@example.com', 'status': 'active'},
{'user_id': 3, 'email': 'carol@example.com', 'status': 'inactive'}
])
users = db.search('status', 'active')
user = db.search('email', 'alice@example.com')
tagged = db.search('tags', 'python')
results = db.search_optimized(status='active', user_id=1)
db.update('user_id', 1, {'last_login': '2025-01-15'})
result = db.all_cursor(limit=100)
documents = result['documents']
if result['has_more']:
next_page = db.all_cursor(after_id=result['next_cursor'], limit=100)
plan = db.explain('search', 'email', 'test@example.com')
print(plan)
with db.transaction():
db.insert({'user_id': 4, 'email': 'dave@example.com', 'balance': 1000})
db.update('user_id', 1, {'balance': 900})
db.update('user_id', 4, {'balance': 1100})
db.begin()
try:
db.insert({'user_id': 5, 'email': 'eve@example.com'})
db.commit()
except Exception:
db.rollback()
raise
with db.transaction():
db.insert({'status': 'processing'})
try:
with db.transaction():
db.insert({'status': 'temporary'})
raise ValueError("Rollback nested only")
except ValueError:
pass
db.insert({'status': 'completed'})
Object Document Mapper (ODM)
KenobiX includes an optional ODM layer for type-safe, Pythonic document operations using dataclasses.
Installation
pip install kenobix[odm]
Usage
from dataclasses import dataclass
from typing import List
from kenobix import KenobiX, Document
@dataclass
class User(Document):
name: str
email: str
age: int
active: bool = True
@dataclass
class Post(Document):
title: str
content: str
author_id: int
tags: List[str]
published: bool = False
db = KenobiX('app.db', indexed_fields=['email', 'name', 'author_id'])
Document.set_database(db)
user = User(name="Alice", email="alice@example.com", age=30)
user.save()
alice = User.get(email="alice@example.com")
users = User.filter(age=30)
all_users = User.all(limit=100)
alice.age = 31
alice.save()
alice.delete()
User.insert_many([user1, user2, user3])
User.delete_many(active=False)
total = User.count()
active_count = User.count(active=True)
ODM Features
- Type Safety - Full type hints with autocomplete support
- Automatic Serialization - Uses cattrs for nested structures
- Indexed Queries - Automatically uses KenobiX indexes
- Bulk Operations - Efficient insert_many, delete_many
- Familiar API - Similar to MongoDB ODMs (ODMantic, MongoEngine)
- Zero Boilerplate - Just use @dataclass decorator
See examples/odm_example.py for complete examples.
ODM Transaction Support
The ODM layer fully supports transactions:
with User.transaction():
alice = User(name="Alice", email="alice@example.com", age=30)
bob = User(name="Bob", email="bob@example.com", age=25)
alice.save()
bob.save()
User.begin()
try:
user = User.get(email="alice@example.com")
user.age = 31
user.save()
User.commit()
except Exception:
User.rollback()
raise
See docs/transactions.md for complete transaction documentation.
Multi-Collection Support
KenobiX supports organizing data into multiple collections (similar to MongoDB collections or SQL tables). Each collection has its own table, indexes, and schema within a single database file.
Quick Example
from kenobix import KenobiX
db = KenobiX('myapp.db')
users = db.collection('users', indexed_fields=['user_id', 'email'])
orders = db.collection('orders', indexed_fields=['order_id', 'user_id'])
products = db.collection('products', indexed_fields=['product_id', 'category'])
db['users'].insert({'user_id': 1, 'name': 'Alice', 'email': 'alice@example.com'})
db['orders'].insert({'order_id': 101, 'user_id': 1, 'amount': 99.99})
users = db['users'].all(limit=100)
orders = db['orders'].all(limit=100)
with db.transaction():
db['users'].insert({'user_id': 2, 'name': 'Bob'})
db['orders'].insert({'order_id': 102, 'user_id': 2, 'amount': 149.99})
Benefits
- Better Organization: Each entity type in its own collection
- Improved Performance: Smaller tables with focused indexes
- Complete Isolation: No mixing of different document types
- Independent Indexes: Each collection can have different indexed fields
- Type Safety: Cleaner queries without type field filtering
ODM with Collections
The ODM layer automatically uses collections:
from dataclasses import dataclass
from kenobix.odm import Document
@dataclass
class User(Document):
class Meta:
collection_name = "users"
indexed_fields = ["user_id", "email"]
user_id: int
name: str
email: str
@dataclass
class Order(Document):
class Meta:
collection_name = "orders"
indexed_fields = ["order_id", "user_id"]
order_id: int
user_id: int
amount: float
user = User(user_id=1, name='Alice', email='alice@example.com')
user.save()
order = Order(order_id=101, user_id=1, amount=99.99)
order.save()
See docs/collections.md for complete documentation and examples/collections_example.py for real-world examples.
ODM Relationships
KenobiX provides transparent relationship support for modeling connections between documents with ForeignKey, RelatedSet, and ManyToMany relationships.
Quick Example
from dataclasses import dataclass, field
from kenobix import KenobiX, ForeignKey, RelatedSet, ManyToMany
from kenobix.odm import Document
db = KenobiX('myapp.db')
Document.set_database(db)
@dataclass
class User(Document):
class Meta:
collection_name = "users"
indexed_fields = ["user_id"]
user_id: int
name: str
@dataclass
class Order(Document):
class Meta:
collection_name = "orders"
indexed_fields = ["order_id", "user_id"]
order_id: int
user_id: int
amount: float
user: ForeignKey[User] = field(
default=ForeignKey("user_id", User),
init=False,
repr=False,
compare=False
)
User.orders = RelatedSet(Order, "user_id")
user = User(user_id=1, name="Alice")
user.save()
order = Order(order_id=101, user_id=1, amount=99.99)
order.save()
order = Order.get(order_id=101)
print(order.user.name)
user = User.get(user_id=1)
for order in user.orders:
print(f"Order {order.order_id}: ${order.amount}")
Many-to-Many Relationships
@dataclass
class Student(Document):
class Meta:
collection_name = "students"
indexed_fields = ["student_id"]
student_id: int
name: str
@dataclass
class Course(Document):
class Meta:
collection_name = "courses"
indexed_fields = ["course_id"]
course_id: int
title: str
Student.courses = ManyToMany(
Course,
through="enrollments",
local_field="student_id",
remote_field="course_id"
)
Course.students = ManyToMany(
Student,
through="enrollments",
local_field="course_id",
remote_field="student_id"
)
student = Student(student_id=1, name="Alice")
student.save()
math = Course(course_id=101, title="Mathematics")
math.save()
student.courses.add(math)
print(f"{student.name} is enrolled in {len(student.courses)} courses")
print(f"{math.title} has {len(math.students)} students")
Relationship Features
- ForeignKey - Many-to-one relationships with lazy loading and caching
- RelatedSet - One-to-many reverse relationships with query/filter methods
- ManyToMany - Many-to-many relationships through automatic junction tables
- Bidirectional Navigation - Navigate relationships from both sides
- Transaction Support - All relationship operations are transaction-aware
- Type Safety - Full generic type hints for IDE autocomplete
See docs/relationships.md for complete documentation and examples/relationships_example.py for 26 detailed examples.
When to Use KenobiX
Perfect For:
- ✅ Applications with 1,000 - 1,000,000+ documents
- ✅ Frequent searches and updates
- ✅ Known query patterns (can index those fields)
- ✅ Complex document structures
- ✅ Need sub-millisecond query times
- ✅ Prototypes that need to scale
Consider Alternatives For:
- ⚠️ Pure insert-only workloads (indexing overhead not worth it)
- ⚠️ < 100 documents (overhead not justified)
- ⚠️ Truly massive scale (> 10M documents - use PostgreSQL/MongoDB)
When to Use Transactions
Use Transactions For:
- ✅ Financial operations - Balance transfers, payments, refunds
- ✅ Multi-step updates - Ensuring related data stays consistent
- ✅ Batch operations - 50-100x performance boost for bulk inserts
- ✅ Business logic invariants - Total inventory, account balances, quotas
- ✅ Error recovery - Automatic rollback on exceptions
Auto-commit is Fine For:
- ⚠️ Single document inserts/updates (no performance benefit)
- ⚠️ Independent operations (no consistency requirements)
- ⚠️ Read-only queries (no transaction needed)
Performance Note: Transactions can improve bulk insert performance by 50-100x by deferring commit until the end.
for doc in documents:
db.insert(doc)
with db.transaction():
for doc in documents:
db.insert(doc)
Index Selection Strategy
Rule of thumb: Index your 3-6 most frequently queried fields.
db = KenobiX('app.db', indexed_fields=[
'user_id',
'email',
'status',
'created_at',
])
API Documentation
Initialization
KenobiX(file, indexed_fields=None)
file: Path to SQLite database (created if doesn't exist)
indexed_fields: List of document fields to create indexes for
CRUD Operations
db.insert(document)
db.insert_many(documents)
db.search(key, value, limit=100)
db.search_optimized(**filters)
db.update(key, value, new_dict)
db.remove(key, value)
db.purge()
db.all(limit=100, offset=0)
Transaction Operations
with db.transaction():
db.insert(...)
db.update(...)
db.begin()
db.commit()
db.rollback()
sp = db.savepoint()
db.rollback_to(sp)
db.release_savepoint(sp)
Advanced Operations
db.search_pattern(key, regex)
db.find_any(key, value_list)
db.find_all(key, value_list)
db.all_cursor(after_id, limit)
db.explain(operation, *args)
db.stats()
db.get_indexed_fields()
Performance Tips
- Index your query fields - Biggest performance win (15-665x speedup)
- Use transactions for bulk operations - 50-100x faster for batch inserts
- Use
search_optimized() for multi-field queries - More efficient than chaining
- Use cursor pagination for large datasets - Avoids O(n) OFFSET cost
- Batch inserts with
insert_many() - Much faster than individual inserts
- Check query plans with
explain() - Verify indexes are being used
Migration from KenobiDB
KenobiX is API-compatible with KenobiDB. Simply:
from kenobi import KenobiDB
db = KenobiDB('app.db')
from kenobix import KenobiX
db = KenobiX('app.db', indexed_fields=['your', 'query', 'fields'])
Existing databases work without modification. Add indexed_fields to unlock performance gains.
Requirements
- Python 3.11+
- SQLite 3.31.0+ (for generated columns)
Testing
pytest tests/
pytest --cov=kenobix tests/
python3 tests/test_acid_compliance.py
python3 tests/test_transactions.py
python3 tests/test_concurrency.py
python3 scripts/check_concurrency.py
python benchmarks/benchmark_scale.py
python benchmarks/benchmark_complexity.py
Test Coverage: KenobiX maintains 90%+ test coverage across:
- Core database operations (kenobix.py: 88%+)
- ODM layer (odm.py: 93%+)
- 217 tests covering CRUD, indexing, concurrency, transactions, ODM, and relationships
ACID Compliance: 25/25 comprehensive tests passing (100%):
- 6 atomicity tests (all-or-nothing execution)
- 5 consistency tests (data integrity invariants)
- 5 isolation tests (concurrent transaction safety)
- 7 durability tests (crash recovery simulation)
- 2 combined tests (real-world scenarios)
Concurrency Tests: Comprehensive multiprocessing tests verify:
- Multiple readers run in parallel without blocking
- Writers properly serialize via write lock
- Readers not blocked by writers (WAL mode benefit)
- Data integrity under concurrent access
- Race condition detection
See Concurrency Tests for details.
Benchmarking
Comprehensive benchmarks included:
python benchmarks/benchmark_scale.py --sizes "1000,10000,100000"
python benchmarks/benchmark_complexity.py
python benchmarks/benchmark_odm.py --size 10000
ODM Performance
The ODM layer adds overhead for deserialization (cattrs). Results based on robust benchmarks (5 iterations, trimmed mean):
- Write operations: ~7-15% slower (very acceptable)
- Read operations: ~100-900% slower (cattrs deserialization cost)
- Count operations: ~17% slower (minimal deserialization)
- Trade-off: Type safety + developer productivity vs 2-10x slower reads
Key insight: Write overhead is minimal. Read overhead is significant due to cattrs deserialization, not SQL queries (both use identical indexes).
For read-heavy workloads requiring maximum performance, use raw operations. For applications needing type safety and developer productivity, the ODM overhead is acceptable. You can also use a hybrid approach: ODM for most code, raw for hot paths.
Credits
KenobiX is based on KenobiDB by Harrison Erd.
The original KenobiDB provided an excellent foundation with its MongoDB-like API and clean SQLite3 integration. KenobiX builds on this work by adding:
- Full ACID transaction support with context manager API
- Generated column indexes for 15-665x performance improvements
- Optimized concurrency model (no RLock for reads)
- Optional ODM layer with dataclass support
- Cursor-based pagination
- Query plan analysis tools
- Comprehensive benchmark and test suites
Thank you to Harrison Erd for creating KenobiDB!
License
BSD-3-Clause License (same as original KenobiDB)
Copyright (c) 2025 KenobiX Contributors
Original KenobiDB Copyright (c) Harrison Erd
See LICENSE file for details.
Contributing
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
Links
Changelog
See CHANGES.md for the complete changelog.