vention-storage 0.5.4__py3-none-any.whl → 0.6.0__py3-none-any.whl

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.
storage/router_model.py DELETED
@@ -1,247 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any, Dict, List, Optional, Type
4
-
5
- from fastapi import (
6
- APIRouter,
7
- Depends,
8
- HTTPException,
9
- Query,
10
- Request,
11
- Body,
12
- Response,
13
- status,
14
- )
15
- from sqlalchemy.exc import DataError, IntegrityError, StatementError
16
-
17
- from storage.accessor import ModelAccessor
18
- from storage.utils import ModelType
19
-
20
- __all__ = ["build_crud_router"]
21
-
22
- DEFAULT_MAX_RECORDS_PER_MODEL = 100
23
-
24
-
25
- def get_actor(request: Request) -> str:
26
- """
27
- Extract the audit actor from the `X-User` header. Eg: 'Operator', 'Admin', etc.
28
-
29
- Notes:
30
- - Required for **mutating** endpoints (POST, PUT, DELETE, restore).
31
- - Optional for read-only endpoints.
32
- - The value is stored verbatim in `AuditLog.actor`.
33
- - If missing on a mutating endpoint, a 400 error is returned.
34
-
35
- Returns:
36
- The `X-User` header value.
37
-
38
- Raises:
39
- HTTPException(400): If the header is missing for a mutating endpoint.
40
- """
41
- actor = request.headers.get("X-User")
42
- if not actor:
43
- raise HTTPException(status_code=400, detail="Missing X-User header")
44
- return actor
45
-
46
-
47
- def build_crud_router(
48
- accessor: ModelAccessor[ModelType],
49
- *,
50
- max_records: Optional[int] = DEFAULT_MAX_RECORDS_PER_MODEL,
51
- ) -> APIRouter:
52
- """
53
- Build a FastAPI router exposing CRUD + restore endpoints for a single SQLModel component.
54
- """
55
- model: Type[ModelType] = accessor.model
56
- router = APIRouter(prefix=f"/{accessor.component}", tags=[accessor.component])
57
-
58
- # ---------------- READS ----------------
59
-
60
- @router.get("/", response_model=List[model]) # type: ignore[valid-type]
61
- def list_records(
62
- include_deleted: bool = Query(False, description="Include soft-deleted rows"),
63
- ) -> List[ModelType]:
64
- """
65
- List all records for this model.
66
-
67
- By default, soft-deleted records are excluded from results.
68
-
69
- Args:
70
- include_deleted (bool): Set to true to include soft-deleted records. Defaults to false.
71
-
72
- Returns:
73
- List[model]: List of model instances matching the criteria.
74
- """
75
- return accessor.all(include_deleted=include_deleted)
76
-
77
- @router.get("/{record_id}", response_model=model)
78
- def get_record(
79
- record_id: int,
80
- include_deleted: bool = Query(False, description="Include soft-deleted row"),
81
- ) -> ModelType:
82
- """
83
- Retrieve a single record by its ID.
84
-
85
- Args:
86
- record_id (int): ID of the record to fetch.
87
- include_deleted (bool): Set to true to allow returning a soft-deleted record. Defaults to false.
88
-
89
- Returns:
90
- model: The model instance if found.
91
-
92
- Raises:
93
- HTTPException 404: If the record does not exist or is soft-deleted (when include_deleted=false).
94
- """
95
- obj = accessor.get(record_id, include_deleted=include_deleted)
96
- if not obj:
97
- raise HTTPException(status_code=404, detail="Not found")
98
- return obj
99
-
100
- # ---------------- WRITES ----------------
101
-
102
- @router.post("/", response_model=model)
103
- def create_record(
104
- payload: Dict[str, Any] = Body(...),
105
- actor: str = Depends(get_actor),
106
- ) -> ModelType:
107
- """
108
- Create a new record.
109
-
110
- Args:
111
- payload (Dict[str, Any]): JSON body with the record fields.
112
- actor (str): User identifier from the `X-User` header.
113
-
114
- Returns:
115
- model: The newly created model instance.
116
-
117
- Raises:
118
- HTTPException 409: If the maximum number of records has been reached.
119
- HTTPException 422: If the payload violates schema or database constraints.
120
- """
121
- if max_records is not None:
122
- total = len(accessor.all(include_deleted=True))
123
- if total >= max_records:
124
- raise HTTPException(
125
- status_code=409,
126
- detail=f"Max {max_records} records allowed for {accessor.component}",
127
- )
128
- obj = model(**payload)
129
-
130
- try:
131
- return accessor.insert(obj, actor=actor)
132
- except (IntegrityError, DataError, StatementError) as e:
133
- raise HTTPException(status_code=422, detail=str(e)) from e
134
-
135
- @router.put("/{record_id}", response_model=model)
136
- def update_record(
137
- record_id: int,
138
- response: Response,
139
- payload: Dict[str, Any] = Body(...),
140
- actor: str = Depends(get_actor),
141
- ) -> ModelType:
142
- """
143
- Upsert a record (PUT semantics).
144
-
145
- Args:
146
- record_id (int): ID of the record to update or create.
147
- payload (Dict[str, Any]): JSON body with the record fields (the `id` key, if present, is ignored).
148
- actor (str): User identifier from the `X-User` header.
149
- response (Response): Used to adjust the HTTP status code.
150
-
151
- Returns:
152
- model: The updated or newly created model instance.
153
-
154
- If the record exists, it is updated in place (200 OK).
155
- If the record does not exist, it is created at this ID (201 Created).
156
- """
157
- existed = accessor.get(record_id, include_deleted=True) is not None
158
-
159
- # If this PUT will create a new row, enforce the max records.
160
- if not existed and max_records is not None:
161
- total = len(accessor.all(include_deleted=True))
162
- if total >= max_records:
163
- raise HTTPException(
164
- status_code=409,
165
- detail=f"Max {max_records} records allowed for {accessor.component}",
166
- )
167
-
168
- # Build instance (ignore `id` from body if present)
169
- payload_no_id = {key: value for key, value in payload.items() if key != "id"}
170
- obj = model(id=record_id, **payload_no_id)
171
-
172
- try:
173
- saved = accessor.save(obj, actor=actor)
174
- except (IntegrityError, DataError, StatementError) as e:
175
- raise HTTPException(status_code=422, detail=str(e)) from e
176
-
177
- if not existed:
178
- response.status_code = status.HTTP_201_CREATED
179
- return saved
180
-
181
- @router.delete("/{record_id}")
182
- def delete_record(
183
- record_id: int,
184
- actor: str = Depends(get_actor),
185
- ) -> Dict[str, str]:
186
- """
187
- Delete a record by ID.
188
-
189
- If the model supports soft-delete, the record is marked as deleted.
190
- Otherwise, the record is permanently removed.
191
-
192
- Args:
193
- record_id (int): ID of the record to delete.
194
- actor (str): User identifier from the `X-User` header.
195
-
196
- Returns:
197
- dict: {"status": "deleted"} on success.
198
-
199
- Raises:
200
- HTTPException 404: If the record does not exist.
201
- """
202
- ok = accessor.delete(record_id, actor=actor)
203
- if not ok:
204
- raise HTTPException(status_code=404, detail="Not found")
205
- return {"status": "deleted"}
206
-
207
- # ---------------- RESTORE (soft-delete only) ----------------
208
-
209
- @router.post("/{record_id}/restore")
210
- def restore_record(
211
- record_id: int,
212
- actor: str = Depends(get_actor),
213
- ) -> Dict[str, Any]:
214
- """
215
- Restore a soft-deleted record.
216
-
217
- This endpoint only applies if the model has a `deleted_at` field.
218
-
219
- Args:
220
- record_id (int): ID of the record to restore.
221
- actor (str): User identifier from the `X-User` header.
222
-
223
- Returns:
224
- dict: {"status": "ok", "restored": True} if the record was restored,
225
- {"status": "ok", "restored": False} if it was not soft-deleted.
226
-
227
- Raises:
228
- HTTPException 404: If the record does not exist.
229
- HTTPException 409: If the model does not support soft-delete/restore.
230
- """
231
- if not hasattr(accessor.model, "deleted_at"):
232
- raise HTTPException(
233
- status_code=409,
234
- detail=f"{accessor.component} does not support soft delete/restore",
235
- )
236
-
237
- obj = accessor.get(record_id, include_deleted=True)
238
- if not obj:
239
- raise HTTPException(status_code=404, detail="Not found")
240
-
241
- if getattr(obj, "deleted_at") is None:
242
- return {"status": "ok", "restored": False}
243
-
244
- accessor.restore(record_id, actor=actor)
245
- return {"status": "ok", "restored": True}
246
-
247
- return router
@@ -1,13 +0,0 @@
1
- storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- storage/accessor.py,sha256=hpWenJQlQWNFppubC47lwG-j1ulyDlQlFJu06SufOtk,10385
3
- storage/auditor.py,sha256=tsvsb9qlHdcknY5OAXRZBMcN4nFDKtN3Ln1LfgozJrw,2090
4
- storage/bootstrap.py,sha256=16wbX5WIJd8atWYi2scvsIYjE7A-YDzfWzDp4sPZ17I,1444
5
- storage/database.py,sha256=BGRnlH0qBVnYffp0F1TCWGb6OS8cdlMmEJ1-LeHH4oc,3184
6
- storage/hooks.py,sha256=rMK8R-VO0B7caZn9HtTlcetx0KQMvsl1Aq-t9mbFA4Q,1298
7
- storage/io_helpers.py,sha256=c9Lzqc1kKwMquleCriGHYIIWVgDUmJRakIB1TNMpOjs,5061
8
- storage/router_database.py,sha256=UcsU_OBXVynNAGW6cHYdHvFdmfTtzBcO6x28mXlbsR4,10177
9
- storage/router_model.py,sha256=uLxnn1zzeWTorEOEH5ExEsc3lHWCxjZwROSWaYbOXuU,8316
10
- storage/utils.py,sha256=uK30rKwwb1lqxfP3qQzsqH53umW-dCLh1jOec1r2p1k,588
11
- vention_storage-0.5.4.dist-info/METADATA,sha256=y7xsutKsOl6jkroktn2T8Qkm4vhnVQRnuk7UkQoTwOc,8396
12
- vention_storage-0.5.4.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
13
- vention_storage-0.5.4.dist-info/RECORD,,