tigrbl_engine_xlsx 0.1.1.dev1__tar.gz → 0.1.1.dev3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tigrbl_engine_xlsx
3
- Version: 0.1.1.dev1
3
+ Version: 0.1.1.dev3
4
4
  Summary: XLSX engine plugin for tigrbl where each workbook is a database and each sheet is a table.
5
5
  Project-URL: Homepage, https://github.com/swarmauri/swarmauri-sdk
6
6
  Author-email: Jacob Stewart <jacob@swarmauri.com>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "tigrbl_engine_xlsx"
7
- version = "0.1.1.dev1"
7
+ version = "0.1.1.dev3"
8
8
  description = "XLSX engine plugin for tigrbl where each workbook is a database and each sheet is a table."
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import os
4
4
  import tempfile
5
+ import uuid
5
6
 
6
7
  from typing import (
7
8
  TYPE_CHECKING,
@@ -122,6 +123,7 @@ class XlsxSession(TigrblSessionBase):
122
123
  self._snap_ver: Dict[str, int] = {}
123
124
  self._puts: Dict[Tuple[type, Any], Dict[str, Any]] = {}
124
125
  self._dels: set[Tuple[type, Any]] = set()
126
+ self._tracked: Dict[Tuple[type, Any], Any] = {}
125
127
 
126
128
  def workbook(self):
127
129
  return self._cat.workbook
@@ -130,7 +132,19 @@ class XlsxSession(TigrblSessionBase):
130
132
  return self._cat.workbook[name]
131
133
 
132
134
  def table(self, name: str) -> list[dict[str, Any]]:
133
- return [dict(row) for row in self._cat.get_live(name)]
135
+ rows = [dict(row) for row in self._cat.get_live(name)]
136
+ pk = self._pk_of(name)
137
+ by_pk = {row.get(pk): row for row in rows}
138
+
139
+ for (model, ident), row in self._puts.items():
140
+ if self._table(model) == name:
141
+ by_pk[ident] = dict(row)
142
+
143
+ for model, ident in self._dels:
144
+ if self._table(model) == name:
145
+ by_pk.pop(ident, None)
146
+
147
+ return list(by_pk.values())
134
148
 
135
149
  async def run_sync(self, fn: Callable[[Any], Any]) -> Any:
136
150
  out = fn(self)
@@ -141,6 +155,7 @@ class XlsxSession(TigrblSessionBase):
141
155
  self._snap_ver.clear()
142
156
  self._puts.clear()
143
157
  self._dels.clear()
158
+ self._tracked.clear()
144
159
 
145
160
  async def _tx_commit_impl(self) -> None:
146
161
  iso = (self._spec.isolation if self._spec else None) or "read_committed"
@@ -179,22 +194,51 @@ class XlsxSession(TigrblSessionBase):
179
194
 
180
195
  self._puts.clear()
181
196
  self._dels.clear()
197
+ self._tracked.clear()
182
198
 
183
199
  async def _tx_rollback_impl(self) -> None:
184
200
  self._snap.clear()
185
201
  self._snap_ver.clear()
186
202
  self._puts.clear()
187
203
  self._dels.clear()
204
+ self._tracked.clear()
205
+
206
+ @staticmethod
207
+ def _pk_default(model: type, pk: str) -> Any:
208
+ table = getattr(model, "__table__", None)
209
+ if table is None:
210
+ return None
211
+ try:
212
+ column = table.columns.get(pk)
213
+ except Exception:
214
+ return None
215
+ if column is None:
216
+ return None
217
+ default = getattr(column, "default", None)
218
+ if default is None:
219
+ return None
220
+ arg = getattr(default, "arg", None)
221
+ if callable(arg):
222
+ try:
223
+ return arg()
224
+ except TypeError:
225
+ return arg(None)
226
+ return arg
188
227
 
189
228
  def _add_impl(self, obj: Any) -> Any:
190
229
  model = obj.__class__
191
230
  pk = _single_pk_name(model)
192
231
  ident = getattr(obj, pk)
232
+ if ident is None:
233
+ ident = self._pk_default(model, pk)
234
+ if ident is not None:
235
+ setattr(obj, pk, ident)
193
236
  if ident is None:
194
237
  raise ValueError(f"primary key {pk!r} must be set")
195
238
  row = {column: getattr(obj, column, None) for column in _model_columns(model)}
196
239
  self._puts[(model, ident)] = row
197
240
  self._dels.discard((model, ident))
241
+ self._tracked[(model, ident)] = obj
198
242
  return None
199
243
 
200
244
  async def _delete_impl(self, obj: Any) -> None:
@@ -203,8 +247,16 @@ class XlsxSession(TigrblSessionBase):
203
247
  ident = getattr(obj, pk)
204
248
  self._puts.pop((model, ident), None)
205
249
  self._dels.add((model, ident))
250
+ self._tracked.pop((model, ident), None)
206
251
 
207
252
  async def _flush_impl(self) -> None:
253
+ for (model, ident), obj in list(self._tracked.items()):
254
+ if (model, ident) in self._dels:
255
+ continue
256
+ row = {
257
+ column: getattr(obj, column, None) for column in _model_columns(model)
258
+ }
259
+ self._puts[(model, ident)] = row
208
260
  return
209
261
 
210
262
  async def _refresh_impl(self, obj: Any) -> None:
@@ -217,16 +269,19 @@ class XlsxSession(TigrblSessionBase):
217
269
  setattr(obj, column, getattr(fresh, column, None))
218
270
 
219
271
  async def _get_impl(self, model: type, ident: Any) -> Any | None:
272
+ tracked = self._tracked.get((model, ident))
273
+ if tracked is not None:
274
+ return tracked
220
275
  row = self._puts.get((model, ident))
221
276
  if row is not None:
222
- return self._inflate(model, row)
277
+ return self._inflate_tracked(model, ident, row)
223
278
  if (model, ident) in self._dels:
224
279
  return None
225
280
  table_rows = self._rows_for(model)
226
281
  pk = _single_pk_name(model)
227
282
  for table_row in table_rows:
228
283
  if table_row.get(pk) == ident:
229
- return self._inflate(model, table_row)
284
+ return self._inflate_tracked(model, ident, table_row)
230
285
  return None
231
286
 
232
287
  async def _execute_impl(self, stmt: Any) -> Any:
@@ -245,6 +300,7 @@ class XlsxSession(TigrblSessionBase):
245
300
  ident = getattr(obj, pk)
246
301
  self._puts.pop((model, ident), None)
247
302
  self._dels.add((model, ident))
303
+ self._tracked.pop((model, ident), None)
248
304
  result = _ExecuteResult([])
249
305
  result.rowcount = len(items)
250
306
  return result
@@ -280,13 +336,22 @@ class XlsxSession(TigrblSessionBase):
280
336
  setattr(obj, column, data[column])
281
337
  return obj
282
338
 
339
+ def _inflate_tracked(self, model: type, ident: Any, data: Mapping[str, Any]) -> Any:
340
+ obj = self._tracked.get((model, ident))
341
+ if obj is None:
342
+ obj = self._inflate(model, data)
343
+ self._tracked[(model, ident)] = obj
344
+ return obj
345
+
283
346
  def _scan_model(self, model: type) -> List[Any]:
284
347
  out = [self._inflate(model, row) for row in self._rows_for(model)]
285
348
  pk = _single_pk_name(model)
286
349
  by_id = {getattr(obj, pk): obj for obj in out}
287
350
  for (known_model, ident), row in self._puts.items():
288
351
  if known_model is model:
289
- by_id[ident] = self._inflate(model, row)
352
+ by_id[ident] = self._inflate_tracked(model, ident, row)
353
+ for ident, obj in list(by_id.items()):
354
+ self._tracked[(model, ident)] = obj
290
355
  for known_model, ident in self._dels:
291
356
  if known_model is model:
292
357
  by_id.pop(ident, None)
@@ -309,9 +374,15 @@ class XlsxSession(TigrblSessionBase):
309
374
  columns = [self._pk_of(table)]
310
375
  sheet.append(columns)
311
376
  for row in rows:
312
- sheet.append([row.get(col) for col in columns])
377
+ sheet.append([self._excel_value(row.get(col)) for col in columns])
313
378
  self._atomic_save_workbook(workbook, self._cat.path)
314
379
 
380
+ @staticmethod
381
+ def _excel_value(value: Any) -> Any:
382
+ if isinstance(value, uuid.UUID):
383
+ return str(value)
384
+ return value
385
+
315
386
  def _atomic_save_workbook(self, workbook: Any, path: str) -> None:
316
387
  directory = os.path.dirname(path) or "."
317
388
  fd, tmp = tempfile.mkstemp(dir=directory, prefix=".tmp_", suffix=".xlsx")
@@ -351,12 +422,18 @@ class XlsxSession(TigrblSessionBase):
351
422
  return entity
352
423
 
353
424
  def _all_subclasses(base: type) -> list[type]:
425
+ def _safe_subclasses(cls: type) -> list[type]:
426
+ try:
427
+ return list(cls.__subclasses__())
428
+ except TypeError:
429
+ return []
430
+
354
431
  out: list[type] = []
355
- stack = list(base.__subclasses__())
432
+ stack = _safe_subclasses(base)
356
433
  while stack:
357
434
  cls = stack.pop()
358
435
  out.append(cls)
359
- stack.extend(cls.__subclasses__())
436
+ stack.extend(_safe_subclasses(cls))
360
437
  return out
361
438
 
362
439
  def _find_by_table(name: str) -> type | None:
@@ -45,7 +45,7 @@ def app_and_db(tmp_path: Path) -> tuple[TigrblApp, object]:
45
45
  db = session_factory()
46
46
 
47
47
  api = TigrblApp(engine=spec)
48
- api.include_model(XlsxWidget, mount_router=False)
48
+ api.include_table(XlsxWidget, mount_router=False)
49
49
  api.initialize()
50
50
  return api, db
51
51