A framework for storing and managing component and application data with persistence, validation, and audit trails for machine applications.
Table of Contents
- ✨ Features
- 🧠 Concepts & Overview
- ⚙️ Installation & Setup
- 🚀 Quickstart Tutorial
- 🛠 How-to Guides
- 📖 API Reference
- 🔍 Troubleshooting & FAQ
✨ Features
- Persistent storage with SQLite
- Automatic audit trails (who, when, what changed)
- Strong typing & validation via SQLModel
- Lifecycle hooks before/after insert, update, delete
- Soft delete with
deleted_atfields - REST API generation with Create, Read, Update, Delete + audit
- Health & monitoring endpoints (audit log, schema diagram)
- Batch operations for insert/delete
- Session management with smart reuse & transactions
- Bootstrap system for one-command setup
- CSV export/import for backups and migration
- Database backup/restore with integrity checking
🧠 Concepts & Overview
Vention Storage is a component-based persistence layer for machine apps:
- Database → SQLite database with managed sessions and transactions
- ModelAccessor → Strongly-typed Create, Read, Update, Delete interface for your SQLModel classes
- Hooks → Functions that run before/after Create, Read, Update, Delete operations
- AuditLog → Automatically records all data mutations
- Routers → Auto-generated FastAPI endpoints for Create, Read, Update, Delete + database management
⚙️ Installation & Setup
pip install vention-storage
Optional dependencies:
- sqlalchemy-schemadisplay and Graphviz → enable database schema visualization
🚀 Quickstart Tutorial
Define a model, bootstrap storage, and get full Create, Read, Update, Delete endpoints in minutes:
from datetime import datetime
from typing import Optional
from sqlmodel import Field, SQLModel
from fastapi import FastAPI
from storage.bootstrap import bootstrap
from storage.accessor import ModelAccessor
class User(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
email: str
deleted_at: Optional[datetime] = Field(default=None, index=True)
app = FastAPI()
user_accessor = ModelAccessor(User, "users")
bootstrap(
app,
accessors=[user_accessor],
database_url="sqlite:///./my_app.db",
create_tables=True
)
➡️ You now have Create, Read, Update, Delete, audit, backup, and CSV endpoints at /users and /db.
🛠 How-to Guides
Bootstrap Multiple Models
product_accessor = ModelAccessor(Product, "products")
bootstrap(
app,
accessors=[user_accessor, product_accessor],
database_url="sqlite:///./my_app.db",
create_tables=True,
max_records_per_model=100,
enable_db_router=True
)
Export to CSV
response = requests.get("http://localhost:8000/db/export.zip")
with open("backup.zip", "wb") as f:
f.write(response.content)
Backup & Restore
# Backup
r = requests.get("http://localhost:8000/db/backup.sqlite")
with open("backup.sqlite", "wb") as f: f.write(r.content)
# Restore
with open("backup.sqlite", "rb") as f:
files = {"file": ("backup.sqlite", f, "application/x-sqlite3")}
requests.post("http://localhost:8000/db/restore", files=files)
Use Lifecycle Hooks
@user_accessor.before_insert()
def validate_email(session, instance):
if "@" not in instance.email:
raise ValueError("Invalid email")
@user_accessor.after_insert()
def log_creation(session, instance):
print(f"User created: {instance.name}")
Query Audit Logs
from storage.auditor import AuditLog
from sqlmodel import select
with database.transaction() as session:
logs = session.exec(select(AuditLog).where(AuditLog.component == "users")).all()
📖 API Reference
bootstrap
def bootstrap(
app: FastAPI,
*,
accessors: Iterable[ModelAccessor[Any]],
database_url: Optional[str] = None,
create_tables: bool = True,
max_records_per_model: Optional[int] = 5,
enable_db_router: bool = True,
) -> None
ModelAccessor
ModelAccessor(model: Type[ModelType], component_name: str)
Read
get(id, include_deleted=False) -> Optional[ModelType]all(include_deleted=False) -> List[ModelType]
Write
insert(obj, actor="internal") -> ModelTypesave(obj, actor="internal") -> ModelTypedelete(id, actor="internal") -> boolrestore(id, actor="internal") -> bool
Batch
insert_many(objs, actor="internal") -> List[ModelType]delete_many(ids, actor="internal") -> int
Hooks
@accessor.before_insert()@accessor.after_insert()@accessor.before_update()@accessor.after_update()@accessor.before_delete()@accessor.after_delete()
Routers
build_crud_router(accessor, max_records=100) -> APIRouterbuild_db_router(audit_default_limit=100, audit_max_limit=1000) -> APIRouter
Database Helpers
database.set_database_url(url: str) -> Nonedatabase.get_engine() -> Enginedatabase.transaction() -> Iterator[Session]database.use_session(session: Optional[Session] = None) -> Iterator[Session]
AuditLog model
class AuditLog(SQLModel, table=True):
id: int
timestamp: datetime
component: str
record_id: int
operation: str
actor: str
before: Optional[Dict[str, Any]]
after: Optional[Dict[str, Any]]
🔍 Troubleshooting & FAQ
- Diagram endpoint fails → Ensure Graphviz + sqlalchemy-schemadisplay are installed.
- No audit actor shown → Provide X-User header in API requests.
- Soft delete not working → Your model must have a
deleted_atfield. - Restore fails → Ensure
integrity_check=Truepasses when restoring backups.