vention-storage 0.5.1__tar.gz → 0.5.4__tar.gz
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.
- vention_storage-0.5.4/PKG-INFO +318 -0
- vention_storage-0.5.4/README.md +302 -0
- vention_storage-0.5.4/pyproject.toml +25 -0
- vention_storage-0.5.1/PKG-INFO +0 -509
- vention_storage-0.5.1/README.md +0 -491
- vention_storage-0.5.1/pyproject.toml +0 -34
- {vention_storage-0.5.1 → vention_storage-0.5.4}/src/storage/__init__.py +0 -0
- {vention_storage-0.5.1 → vention_storage-0.5.4}/src/storage/accessor.py +0 -0
- {vention_storage-0.5.1 → vention_storage-0.5.4}/src/storage/auditor.py +0 -0
- {vention_storage-0.5.1 → vention_storage-0.5.4}/src/storage/bootstrap.py +0 -0
- {vention_storage-0.5.1 → vention_storage-0.5.4}/src/storage/database.py +0 -0
- {vention_storage-0.5.1 → vention_storage-0.5.4}/src/storage/hooks.py +0 -0
- {vention_storage-0.5.1 → vention_storage-0.5.4}/src/storage/io_helpers.py +0 -0
- {vention_storage-0.5.1 → vention_storage-0.5.4}/src/storage/router_database.py +0 -0
- {vention_storage-0.5.1 → vention_storage-0.5.4}/src/storage/router_model.py +0 -0
- {vention_storage-0.5.1 → vention_storage-0.5.4}/src/storage/utils.py +0 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: vention-storage
|
|
3
|
+
Version: 0.5.4
|
|
4
|
+
Summary: A framework for storing and managing component and application data for machine apps.
|
|
5
|
+
License: Proprietary
|
|
6
|
+
Author: VentionCo
|
|
7
|
+
Requires-Python: >=3.10,<3.11
|
|
8
|
+
Classifier: License :: Other/Proprietary License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Requires-Dist: fastapi (==0.121.1)
|
|
12
|
+
Requires-Dist: python-multipart (>=0.0.20,<0.0.21)
|
|
13
|
+
Requires-Dist: sqlmodel (==0.0.27)
|
|
14
|
+
Requires-Dist: uvicorn (>=0.35.0,<0.36.0)
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Vention Storage
|
|
18
|
+
|
|
19
|
+
A framework for storing and managing component and application data with persistence, validation, and audit trails for machine applications.
|
|
20
|
+
|
|
21
|
+
## Table of Contents
|
|
22
|
+
|
|
23
|
+
- [✨ Features](#-features)
|
|
24
|
+
- [🧠 Concepts & Overview](#-concepts--overview)
|
|
25
|
+
- [⚙️ Installation & Setup](#️-installation--setup)
|
|
26
|
+
- [🚀 Quickstart Tutorial](#-quickstart-tutorial)
|
|
27
|
+
- [🛠 How-to Guides](#-how-to-guides)
|
|
28
|
+
- [📖 API Reference](#-api-reference)
|
|
29
|
+
- [🔍 Troubleshooting & FAQ](#-troubleshooting--faq)
|
|
30
|
+
|
|
31
|
+
## ✨ Features
|
|
32
|
+
|
|
33
|
+
- Persistent storage with SQLite
|
|
34
|
+
- Automatic audit trails (who, when, what changed)
|
|
35
|
+
- Strong typing & validation via SQLModel
|
|
36
|
+
- Lifecycle hooks before/after insert, update, delete
|
|
37
|
+
- Soft delete with `deleted_at` fields
|
|
38
|
+
- REST API generation with Create, Read, Update, Delete + audit
|
|
39
|
+
- Health & monitoring endpoints (audit log, schema diagram)
|
|
40
|
+
- Batch operations for insert/delete
|
|
41
|
+
- Session management with smart reuse & transactions
|
|
42
|
+
- Bootstrap system for one-command setup
|
|
43
|
+
- CSV export/import for backups and migration
|
|
44
|
+
- Database backup/restore with integrity checking
|
|
45
|
+
|
|
46
|
+
## 🧠 Concepts & Overview
|
|
47
|
+
|
|
48
|
+
Vention Storage is a component-based persistence layer for machine apps:
|
|
49
|
+
|
|
50
|
+
- **Database** → SQLite database with managed sessions and transactions
|
|
51
|
+
- **ModelAccessor** → Strongly-typed Create, Read, Update, Delete interface for your SQLModel classes
|
|
52
|
+
- **Hooks** → Functions that run before/after Create, Read, Update, Delete operations
|
|
53
|
+
- **AuditLog** → Automatically records all data mutations
|
|
54
|
+
- **Routers** → Auto-generated FastAPI endpoints for Create, Read, Update, Delete + database management
|
|
55
|
+
|
|
56
|
+
## ⚙️ Installation & Setup
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install vention-storage
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Optional dependencies:**
|
|
63
|
+
- sqlalchemy-schemadisplay and Graphviz → enable database schema visualization
|
|
64
|
+
|
|
65
|
+
MacOS:
|
|
66
|
+
```bash
|
|
67
|
+
brew install graphviz
|
|
68
|
+
pip install sqlalchemy-schemadisplay
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Linux (Debian/Ubuntu)
|
|
72
|
+
```bash
|
|
73
|
+
sudo apt-get install graphviz
|
|
74
|
+
pip install sqlalchemy-schemadisplay
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 🚀 Quickstart Tutorial
|
|
78
|
+
|
|
79
|
+
Define a model, bootstrap storage, and get full Create, Read, Update, Delete endpoints in minutes:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from datetime import datetime
|
|
83
|
+
from typing import Optional
|
|
84
|
+
from sqlmodel import Field, SQLModel
|
|
85
|
+
from fastapi import FastAPI
|
|
86
|
+
from storage.bootstrap import bootstrap
|
|
87
|
+
from storage.accessor import ModelAccessor
|
|
88
|
+
|
|
89
|
+
class User(SQLModel, table=True):
|
|
90
|
+
id: Optional[int] = Field(default=None, primary_key=True)
|
|
91
|
+
name: str
|
|
92
|
+
email: str
|
|
93
|
+
deleted_at: Optional[datetime] = Field(default=None, index=True)
|
|
94
|
+
|
|
95
|
+
app = FastAPI()
|
|
96
|
+
user_accessor = ModelAccessor(User, "users")
|
|
97
|
+
|
|
98
|
+
bootstrap(
|
|
99
|
+
app,
|
|
100
|
+
accessors=[user_accessor],
|
|
101
|
+
database_url="sqlite:///./my_app.db",
|
|
102
|
+
create_tables=True
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
➡️ You now have Create, Read, Update, Delete, audit, backup, and CSV endpoints at `/users` and `/db`.
|
|
107
|
+
|
|
108
|
+
## 🛠 How-to Guides
|
|
109
|
+
|
|
110
|
+
### Bootstrap Multiple Models
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
# user_accessor was created earlier in the Quickstart example
|
|
114
|
+
# Reuse it here to bootstrap multiple models at once
|
|
115
|
+
|
|
116
|
+
product_accessor = ModelAccessor(Product, "products")
|
|
117
|
+
|
|
118
|
+
bootstrap(
|
|
119
|
+
app,
|
|
120
|
+
accessors=[user_accessor, product_accessor],
|
|
121
|
+
database_url="sqlite:///./my_app.db",
|
|
122
|
+
create_tables=True,
|
|
123
|
+
max_records_per_model=100,
|
|
124
|
+
enable_db_router=True
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Export to CSV
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
response = requests.get("http://localhost:8000/db/export.zip")
|
|
132
|
+
with open("backup.zip", "wb") as f:
|
|
133
|
+
f.write(response.content)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Backup & Restore
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
# Backup
|
|
140
|
+
r = requests.get("http://localhost:8000/db/backup.sqlite")
|
|
141
|
+
with open("backup.sqlite", "wb") as f: f.write(r.content)
|
|
142
|
+
|
|
143
|
+
# Restore
|
|
144
|
+
with open("backup.sqlite", "rb") as f:
|
|
145
|
+
files = {"file": ("backup.sqlite", f, "application/x-sqlite3")}
|
|
146
|
+
requests.post("http://localhost:8000/db/restore", files=files)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Use Lifecycle Hooks
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
@user_accessor.before_insert()
|
|
153
|
+
def validate_email(session, instance):
|
|
154
|
+
if "@" not in instance.email:
|
|
155
|
+
raise ValueError("Invalid email")
|
|
156
|
+
|
|
157
|
+
@user_accessor.after_insert()
|
|
158
|
+
def log_creation(session, instance):
|
|
159
|
+
print(f"User created: {instance.name}")
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Query Audit Logs
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from storage.auditor import AuditLog
|
|
166
|
+
from sqlmodel import select
|
|
167
|
+
|
|
168
|
+
with database.transaction() as session:
|
|
169
|
+
logs = session.exec(select(AuditLog).where(AuditLog.component == "users")).all()
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Using the model accessors
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
# Create
|
|
176
|
+
user = user_accessor.insert(User(name="Alice", email="alice@example.com"), actor="admin")
|
|
177
|
+
|
|
178
|
+
# Read
|
|
179
|
+
user = user_accessor.get(user.id)
|
|
180
|
+
|
|
181
|
+
# Update
|
|
182
|
+
user.name = "Alice Smith"
|
|
183
|
+
user_accessor.save(user, actor="admin")
|
|
184
|
+
|
|
185
|
+
# Delete
|
|
186
|
+
user_accessor.delete(user.id, actor="admin")
|
|
187
|
+
|
|
188
|
+
# Restore (for soft-deleted models)
|
|
189
|
+
user_accessor.restore(user.id, actor="admin")
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
### Using the REST API
|
|
194
|
+
|
|
195
|
+
Once bootstrapped, each `ModelAccessor` automatically exposes full CRUD endpoints.
|
|
196
|
+
|
|
197
|
+
Example: interacting with the `/users` API.
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import axios from "axios";
|
|
201
|
+
|
|
202
|
+
const api = axios.create({
|
|
203
|
+
baseURL: "http://localhost:8000", // your backend url
|
|
204
|
+
headers: { "X-User": "operator" }, // used in audit logs
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Create
|
|
208
|
+
export async function createUser(name: string, email: string) {
|
|
209
|
+
const res = await api.post("/users/", { name, email });
|
|
210
|
+
return res.data;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Read
|
|
214
|
+
export async function getUser(id: number) {
|
|
215
|
+
const res = await api.get(`/users/${id}`);
|
|
216
|
+
return res.data;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Update
|
|
220
|
+
export async function updateUser(id: number, name: string) {
|
|
221
|
+
const res = await api.put(`/users/${id}`, { name });
|
|
222
|
+
return res.data;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Delete (soft delete if model supports deleted_at)
|
|
226
|
+
export async function deleteUser(id: number) {
|
|
227
|
+
await api.delete(`/users/${id}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Restore
|
|
231
|
+
export async function restoreUser(id: number) {
|
|
232
|
+
await api.post(`/users/${id}/restore`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// List
|
|
236
|
+
export async function listUsers() {
|
|
237
|
+
const res = await api.get("/users/");
|
|
238
|
+
return res.data;
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
## 📖 API Reference
|
|
244
|
+
|
|
245
|
+
### bootstrap
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
def bootstrap(
|
|
249
|
+
app: FastAPI,
|
|
250
|
+
*,
|
|
251
|
+
accessors: Iterable[ModelAccessor[Any]],
|
|
252
|
+
database_url: Optional[str] = None,
|
|
253
|
+
create_tables: bool = True,
|
|
254
|
+
max_records_per_model: Optional[int] = 5,
|
|
255
|
+
enable_db_router: bool = True,
|
|
256
|
+
) -> None
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### ModelAccessor
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
ModelAccessor(model: Type[ModelType], component_name: str)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Read**
|
|
266
|
+
- `get(id, include_deleted=False) -> Optional[ModelType]`
|
|
267
|
+
- `all(include_deleted=False) -> List[ModelType]`
|
|
268
|
+
|
|
269
|
+
**Write**
|
|
270
|
+
- `insert(obj, actor="internal") -> ModelType`
|
|
271
|
+
- `save(obj, actor="internal") -> ModelType`
|
|
272
|
+
- `delete(id, actor="internal") -> bool`
|
|
273
|
+
- `restore(id, actor="internal") -> bool`
|
|
274
|
+
|
|
275
|
+
**Batch**
|
|
276
|
+
- `insert_many(objs, actor="internal") -> List[ModelType]`
|
|
277
|
+
- `delete_many(ids, actor="internal") -> int`
|
|
278
|
+
|
|
279
|
+
**Hooks**
|
|
280
|
+
- `@accessor.before_insert()`
|
|
281
|
+
- `@accessor.after_insert()`
|
|
282
|
+
- `@accessor.before_update()`
|
|
283
|
+
- `@accessor.after_update()`
|
|
284
|
+
- `@accessor.before_delete()`
|
|
285
|
+
- `@accessor.after_delete()`
|
|
286
|
+
|
|
287
|
+
### Routers
|
|
288
|
+
|
|
289
|
+
- `build_crud_router(accessor, max_records=100) -> APIRouter`
|
|
290
|
+
- `build_db_router(audit_default_limit=100, audit_max_limit=1000) -> APIRouter`
|
|
291
|
+
|
|
292
|
+
### Database Helpers
|
|
293
|
+
|
|
294
|
+
- `database.set_database_url(url: str) -> None`
|
|
295
|
+
- `database.get_engine() -> Engine`
|
|
296
|
+
- `database.transaction() -> Iterator[Session]`
|
|
297
|
+
- `database.use_session(session: Optional[Session] = None) -> Iterator[Session]`
|
|
298
|
+
|
|
299
|
+
### AuditLog model
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
class AuditLog(SQLModel, table=True):
|
|
303
|
+
id: int
|
|
304
|
+
timestamp: datetime
|
|
305
|
+
component: str
|
|
306
|
+
record_id: int
|
|
307
|
+
operation: str
|
|
308
|
+
actor: str
|
|
309
|
+
before: Optional[Dict[str, Any]]
|
|
310
|
+
after: Optional[Dict[str, Any]]
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## 🔍 Troubleshooting & FAQ
|
|
314
|
+
|
|
315
|
+
- **Diagram endpoint fails** → Ensure Graphviz + sqlalchemy-schemadisplay are installed.
|
|
316
|
+
- **No audit actor shown** → Provide X-User header in API requests.
|
|
317
|
+
- **Soft delete not working** → Your model must have a `deleted_at` field.
|
|
318
|
+
- **Restore fails** → Ensure `integrity_check=True` passes when restoring backups.
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# Vention Storage
|
|
2
|
+
|
|
3
|
+
A framework for storing and managing component and application data with persistence, validation, and audit trails for machine applications.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [🧠 Concepts & Overview](#-concepts--overview)
|
|
9
|
+
- [⚙️ Installation & Setup](#️-installation--setup)
|
|
10
|
+
- [🚀 Quickstart Tutorial](#-quickstart-tutorial)
|
|
11
|
+
- [🛠 How-to Guides](#-how-to-guides)
|
|
12
|
+
- [📖 API Reference](#-api-reference)
|
|
13
|
+
- [🔍 Troubleshooting & FAQ](#-troubleshooting--faq)
|
|
14
|
+
|
|
15
|
+
## ✨ Features
|
|
16
|
+
|
|
17
|
+
- Persistent storage with SQLite
|
|
18
|
+
- Automatic audit trails (who, when, what changed)
|
|
19
|
+
- Strong typing & validation via SQLModel
|
|
20
|
+
- Lifecycle hooks before/after insert, update, delete
|
|
21
|
+
- Soft delete with `deleted_at` fields
|
|
22
|
+
- REST API generation with Create, Read, Update, Delete + audit
|
|
23
|
+
- Health & monitoring endpoints (audit log, schema diagram)
|
|
24
|
+
- Batch operations for insert/delete
|
|
25
|
+
- Session management with smart reuse & transactions
|
|
26
|
+
- Bootstrap system for one-command setup
|
|
27
|
+
- CSV export/import for backups and migration
|
|
28
|
+
- Database backup/restore with integrity checking
|
|
29
|
+
|
|
30
|
+
## 🧠 Concepts & Overview
|
|
31
|
+
|
|
32
|
+
Vention Storage is a component-based persistence layer for machine apps:
|
|
33
|
+
|
|
34
|
+
- **Database** → SQLite database with managed sessions and transactions
|
|
35
|
+
- **ModelAccessor** → Strongly-typed Create, Read, Update, Delete interface for your SQLModel classes
|
|
36
|
+
- **Hooks** → Functions that run before/after Create, Read, Update, Delete operations
|
|
37
|
+
- **AuditLog** → Automatically records all data mutations
|
|
38
|
+
- **Routers** → Auto-generated FastAPI endpoints for Create, Read, Update, Delete + database management
|
|
39
|
+
|
|
40
|
+
## ⚙️ Installation & Setup
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install vention-storage
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Optional dependencies:**
|
|
47
|
+
- sqlalchemy-schemadisplay and Graphviz → enable database schema visualization
|
|
48
|
+
|
|
49
|
+
MacOS:
|
|
50
|
+
```bash
|
|
51
|
+
brew install graphviz
|
|
52
|
+
pip install sqlalchemy-schemadisplay
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Linux (Debian/Ubuntu)
|
|
56
|
+
```bash
|
|
57
|
+
sudo apt-get install graphviz
|
|
58
|
+
pip install sqlalchemy-schemadisplay
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 🚀 Quickstart Tutorial
|
|
62
|
+
|
|
63
|
+
Define a model, bootstrap storage, and get full Create, Read, Update, Delete endpoints in minutes:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from datetime import datetime
|
|
67
|
+
from typing import Optional
|
|
68
|
+
from sqlmodel import Field, SQLModel
|
|
69
|
+
from fastapi import FastAPI
|
|
70
|
+
from storage.bootstrap import bootstrap
|
|
71
|
+
from storage.accessor import ModelAccessor
|
|
72
|
+
|
|
73
|
+
class User(SQLModel, table=True):
|
|
74
|
+
id: Optional[int] = Field(default=None, primary_key=True)
|
|
75
|
+
name: str
|
|
76
|
+
email: str
|
|
77
|
+
deleted_at: Optional[datetime] = Field(default=None, index=True)
|
|
78
|
+
|
|
79
|
+
app = FastAPI()
|
|
80
|
+
user_accessor = ModelAccessor(User, "users")
|
|
81
|
+
|
|
82
|
+
bootstrap(
|
|
83
|
+
app,
|
|
84
|
+
accessors=[user_accessor],
|
|
85
|
+
database_url="sqlite:///./my_app.db",
|
|
86
|
+
create_tables=True
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
➡️ You now have Create, Read, Update, Delete, audit, backup, and CSV endpoints at `/users` and `/db`.
|
|
91
|
+
|
|
92
|
+
## 🛠 How-to Guides
|
|
93
|
+
|
|
94
|
+
### Bootstrap Multiple Models
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
# user_accessor was created earlier in the Quickstart example
|
|
98
|
+
# Reuse it here to bootstrap multiple models at once
|
|
99
|
+
|
|
100
|
+
product_accessor = ModelAccessor(Product, "products")
|
|
101
|
+
|
|
102
|
+
bootstrap(
|
|
103
|
+
app,
|
|
104
|
+
accessors=[user_accessor, product_accessor],
|
|
105
|
+
database_url="sqlite:///./my_app.db",
|
|
106
|
+
create_tables=True,
|
|
107
|
+
max_records_per_model=100,
|
|
108
|
+
enable_db_router=True
|
|
109
|
+
)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Export to CSV
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
response = requests.get("http://localhost:8000/db/export.zip")
|
|
116
|
+
with open("backup.zip", "wb") as f:
|
|
117
|
+
f.write(response.content)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Backup & Restore
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
# Backup
|
|
124
|
+
r = requests.get("http://localhost:8000/db/backup.sqlite")
|
|
125
|
+
with open("backup.sqlite", "wb") as f: f.write(r.content)
|
|
126
|
+
|
|
127
|
+
# Restore
|
|
128
|
+
with open("backup.sqlite", "rb") as f:
|
|
129
|
+
files = {"file": ("backup.sqlite", f, "application/x-sqlite3")}
|
|
130
|
+
requests.post("http://localhost:8000/db/restore", files=files)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Use Lifecycle Hooks
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
@user_accessor.before_insert()
|
|
137
|
+
def validate_email(session, instance):
|
|
138
|
+
if "@" not in instance.email:
|
|
139
|
+
raise ValueError("Invalid email")
|
|
140
|
+
|
|
141
|
+
@user_accessor.after_insert()
|
|
142
|
+
def log_creation(session, instance):
|
|
143
|
+
print(f"User created: {instance.name}")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Query Audit Logs
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from storage.auditor import AuditLog
|
|
150
|
+
from sqlmodel import select
|
|
151
|
+
|
|
152
|
+
with database.transaction() as session:
|
|
153
|
+
logs = session.exec(select(AuditLog).where(AuditLog.component == "users")).all()
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Using the model accessors
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
# Create
|
|
160
|
+
user = user_accessor.insert(User(name="Alice", email="alice@example.com"), actor="admin")
|
|
161
|
+
|
|
162
|
+
# Read
|
|
163
|
+
user = user_accessor.get(user.id)
|
|
164
|
+
|
|
165
|
+
# Update
|
|
166
|
+
user.name = "Alice Smith"
|
|
167
|
+
user_accessor.save(user, actor="admin")
|
|
168
|
+
|
|
169
|
+
# Delete
|
|
170
|
+
user_accessor.delete(user.id, actor="admin")
|
|
171
|
+
|
|
172
|
+
# Restore (for soft-deleted models)
|
|
173
|
+
user_accessor.restore(user.id, actor="admin")
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
### Using the REST API
|
|
178
|
+
|
|
179
|
+
Once bootstrapped, each `ModelAccessor` automatically exposes full CRUD endpoints.
|
|
180
|
+
|
|
181
|
+
Example: interacting with the `/users` API.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import axios from "axios";
|
|
185
|
+
|
|
186
|
+
const api = axios.create({
|
|
187
|
+
baseURL: "http://localhost:8000", // your backend url
|
|
188
|
+
headers: { "X-User": "operator" }, // used in audit logs
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Create
|
|
192
|
+
export async function createUser(name: string, email: string) {
|
|
193
|
+
const res = await api.post("/users/", { name, email });
|
|
194
|
+
return res.data;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Read
|
|
198
|
+
export async function getUser(id: number) {
|
|
199
|
+
const res = await api.get(`/users/${id}`);
|
|
200
|
+
return res.data;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Update
|
|
204
|
+
export async function updateUser(id: number, name: string) {
|
|
205
|
+
const res = await api.put(`/users/${id}`, { name });
|
|
206
|
+
return res.data;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Delete (soft delete if model supports deleted_at)
|
|
210
|
+
export async function deleteUser(id: number) {
|
|
211
|
+
await api.delete(`/users/${id}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Restore
|
|
215
|
+
export async function restoreUser(id: number) {
|
|
216
|
+
await api.post(`/users/${id}/restore`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// List
|
|
220
|
+
export async function listUsers() {
|
|
221
|
+
const res = await api.get("/users/");
|
|
222
|
+
return res.data;
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
## 📖 API Reference
|
|
228
|
+
|
|
229
|
+
### bootstrap
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
def bootstrap(
|
|
233
|
+
app: FastAPI,
|
|
234
|
+
*,
|
|
235
|
+
accessors: Iterable[ModelAccessor[Any]],
|
|
236
|
+
database_url: Optional[str] = None,
|
|
237
|
+
create_tables: bool = True,
|
|
238
|
+
max_records_per_model: Optional[int] = 5,
|
|
239
|
+
enable_db_router: bool = True,
|
|
240
|
+
) -> None
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### ModelAccessor
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
ModelAccessor(model: Type[ModelType], component_name: str)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Read**
|
|
250
|
+
- `get(id, include_deleted=False) -> Optional[ModelType]`
|
|
251
|
+
- `all(include_deleted=False) -> List[ModelType]`
|
|
252
|
+
|
|
253
|
+
**Write**
|
|
254
|
+
- `insert(obj, actor="internal") -> ModelType`
|
|
255
|
+
- `save(obj, actor="internal") -> ModelType`
|
|
256
|
+
- `delete(id, actor="internal") -> bool`
|
|
257
|
+
- `restore(id, actor="internal") -> bool`
|
|
258
|
+
|
|
259
|
+
**Batch**
|
|
260
|
+
- `insert_many(objs, actor="internal") -> List[ModelType]`
|
|
261
|
+
- `delete_many(ids, actor="internal") -> int`
|
|
262
|
+
|
|
263
|
+
**Hooks**
|
|
264
|
+
- `@accessor.before_insert()`
|
|
265
|
+
- `@accessor.after_insert()`
|
|
266
|
+
- `@accessor.before_update()`
|
|
267
|
+
- `@accessor.after_update()`
|
|
268
|
+
- `@accessor.before_delete()`
|
|
269
|
+
- `@accessor.after_delete()`
|
|
270
|
+
|
|
271
|
+
### Routers
|
|
272
|
+
|
|
273
|
+
- `build_crud_router(accessor, max_records=100) -> APIRouter`
|
|
274
|
+
- `build_db_router(audit_default_limit=100, audit_max_limit=1000) -> APIRouter`
|
|
275
|
+
|
|
276
|
+
### Database Helpers
|
|
277
|
+
|
|
278
|
+
- `database.set_database_url(url: str) -> None`
|
|
279
|
+
- `database.get_engine() -> Engine`
|
|
280
|
+
- `database.transaction() -> Iterator[Session]`
|
|
281
|
+
- `database.use_session(session: Optional[Session] = None) -> Iterator[Session]`
|
|
282
|
+
|
|
283
|
+
### AuditLog model
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
class AuditLog(SQLModel, table=True):
|
|
287
|
+
id: int
|
|
288
|
+
timestamp: datetime
|
|
289
|
+
component: str
|
|
290
|
+
record_id: int
|
|
291
|
+
operation: str
|
|
292
|
+
actor: str
|
|
293
|
+
before: Optional[Dict[str, Any]]
|
|
294
|
+
after: Optional[Dict[str, Any]]
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## 🔍 Troubleshooting & FAQ
|
|
298
|
+
|
|
299
|
+
- **Diagram endpoint fails** → Ensure Graphviz + sqlalchemy-schemadisplay are installed.
|
|
300
|
+
- **No audit actor shown** → Provide X-User header in API requests.
|
|
301
|
+
- **Soft delete not working** → Your model must have a `deleted_at` field.
|
|
302
|
+
- **Restore fails** → Ensure `integrity_check=True` passes when restoring backups.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "vention-storage"
|
|
3
|
+
version = "0.5.4"
|
|
4
|
+
description = "A framework for storing and managing component and application data for machine apps."
|
|
5
|
+
authors = [ "VentionCo" ]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = "Proprietary"
|
|
8
|
+
packages = [{ include = "storage", from = "src" }]
|
|
9
|
+
|
|
10
|
+
[tool.poetry.dependencies]
|
|
11
|
+
sqlmodel = "0.0.27"
|
|
12
|
+
python = ">=3.10,<3.11"
|
|
13
|
+
fastapi = "0.121.1"
|
|
14
|
+
uvicorn = "^0.35.0"
|
|
15
|
+
python-multipart = "^0.0.20"
|
|
16
|
+
|
|
17
|
+
[tool.poetry.group.dev.dependencies]
|
|
18
|
+
pytest = "^8.3.4"
|
|
19
|
+
ruff = "^0.8.0"
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = [
|
|
23
|
+
"poetry-core>=1.0.0,<2.0.0"
|
|
24
|
+
]
|
|
25
|
+
build-backend = "poetry.core.masonry.api"
|