tigrbl_engine_numpy 0.1.1.dev3__tar.gz → 0.1.1.dev5__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.
- {tigrbl_engine_numpy-0.1.1.dev3 → tigrbl_engine_numpy-0.1.1.dev5}/.gitignore +4 -0
- {tigrbl_engine_numpy-0.1.1.dev3 → tigrbl_engine_numpy-0.1.1.dev5}/PKG-INFO +1 -1
- {tigrbl_engine_numpy-0.1.1.dev3 → tigrbl_engine_numpy-0.1.1.dev5}/pyproject.toml +1 -1
- {tigrbl_engine_numpy-0.1.1.dev3 → tigrbl_engine_numpy-0.1.1.dev5}/src/tigrbl_engine_numpy/session.py +138 -58
- {tigrbl_engine_numpy-0.1.1.dev3 → tigrbl_engine_numpy-0.1.1.dev5}/tests/test_numpy_io.py +69 -0
- {tigrbl_engine_numpy-0.1.1.dev3 → tigrbl_engine_numpy-0.1.1.dev5}/tests/test_tigrblapp_rpc_usage.py +3 -2
- {tigrbl_engine_numpy-0.1.1.dev3 → tigrbl_engine_numpy-0.1.1.dev5}/LICENSE +0 -0
- {tigrbl_engine_numpy-0.1.1.dev3 → tigrbl_engine_numpy-0.1.1.dev5}/README.md +0 -0
- {tigrbl_engine_numpy-0.1.1.dev3 → tigrbl_engine_numpy-0.1.1.dev5}/distout/.gitignore +0 -0
- {tigrbl_engine_numpy-0.1.1.dev3 → tigrbl_engine_numpy-0.1.1.dev5}/src/tigrbl_engine_numpy/__init__.py +0 -0
- {tigrbl_engine_numpy-0.1.1.dev3 → tigrbl_engine_numpy-0.1.1.dev5}/src/tigrbl_engine_numpy/engine.py +0 -0
- {tigrbl_engine_numpy-0.1.1.dev3 → tigrbl_engine_numpy-0.1.1.dev5}/tests/test_smoke.py +0 -0
|
@@ -30,9 +30,13 @@ pkgs/standards/peagen/.pymon
|
|
|
30
30
|
gateway.db
|
|
31
31
|
kms.db
|
|
32
32
|
*.asc
|
|
33
|
+
*.kid
|
|
33
34
|
*.so
|
|
34
35
|
*.db
|
|
35
36
|
target/
|
|
36
37
|
!.gitkeep # keep the empty dir in repo
|
|
37
38
|
pkgs/experimental/swarmakit/libs/svelte/.vscode/extensions.json
|
|
38
39
|
node_modules/
|
|
40
|
+
*.zip
|
|
41
|
+
.pymon
|
|
42
|
+
/.tmp_pydeps
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tigrbl_engine_numpy
|
|
3
|
-
Version: 0.1.1.
|
|
3
|
+
Version: 0.1.1.dev5
|
|
4
4
|
Summary: NumPy engine plugin for tigrbl with array-to-table helpers.
|
|
5
5
|
Project-URL: Homepage, https://github.com/swarmauri/swarmauri-sdk
|
|
6
6
|
Author-email: Jacob Stewart <jacob@swarmauri.com>
|
{tigrbl_engine_numpy-0.1.1.dev3 → tigrbl_engine_numpy-0.1.1.dev5}/src/tigrbl_engine_numpy/session.py
RENAMED
|
@@ -16,7 +16,49 @@ from typing import (
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
import numpy as np
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from tigrbl.session.base import TigrblSessionBase
|
|
22
|
+
except Exception:
|
|
23
|
+
from abc import ABC, abstractmethod
|
|
24
|
+
|
|
25
|
+
class TigrblSessionBase(ABC):
|
|
26
|
+
def __init__(self, spec=None):
|
|
27
|
+
self._spec = spec
|
|
28
|
+
|
|
29
|
+
def apply_spec(self, spec):
|
|
30
|
+
self._spec = spec
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def _add_impl(self, obj): ...
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
async def _delete_impl(self, obj): ...
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
async def _flush_impl(self): ...
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
async def _refresh_impl(self, obj): ...
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
async def _get_impl(self, model, ident): ...
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
async def _execute_impl(self, stmt): ...
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
async def _tx_begin_impl(self): ...
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
async def _tx_commit_impl(self): ...
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
async def _tx_rollback_impl(self): ...
|
|
58
|
+
|
|
59
|
+
@abstractmethod
|
|
60
|
+
async def _close_impl(self): ...
|
|
61
|
+
|
|
20
62
|
|
|
21
63
|
try:
|
|
22
64
|
from tigrbl.session.spec import SessionSpec
|
|
@@ -69,15 +111,21 @@ class NumpySession(TigrblSessionBase):
|
|
|
69
111
|
self._puts: dict[tuple[type, Any], dict[str, Any]] = {}
|
|
70
112
|
self._dels: set[tuple[type, Any]] = set()
|
|
71
113
|
self._tracked: dict[tuple[type, Any], Any] = {}
|
|
114
|
+
self._tracked: dict[tuple[type, Any], Any] = {}
|
|
72
115
|
|
|
73
116
|
def to_records(self) -> list[dict[str, Any]]:
|
|
74
117
|
pk = self._engine.catalog.pk
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
118
|
+
by_id: dict[Any, dict[str, Any]] = {}
|
|
119
|
+
for row in self._engine.catalog.rows:
|
|
120
|
+
ident = row.get(pk)
|
|
121
|
+
if ident is None:
|
|
122
|
+
continue
|
|
123
|
+
by_id[ident] = dict(row)
|
|
124
|
+
for (_, ident), row in self._puts.items():
|
|
125
|
+
by_id[ident] = dict(row)
|
|
126
|
+
for _, ident in self._dels:
|
|
127
|
+
by_id.pop(ident, None)
|
|
128
|
+
return list(by_id.values())
|
|
81
129
|
|
|
82
130
|
def array(self) -> np.ndarray:
|
|
83
131
|
rows = self.to_records()
|
|
@@ -163,6 +211,7 @@ class NumpySession(TigrblSessionBase):
|
|
|
163
211
|
self._puts.clear()
|
|
164
212
|
self._dels.clear()
|
|
165
213
|
self._tracked.clear()
|
|
214
|
+
self._tracked.clear()
|
|
166
215
|
|
|
167
216
|
async def _tx_commit_impl(self) -> None:
|
|
168
217
|
iso = (self._spec.isolation if self._spec else None) or "read_committed"
|
|
@@ -193,44 +242,33 @@ class NumpySession(TigrblSessionBase):
|
|
|
193
242
|
self._puts.clear()
|
|
194
243
|
self._dels.clear()
|
|
195
244
|
self._tracked.clear()
|
|
245
|
+
self._tracked.clear()
|
|
196
246
|
|
|
197
247
|
async def _tx_rollback_impl(self) -> None:
|
|
198
248
|
self._puts.clear()
|
|
199
249
|
self._dels.clear()
|
|
200
250
|
self._tracked.clear()
|
|
201
251
|
|
|
202
|
-
@staticmethod
|
|
203
|
-
def _pk_default(model: type, pk: str) -> Any:
|
|
204
|
-
table = getattr(model, "__table__", None)
|
|
205
|
-
if table is None:
|
|
206
|
-
return None
|
|
207
|
-
try:
|
|
208
|
-
column = table.columns.get(pk)
|
|
209
|
-
except Exception:
|
|
210
|
-
return None
|
|
211
|
-
if column is None:
|
|
212
|
-
return None
|
|
213
|
-
default = getattr(column, "default", None)
|
|
214
|
-
if default is None:
|
|
215
|
-
return None
|
|
216
|
-
arg = getattr(default, "arg", None)
|
|
217
|
-
if callable(arg):
|
|
218
|
-
try:
|
|
219
|
-
return arg()
|
|
220
|
-
except TypeError:
|
|
221
|
-
return arg(None)
|
|
222
|
-
return arg
|
|
223
|
-
|
|
224
252
|
def _add_impl(self, obj: Any) -> Any:
|
|
225
253
|
model = obj.__class__
|
|
226
254
|
pk = _single_pk_name(model)
|
|
227
255
|
ident = getattr(obj, pk)
|
|
228
256
|
if ident is None:
|
|
229
|
-
|
|
230
|
-
|
|
257
|
+
default = getattr(getattr(model, "__table__", None), "columns", {}).get(pk)
|
|
258
|
+
default = getattr(default, "default", None)
|
|
259
|
+
if default is not None:
|
|
260
|
+
arg = getattr(default, "arg", default)
|
|
261
|
+
if callable(arg):
|
|
262
|
+
try:
|
|
263
|
+
ident = arg()
|
|
264
|
+
except TypeError:
|
|
265
|
+
ident = arg(None)
|
|
266
|
+
else:
|
|
267
|
+
ident = arg
|
|
231
268
|
setattr(obj, pk, ident)
|
|
232
269
|
if ident is None:
|
|
233
270
|
raise ValueError(f"primary key {pk!r} must be set")
|
|
271
|
+
self._tracked[(model, ident)] = obj
|
|
234
272
|
row = {c: getattr(obj, c, None) for c in _model_columns(model)}
|
|
235
273
|
self._puts[(model, ident)] = row
|
|
236
274
|
self._dels.discard((model, ident))
|
|
@@ -243,40 +281,42 @@ class NumpySession(TigrblSessionBase):
|
|
|
243
281
|
self._puts.pop((model, ident), None)
|
|
244
282
|
self._dels.add((model, ident))
|
|
245
283
|
self._tracked.pop((model, ident), None)
|
|
284
|
+
self._tracked.pop((model, ident), None)
|
|
246
285
|
|
|
247
286
|
async def _flush_impl(self) -> None:
|
|
248
|
-
for (model, ident), obj in self._tracked.items():
|
|
287
|
+
for (model, ident), obj in list(self._tracked.items()):
|
|
249
288
|
if (model, ident) in self._dels:
|
|
250
289
|
continue
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
290
|
+
row = {c: getattr(obj, c, None) for c in _model_columns(model)}
|
|
291
|
+
baseline = self._resolve_row(model, ident)
|
|
292
|
+
if baseline is None:
|
|
293
|
+
continue
|
|
294
|
+
if row == dict(baseline):
|
|
295
|
+
continue
|
|
296
|
+
if self._spec and self._spec.read_only:
|
|
297
|
+
raise RuntimeError("read-only session: writes detected during flush")
|
|
298
|
+
self._puts[(model, ident)] = row
|
|
255
299
|
|
|
256
300
|
async def _refresh_impl(self, obj: Any) -> None:
|
|
257
301
|
pk = _single_pk_name(obj.__class__)
|
|
258
302
|
ident = getattr(obj, pk)
|
|
259
|
-
|
|
260
|
-
if
|
|
303
|
+
row = self._resolve_row(obj.__class__, ident)
|
|
304
|
+
if row is None:
|
|
261
305
|
return
|
|
262
306
|
for c in _model_columns(obj.__class__):
|
|
263
|
-
|
|
307
|
+
if c in row:
|
|
308
|
+
setattr(obj, c, row[c])
|
|
264
309
|
|
|
265
310
|
async def _get_impl(self, model: type, ident: Any) -> Any | None:
|
|
266
|
-
row = self._puts.get((model, ident))
|
|
267
|
-
if row is not None:
|
|
268
|
-
obj = self._inflate(model, row)
|
|
269
|
-
self._tracked[(model, ident)] = obj
|
|
270
|
-
return obj
|
|
271
311
|
if (model, ident) in self._dels:
|
|
272
312
|
return None
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
return
|
|
313
|
+
tracked = self._tracked.get((model, ident))
|
|
314
|
+
if tracked is not None:
|
|
315
|
+
return tracked
|
|
316
|
+
row = self._resolve_row(model, ident)
|
|
317
|
+
if row is None:
|
|
318
|
+
return None
|
|
319
|
+
return self._hydrate_tracked(model, ident, row)
|
|
280
320
|
|
|
281
321
|
async def _execute_impl(self, stmt: Any) -> Any:
|
|
282
322
|
kind = type(stmt).__name__.lower()
|
|
@@ -299,6 +339,7 @@ class NumpySession(TigrblSessionBase):
|
|
|
299
339
|
raise NotImplementedError(f"Unsupported statement: {type(stmt)}")
|
|
300
340
|
|
|
301
341
|
async def _close_impl(self) -> None:
|
|
342
|
+
self._tracked.clear()
|
|
302
343
|
return
|
|
303
344
|
|
|
304
345
|
def _inflate(self, model: type, data: Mapping[str, Any]) -> Any:
|
|
@@ -309,17 +350,43 @@ class NumpySession(TigrblSessionBase):
|
|
|
309
350
|
return obj
|
|
310
351
|
|
|
311
352
|
def _scan_model(self, model: type) -> List[Any]:
|
|
312
|
-
out = [self._inflate(model, row) for row in self._engine.catalog.rows]
|
|
313
353
|
pk = _single_pk_name(model)
|
|
314
|
-
by_id
|
|
354
|
+
by_id: dict[Any, Any] = {}
|
|
355
|
+
for row in self._engine.catalog.rows:
|
|
356
|
+
ident = row.get(pk)
|
|
357
|
+
if ident is None:
|
|
358
|
+
continue
|
|
359
|
+
by_id[ident] = self._hydrate_tracked(model, ident, row)
|
|
315
360
|
for (m, ident), row in self._puts.items():
|
|
316
361
|
if m is model:
|
|
317
|
-
by_id[ident] = self.
|
|
362
|
+
by_id[ident] = self._hydrate_tracked(model, ident, row)
|
|
318
363
|
for m, ident in self._dels:
|
|
319
364
|
if m is model:
|
|
320
365
|
by_id.pop(ident, None)
|
|
366
|
+
self._tracked.pop((model, ident), None)
|
|
321
367
|
return list(by_id.values())
|
|
322
368
|
|
|
369
|
+
def _resolve_row(self, model: type, ident: Any) -> Mapping[str, Any] | None:
|
|
370
|
+
row = self._puts.get((model, ident))
|
|
371
|
+
if row is not None:
|
|
372
|
+
return row
|
|
373
|
+
pk = _single_pk_name(model)
|
|
374
|
+
for record in self._engine.catalog.rows:
|
|
375
|
+
if record.get(pk) == ident:
|
|
376
|
+
return record
|
|
377
|
+
return None
|
|
378
|
+
|
|
379
|
+
def _hydrate_tracked(self, model: type, ident: Any, data: Mapping[str, Any]) -> Any:
|
|
380
|
+
obj = self._tracked.get((model, ident))
|
|
381
|
+
if obj is None:
|
|
382
|
+
obj = self._inflate(model, data)
|
|
383
|
+
self._tracked[(model, ident)] = obj
|
|
384
|
+
return obj
|
|
385
|
+
for c in _model_columns(model):
|
|
386
|
+
if c in data:
|
|
387
|
+
setattr(obj, c, data[c])
|
|
388
|
+
return obj
|
|
389
|
+
|
|
323
390
|
def _decompose_select(
|
|
324
391
|
self, stmt: Any
|
|
325
392
|
) -> Tuple[
|
|
@@ -349,14 +416,20 @@ class NumpySession(TigrblSessionBase):
|
|
|
349
416
|
|
|
350
417
|
def _all_subclasses(base: type) -> list[type]:
|
|
351
418
|
out: list[type] = []
|
|
352
|
-
stack =
|
|
419
|
+
stack = [base]
|
|
420
|
+
seen: set[type] = set()
|
|
353
421
|
while stack:
|
|
354
422
|
cls = stack.pop()
|
|
355
|
-
out.append(cls)
|
|
356
423
|
try:
|
|
357
|
-
|
|
424
|
+
children = cls.__subclasses__()
|
|
358
425
|
except TypeError:
|
|
359
|
-
|
|
426
|
+
continue
|
|
427
|
+
for child in children:
|
|
428
|
+
if child in seen:
|
|
429
|
+
continue
|
|
430
|
+
seen.add(child)
|
|
431
|
+
out.append(child)
|
|
432
|
+
stack.append(child)
|
|
360
433
|
return out
|
|
361
434
|
|
|
362
435
|
def _find_by_table(name: str) -> type | None:
|
|
@@ -365,6 +438,13 @@ class NumpySession(TigrblSessionBase):
|
|
|
365
438
|
return cls
|
|
366
439
|
return None
|
|
367
440
|
|
|
441
|
+
table = getattr(stmt, "table", None)
|
|
442
|
+
name = getattr(table, "name", None)
|
|
443
|
+
if isinstance(name, str):
|
|
444
|
+
found = _find_by_table(name)
|
|
445
|
+
if found is not None:
|
|
446
|
+
return found
|
|
447
|
+
|
|
368
448
|
for attr_name in ("_from_objects", "_froms", "froms"):
|
|
369
449
|
value = getattr(stmt, attr_name, None)
|
|
370
450
|
if value is not None:
|
|
@@ -4,10 +4,34 @@ from pathlib import Path
|
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import pytest
|
|
7
|
+
from tigrbl.specs import F, IO, S
|
|
8
|
+
from tigrbl.shortcuts import acol
|
|
9
|
+
from tigrbl.table import Table
|
|
10
|
+
from tigrbl.types import Mapped, String
|
|
7
11
|
|
|
8
12
|
from tigrbl_engine_numpy import numpy_engine
|
|
9
13
|
|
|
10
14
|
|
|
15
|
+
class _Widget(Table):
|
|
16
|
+
__tablename__ = "session_widgets"
|
|
17
|
+
|
|
18
|
+
id: Mapped[str] = acol(
|
|
19
|
+
storage=S(type_=String(64), primary_key=True, nullable=False),
|
|
20
|
+
field=F(py_type=str),
|
|
21
|
+
io=IO(out_verbs=("read", "list")),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
name: Mapped[str] = acol(
|
|
25
|
+
storage=S(type_=String(50), nullable=False),
|
|
26
|
+
field=F(py_type=str),
|
|
27
|
+
io=IO(
|
|
28
|
+
in_verbs=("create", "update", "replace"),
|
|
29
|
+
out_verbs=("read", "list"),
|
|
30
|
+
mutable_verbs=("create", "update", "replace"),
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
11
35
|
def test_numpy_session_save_and_load_npy(tmp_path: Path) -> None:
|
|
12
36
|
target = tmp_path / "records.npy"
|
|
13
37
|
_, session_factory = numpy_engine(
|
|
@@ -134,3 +158,48 @@ def test_numpy_session_save_uses_atomic_replace(
|
|
|
134
158
|
assert len(calls) == 1
|
|
135
159
|
assert calls[0][1] == str(target)
|
|
136
160
|
assert Path(calls[0][0]).name.startswith(".tmp_")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@pytest.mark.asyncio
|
|
164
|
+
async def test_numpy_session_get_reuses_tracked_instance() -> None:
|
|
165
|
+
ident = "fixed-id"
|
|
166
|
+
_, session_factory = numpy_engine(
|
|
167
|
+
mapping={
|
|
168
|
+
"array": np.array([[ident, "a"]], dtype=object),
|
|
169
|
+
"columns": ["id", "name"],
|
|
170
|
+
"pk": "id",
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
session = session_factory()
|
|
174
|
+
|
|
175
|
+
first = await session.get(_Widget, ident)
|
|
176
|
+
assert first is not None
|
|
177
|
+
first.name = "mutated"
|
|
178
|
+
|
|
179
|
+
second = await session.get(_Widget, ident)
|
|
180
|
+
assert second is first
|
|
181
|
+
assert second.name == "mutated"
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@pytest.mark.asyncio
|
|
185
|
+
async def test_numpy_session_refresh_updates_tracked_instance() -> None:
|
|
186
|
+
ident = "fixed-id"
|
|
187
|
+
engine, session_factory = numpy_engine(
|
|
188
|
+
mapping={
|
|
189
|
+
"array": np.array([[ident, "a"]], dtype=object),
|
|
190
|
+
"columns": ["id", "name"],
|
|
191
|
+
"pk": "id",
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
session = session_factory()
|
|
195
|
+
|
|
196
|
+
item = await session.get(_Widget, ident)
|
|
197
|
+
assert item is not None
|
|
198
|
+
item.name = "mutated"
|
|
199
|
+
|
|
200
|
+
engine.catalog.rows[0]["name"] = "server"
|
|
201
|
+
await session.refresh(item)
|
|
202
|
+
|
|
203
|
+
again = await session.get(_Widget, ident)
|
|
204
|
+
assert again is item
|
|
205
|
+
assert again.name == "server"
|
{tigrbl_engine_numpy-0.1.1.dev3 → tigrbl_engine_numpy-0.1.1.dev5}/tests/test_tigrblapp_rpc_usage.py
RENAMED
|
@@ -4,10 +4,11 @@ import numpy as np
|
|
|
4
4
|
import pytest
|
|
5
5
|
|
|
6
6
|
from tigrbl import TigrblApp
|
|
7
|
-
from tigrbl
|
|
7
|
+
from tigrbl import rpc_call
|
|
8
8
|
from tigrbl.engine import EngineSpec
|
|
9
9
|
from tigrbl.orm.mixins import GUIDPk
|
|
10
|
-
from tigrbl.specs import F, IO, S
|
|
10
|
+
from tigrbl.specs import F, IO, S
|
|
11
|
+
from tigrbl.shortcuts import acol
|
|
11
12
|
from tigrbl.table import Table
|
|
12
13
|
from tigrbl.types import Mapped, String
|
|
13
14
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tigrbl_engine_numpy-0.1.1.dev3 → tigrbl_engine_numpy-0.1.1.dev5}/src/tigrbl_engine_numpy/engine.py
RENAMED
|
File without changes
|
|
File without changes
|