tigrbl_engine_dataframe 0.1.10.dev1__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.
@@ -0,0 +1,41 @@
1
+ """tigrbl_engine_dataframe: DataFrame-backed Tigrbl engine"""
2
+
3
+ from .df_engine import dataframe_engine, dataframe_capabilities, DataFrameCatalog
4
+ from .df_session import TransactionalDataFrameSession
5
+
6
+ __all__ = [
7
+ "dataframe_engine",
8
+ "dataframe_capabilities",
9
+ "DataFrameCatalog",
10
+ "TransactionalDataFrameSession",
11
+ "register",
12
+ ]
13
+
14
+
15
+ def register() -> None:
16
+ """
17
+ Entry point target for group 'tigrbl.engine'. This function will be loaded
18
+ by Tigrbl's plugin system. It attempts to register the 'dataframe' kind
19
+ with whatever registry is exposed by the installed Tigrbl version.
20
+ """
21
+ register_fn = None
22
+ try:
23
+ from tigrbl.engine.registry import register_engine as _reg
24
+
25
+ register_fn = _reg
26
+ except Exception:
27
+ try:
28
+ from tigrbl.engine.plugins import register_engine as _reg2
29
+
30
+ register_fn = _reg2
31
+ except Exception:
32
+ try:
33
+ from tigrbl.engine import register_engine as _reg3 # type: ignore
34
+
35
+ register_fn = _reg3
36
+ except Exception as exc:
37
+ raise RuntimeError(
38
+ "Could not locate Tigrbl engine registry to register plugin"
39
+ ) from exc
40
+
41
+ register_fn("dataframe", dataframe_engine, dataframe_capabilities)
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, Callable, Dict, Mapping, Optional, Tuple
5
+ import threading
6
+
7
+ import pandas as pd
8
+
9
+ from tigrbl.session.spec import SessionSpec
10
+ from .df_session import TransactionalDataFrameSession
11
+
12
+ # ---- Engine object: in-memory catalog of DataFrames + versions ----
13
+
14
+
15
+ @dataclass
16
+ class DataFrameCatalog:
17
+ tables: Dict[str, pd.DataFrame] = field(default_factory=dict) # name -> live frame
18
+ pks: Dict[str, str] = field(default_factory=dict) # name -> primary-key column
19
+ table_ver: Dict[str, int] = field(default_factory=dict) # name -> monotonic version
20
+ lock: threading.RLock = field(
21
+ default_factory=threading.RLock
22
+ ) # atomic apply on commit
23
+
24
+ def get_live(self, name: str) -> pd.DataFrame:
25
+ if name not in self.tables:
26
+ self.tables[name] = pd.DataFrame()
27
+ self.table_ver[name] = 0
28
+ self.table_ver.setdefault(name, 0)
29
+ return self.tables[name]
30
+
31
+ def bump(self, name: str) -> None:
32
+ self.table_ver[name] = self.table_ver.get(name, 0) + 1
33
+
34
+
35
+ # ---- Builder expected by Tigrbl EngineSpec (kind='dataframe') ----
36
+
37
+
38
+ def dataframe_engine(
39
+ *,
40
+ mapping: Optional[Mapping[str, object]] = None,
41
+ spec: Any = None,
42
+ dsn: Optional[str] = None,
43
+ ) -> Tuple[DataFrameCatalog, Callable[[], TransactionalDataFrameSession]]:
44
+ """
45
+ Return (engine, sessionmaker) for the 'dataframe' kind.
46
+
47
+ EngineSpec(kind="dataframe") calls this with:
48
+ - mapping: plugin-specific config (tables, pks)
49
+ - spec: the EngineSpec instance (not used here)
50
+ - dsn: optional DSN (not used here)
51
+ """
52
+ m = dict(mapping or {})
53
+ initial_tables = m.get("tables") or {}
54
+ pks = m.get("pks") or {}
55
+
56
+ if not isinstance(initial_tables, dict):
57
+ raise TypeError("mapping['tables'] must be a dict[str, pandas.DataFrame]")
58
+ if not isinstance(pks, dict):
59
+ raise TypeError("mapping['pks'] must be a dict[str, str]")
60
+
61
+ # Defensive copy of tables
62
+ tables = {
63
+ k: (v.copy() if isinstance(v, pd.DataFrame) else v)
64
+ for k, v in initial_tables.items()
65
+ }
66
+ cat = DataFrameCatalog(tables=tables, pks=dict(pks))
67
+
68
+ def mk() -> TransactionalDataFrameSession:
69
+ # A neutral SessionSpec is attached here; the effective SessionSpec from
70
+ # session_ctx is typically applied by the Tigrbl layer wrapping the sessionmaker.
71
+ return TransactionalDataFrameSession(cat, spec=SessionSpec())
72
+
73
+ return cat, mk
74
+
75
+
76
+ # ---- Capabilities (optional but useful for validation) ----
77
+
78
+
79
+ def dataframe_capabilities() -> dict[str, object]:
80
+ """Report capabilities for the 'dataframe' engine."""
81
+ return {
82
+ "transactional": True,
83
+ "read_only_enforced": True,
84
+ "isolation_levels": {
85
+ "read_committed",
86
+ "repeatable_read",
87
+ "snapshot",
88
+ "serializable",
89
+ },
90
+ }
@@ -0,0 +1,487 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple
4
+
5
+ import pandas as pd
6
+
7
+ # Prefer Tigrbl's base; provide a minimal fallback if missing (package can still import)
8
+ try:
9
+ from tigrbl.session.base import TigrblSessionBase
10
+ except Exception: # fallback minimal ABC
11
+ from abc import ABC, abstractmethod
12
+
13
+ class TigrblSessionBase(ABC):
14
+ def __init__(self, spec=None):
15
+ self._spec = spec
16
+ self._open = False
17
+ self._dirty = False
18
+
19
+ async def begin(self):
20
+ self._open = True
21
+
22
+ async def commit(self):
23
+ self._open = False
24
+ self._dirty = False
25
+
26
+ async def rollback(self):
27
+ self._open = False
28
+ self._dirty = False
29
+
30
+ def in_transaction(self):
31
+ return self._open
32
+
33
+ async def run_sync(self, fn):
34
+ rv = fn(self)
35
+ return await rv if hasattr(rv, "__await__") else rv
36
+
37
+ def apply_spec(self, spec):
38
+ self._spec = spec
39
+
40
+ # abstract CRUD/lifecycle
41
+ @abstractmethod
42
+ def _add_impl(self, obj): ...
43
+ @abstractmethod
44
+ async def _delete_impl(self, obj): ...
45
+ @abstractmethod
46
+ async def _flush_impl(self): ...
47
+ @abstractmethod
48
+ async def _refresh_impl(self, obj): ...
49
+ @abstractmethod
50
+ async def _get_impl(self, model, ident): ...
51
+ @abstractmethod
52
+ async def _execute_impl(self, stmt): ...
53
+ @abstractmethod
54
+ async def _tx_begin_impl(self): ...
55
+ @abstractmethod
56
+ async def _tx_commit_impl(self): ...
57
+ @abstractmethod
58
+ async def _tx_rollback_impl(self): ...
59
+ @abstractmethod
60
+ async def _close_impl(self): ...
61
+ # public CRUD wrappers
62
+ def add(self, obj):
63
+ self._dirty = True
64
+ return self._add_impl(obj)
65
+
66
+ async def delete(self, obj):
67
+ self._dirty = True
68
+ return await self._delete_impl(obj)
69
+
70
+ async def flush(self):
71
+ return await self._flush_impl()
72
+
73
+ async def refresh(self, obj):
74
+ return await self._refresh_impl(obj)
75
+
76
+ async def get(self, model, ident):
77
+ return await self._get_impl(model, ident)
78
+
79
+ async def execute(self, stmt):
80
+ return await self._execute_impl(stmt)
81
+
82
+ async def close(self):
83
+ return await self._close_impl()
84
+
85
+
86
+ try:
87
+ from tigrbl.session.spec import SessionSpec
88
+ except Exception:
89
+
90
+ class SessionSpec:
91
+ def __init__(self, isolation=None, read_only=None):
92
+ self.isolation = isolation
93
+ self.read_only = read_only
94
+
95
+
96
+ try:
97
+ from tigrbl.core.crud.helpers.model import _single_pk_name, _model_columns
98
+ except Exception:
99
+ # Minimal fallbacks
100
+ def _single_pk_name(model):
101
+ return "id"
102
+
103
+ def _model_columns(model):
104
+ return getattr(model, "__annotations__", {}) or {"id": int}
105
+
106
+
107
+ try:
108
+ from tigrbl.core.crud.helpers import NoResultFound
109
+ except Exception:
110
+
111
+ class NoResultFound(Exception):
112
+ pass
113
+
114
+
115
+ from .df_engine import DataFrameCatalog
116
+
117
+ # ---- Result facades compatible with Tigrbl CRUD ----
118
+
119
+
120
+ class _ScalarResult:
121
+ def __init__(self, items: Sequence[Any]) -> None:
122
+ self._items = list(items)
123
+
124
+ def scalars(self) -> "_ScalarResult":
125
+ return self
126
+
127
+ def all(self) -> List[Any]:
128
+ return list(self._items)
129
+
130
+ def scalar_one(self) -> Any:
131
+ if len(self._items) != 1:
132
+ raise NoResultFound("expected exactly one row")
133
+ return self._items[0]
134
+
135
+
136
+ class _ExecuteResult(_ScalarResult):
137
+ rowcount: int = 0
138
+
139
+
140
+ # ---- Transactional DataFrame Session ----
141
+
142
+
143
+ class TransactionalDataFrameSession(TigrblSessionBase):
144
+ """Native-transaction session over pandas DataFrames."""
145
+
146
+ def __init__(
147
+ self, catalog: DataFrameCatalog, spec: Optional[SessionSpec] = None
148
+ ) -> None:
149
+ super().__init__(spec)
150
+ self._cat = catalog
151
+ self._snap: Dict[str, pd.DataFrame] = {}
152
+ self._snap_ver: Dict[str, int] = {}
153
+ self._puts: Dict[Tuple[type, Any], Dict[str, Any]] = {}
154
+ self._dels: set[Tuple[type, Any]] = set()
155
+
156
+ # ---- lifecycle / async marker ----
157
+ async def run_sync(self, fn: Callable[[Any], Any]) -> Any:
158
+ out = fn(self)
159
+ return await out if hasattr(out, "__await__") else out
160
+
161
+ # ---- TX primitives ----
162
+ async def _tx_begin_impl(self) -> None:
163
+ self._snap.clear()
164
+ self._snap_ver.clear()
165
+ self._puts.clear()
166
+ self._dels.clear()
167
+
168
+ async def _tx_commit_impl(self) -> None:
169
+ iso = (self._spec.isolation if self._spec else None) or "read_committed"
170
+ # Conflict detection (coarse, per-table)
171
+ if iso in ("repeatable_read", "snapshot", "serializable"):
172
+ for tbl, ver in self._snap_ver.items():
173
+ if self._cat.table_ver.get(tbl, 0) != ver:
174
+ raise RuntimeError(f"transaction conflict on table '{tbl}'")
175
+
176
+ # Apply mutations atomically
177
+ with self._cat.lock:
178
+ # deletes
179
+ dels_by_tbl: Dict[str, List[Any]] = {}
180
+ for model, ident in self._dels:
181
+ tbl = self._table(model)
182
+ dels_by_tbl.setdefault(tbl, []).append(ident)
183
+ for tbl, idents in dels_by_tbl.items():
184
+ pk = self._pk_of(tbl)
185
+ live = self._cat.get_live(tbl)
186
+ if pk in live.columns and not live.empty:
187
+ self._cat.tables[tbl] = live[~live[pk].isin(idents)].copy()
188
+ self._cat.bump(tbl)
189
+
190
+ # upserts
191
+ puts_by_tbl: Dict[str, List[Dict[str, Any]]] = {}
192
+ for (model, _), row in self._puts.items():
193
+ puts_by_tbl.setdefault(self._table(model), []).append(row)
194
+ for tbl, rows in puts_by_tbl.items():
195
+ pk = self._pk_of(tbl)
196
+ live = self._cat.get_live(tbl)
197
+ df_new = pd.DataFrame(rows)
198
+ if df_new.empty:
199
+ continue
200
+ if pk not in df_new.columns:
201
+ raise RuntimeError(f"missing pk '{pk}' for table '{tbl}'")
202
+ if live.empty:
203
+ combined = df_new.copy()
204
+ else:
205
+ combined = live[~live[pk].isin(df_new[pk])].copy()
206
+ combined = pd.concat([combined, df_new], ignore_index=True)
207
+ self._cat.tables[tbl] = combined
208
+ self._cat.bump(tbl)
209
+
210
+ self._puts.clear()
211
+ self._dels.clear()
212
+
213
+ async def _tx_rollback_impl(self) -> None:
214
+ self._snap.clear()
215
+ self._snap_ver.clear()
216
+ self._puts.clear()
217
+ self._dels.clear()
218
+
219
+ # ---- CRUD primitives ----
220
+ def _add_impl(self, obj: Any) -> Any:
221
+ model = obj.__class__
222
+ pk = _single_pk_name(model)
223
+ ident = getattr(obj, pk)
224
+ if ident is None:
225
+ raise ValueError(f"primary key {pk!r} must be set")
226
+ row = {c: getattr(obj, c, None) for c in _model_columns(model)}
227
+ self._puts[(model, ident)] = row
228
+ self._dels.discard((model, ident))
229
+ return None
230
+
231
+ async def _delete_impl(self, obj: Any) -> None:
232
+ model = obj.__class__
233
+ pk = _single_pk_name(model)
234
+ ident = getattr(obj, pk)
235
+ self._puts.pop((model, ident), None)
236
+ self._dels.add((model, ident))
237
+
238
+ async def _flush_impl(self) -> None:
239
+ return
240
+
241
+ async def _refresh_impl(self, obj: Any) -> None:
242
+ pk = _single_pk_name(obj.__class__)
243
+ ident = getattr(obj, pk)
244
+ fresh = await self._get_impl(obj.__class__, ident)
245
+ if fresh is None:
246
+ return
247
+ for c in _model_columns(obj.__class__):
248
+ setattr(obj, c, getattr(fresh, c, None))
249
+
250
+ async def _get_impl(self, model: type, ident: Any) -> Any | None:
251
+ row = self._puts.get((model, ident))
252
+ if row is not None:
253
+ return self._inflate(model, row)
254
+ if (model, ident) in self._dels:
255
+ return None
256
+ df = self._frame_for(model)
257
+ pk = _single_pk_name(model)
258
+ if pk not in df.columns or df.empty:
259
+ return None
260
+ m = df[df[pk] == ident]
261
+ if m.empty:
262
+ return None
263
+ return self._inflate(model, m.iloc[0].to_dict())
264
+
265
+ async def _execute_impl(self, stmt: Any) -> Any:
266
+ kind = type(stmt).__name__.lower()
267
+ if "select" in kind:
268
+ model, where, order, limit, offset = self._decompose_select(stmt)
269
+ items = self._scan_model(model)
270
+ items = [o for o in items if self._matches_obj(o, where)]
271
+ items = self._order_slice(items, order, limit, offset)
272
+ return _ExecuteResult(items)
273
+ if "delete" in kind:
274
+ model, where = self._decompose_delete(stmt)
275
+ items = self._scan_model(model)
276
+ items = [o for o in items if self._matches_obj(o, where)]
277
+ for o in items:
278
+ pk = _single_pk_name(model)
279
+ ident = getattr(o, pk)
280
+ self._puts.pop((model, ident), None)
281
+ self._dels.add((model, ident))
282
+ res = _ExecuteResult([])
283
+ res.rowcount = len(items)
284
+ return res
285
+ raise NotImplementedError(f"Unsupported statement: {type(stmt)}")
286
+
287
+ async def _close_impl(self) -> None:
288
+ return
289
+
290
+ # ---- helpers ----
291
+ def _table(self, model: type) -> str:
292
+ return getattr(model, "__tablename__", None) or model.__name__
293
+
294
+ def _pk_of(self, table: str) -> str:
295
+ if table in self._cat.pks:
296
+ return self._cat.pks[table]
297
+ live = self._cat.get_live(table)
298
+ if "id" in live.columns:
299
+ return "id"
300
+ raise RuntimeError(f"primary key for table '{table}' is unknown")
301
+
302
+ def _frame_for(self, model: type) -> pd.DataFrame:
303
+ table = self._table(model)
304
+ iso = (self._spec.isolation if self._spec else None) or "read_committed"
305
+ if (
306
+ iso in ("repeatable_read", "snapshot", "serializable")
307
+ and table not in self._snap
308
+ ):
309
+ live = self._cat.get_live(table)
310
+ self._snap[table] = live.copy(deep=True)
311
+ self._snap_ver[table] = self._cat.table_ver.get(table, 0)
312
+ return (
313
+ self._snap.get(table) if table in self._snap else self._cat.get_live(table)
314
+ )
315
+
316
+ def _inflate(self, model: type, data: Mapping[str, Any]) -> Any:
317
+ obj = model()
318
+ for c in _model_columns(model):
319
+ if c in data:
320
+ setattr(obj, c, data[c])
321
+ return obj
322
+
323
+ def _scan_model(self, model: type) -> List[Any]:
324
+ df = self._frame_for(model)
325
+ out: List[Any] = []
326
+ if not df.empty:
327
+ for _, row in df.iterrows():
328
+ out.append(self._inflate(model, row.to_dict()))
329
+ # overlay upserts / deletes
330
+ pk = _single_pk_name(model)
331
+ by_id = {getattr(o, pk): o for o in out}
332
+ for (m, ident), row in self._puts.items():
333
+ if m is model:
334
+ by_id[ident] = self._inflate(model, row)
335
+ for m, ident in list(self._dels):
336
+ if m is model:
337
+ by_id.pop(ident, None)
338
+ return list(by_id.values())
339
+
340
+ # ---- duck-typed stmt parsing (eq/IN/order/limit/offset) ----
341
+ def _decompose_select(
342
+ self, stmt: Any
343
+ ) -> Tuple[
344
+ type,
345
+ list[Tuple[str, str, Any]],
346
+ list[Tuple[str, str]],
347
+ Optional[int],
348
+ Optional[int],
349
+ ]:
350
+ model = self._extract_model(stmt)
351
+ where = self._extract_predicates(stmt)
352
+ order = self._extract_order_by(stmt)
353
+ limit = self._extract_int(stmt, ["_limit", "_limit_clause", "limit"])
354
+ offset = self._extract_int(stmt, ["_offset", "_offset_clause", "offset"])
355
+ return model, where, order, limit, offset
356
+
357
+ def _decompose_delete(self, stmt: Any) -> Tuple[type, list[Tuple[str, str, Any]]]:
358
+ model = self._extract_model(stmt)
359
+ where = self._extract_predicates(stmt)
360
+ return model, where
361
+
362
+ def _extract_model(self, stmt: Any) -> type:
363
+ for a in ("_from_objects", "_froms", "froms"):
364
+ v = getattr(stmt, a, None)
365
+ if v:
366
+ t = v[0] if isinstance(v, (list, tuple)) else v
367
+ name = getattr(t, "name", None)
368
+ if isinstance(name, str):
369
+ for cls in object.__subclasses__(object):
370
+ if getattr(cls, "__tablename__", None) == name:
371
+ return cls
372
+ rc = getattr(stmt, "_raw_columns", None) or getattr(stmt, "columns", None)
373
+ if rc:
374
+ ent = rc[0]
375
+ table = getattr(ent, "table", None)
376
+ name = getattr(table, "name", None)
377
+ if name:
378
+ for cls in object.__subclasses__(object):
379
+ if getattr(cls, "__tablename__", None) == name:
380
+ return cls
381
+ raise RuntimeError("Cannot resolve model from statement")
382
+
383
+ def _extract_predicates(self, stmt: Any) -> list[Tuple[str, str, Any]]:
384
+ where = getattr(stmt, "whereclause", None) or getattr(
385
+ stmt, "_whereclause", None
386
+ )
387
+ if where is None:
388
+ return []
389
+ parts = (
390
+ getattr(where, "clauses", None)
391
+ or getattr(where, "get_children", lambda: [])()
392
+ )
393
+ nodes = list(parts) if parts else [where]
394
+ out: list[Tuple[str, str, Any]] = []
395
+ for n in nodes:
396
+ left, right, op = (
397
+ getattr(n, "left", None),
398
+ getattr(n, "right", None),
399
+ getattr(n, "operator", None),
400
+ )
401
+ if left is None or right is None:
402
+ continue
403
+ name = getattr(left, "key", None) or getattr(left, "name", None)
404
+ if name is None:
405
+ continue
406
+ opname = getattr(op, "__name__", str(op))
407
+ if "eq" in opname:
408
+ val = (
409
+ getattr(right, "value", None)
410
+ if hasattr(right, "value")
411
+ else getattr(right, "literal", None)
412
+ )
413
+ out.append((str(name), "eq", val))
414
+ continue
415
+ rclauses = getattr(right, "clauses", None)
416
+ if rclauses is not None and "in" in opname:
417
+ vals = [
418
+ getattr(lit, "value", None)
419
+ if hasattr(lit, "value")
420
+ else getattr(lit, "literal", None)
421
+ for lit in rclauses
422
+ ]
423
+ out.append((str(name), "in", vals))
424
+ return out
425
+
426
+ def _extract_order_by(self, stmt: Any) -> list[Tuple[str, str]]:
427
+ order = getattr(stmt, "_order_by_clause", None) or getattr(
428
+ stmt, "_order_by_clauses", None
429
+ )
430
+ if not order:
431
+ return []
432
+ clauses = getattr(order, "clauses", None) or order
433
+ clauses = clauses if isinstance(clauses, (list, tuple)) else [clauses]
434
+ for ob in clauses:
435
+ col = (
436
+ getattr(ob, "element", None)
437
+ or getattr(ob, "this", None)
438
+ or getattr(ob, "expr", None)
439
+ )
440
+ name = getattr(col, "key", None) or getattr(col, "name", None)
441
+ direction = "desc" if "desc" in type(ob).__name__.lower() else "asc"
442
+ if name:
443
+ return [(str(name), direction)]
444
+ return []
445
+
446
+ def _extract_int(self, stmt: Any, names: Sequence[str]) -> Optional[int]:
447
+ for n in names:
448
+ v = getattr(stmt, n, None)
449
+ if v is None:
450
+ continue
451
+ try:
452
+ return int(v)
453
+ except Exception:
454
+ val = getattr(v, "value", None)
455
+ if val is not None:
456
+ try:
457
+ return int(val)
458
+ except Exception:
459
+ pass
460
+ return None
461
+
462
+ def _matches_obj(self, obj: Any, where: list[Tuple[str, str, Any]]) -> bool:
463
+ for name, op, val in where:
464
+ dv = getattr(obj, name, None)
465
+ if op == "eq" and dv != val:
466
+ return False
467
+ if op == "in" and dv not in set(val):
468
+ return False
469
+ return True
470
+
471
+ def _order_slice(
472
+ self,
473
+ items: List[Any],
474
+ order: list[Tuple[str, str]],
475
+ limit: Optional[int],
476
+ offset: Optional[int],
477
+ ) -> List[Any]:
478
+ if order:
479
+ col, direction = order[0]
480
+ items.sort(
481
+ key=lambda o: getattr(o, col, None), reverse=(direction == "desc")
482
+ )
483
+ if isinstance(offset, int):
484
+ items = items[max(0, offset) :]
485
+ if isinstance(limit, int):
486
+ items = items[: max(0, limit)]
487
+ return items
@@ -0,0 +1,50 @@
1
+ Metadata-Version: 2.4
2
+ Name: tigrbl_engine_dataframe
3
+ Version: 0.1.10.dev1
4
+ Summary: Tigrbl engine plugin providing transactional pandas DataFrame sessions.
5
+ Project-URL: Homepage, https://github.com/swarmauri/swarmauri-sdk
6
+ Project-URL: Repository, https://github.com/swarmauri/swarmauri-sdk/tree/master/pkgs/experimental/tigrbl_engine_dataframe
7
+ Author-email: Jacob Stewart <jacob@swarmauri.com>
8
+ License-Expression: Apache-2.0
9
+ License-File: LICENSE
10
+ Keywords: database,dataframe,engine,pandas,plugin,tigrbl
11
+ Classifier: Development Status :: 1 - Planning
12
+ Classifier: Environment :: Plugins
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Database
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Requires-Python: <3.14,>=3.10
24
+ Requires-Dist: pandas>=2.0
25
+ Requires-Dist: tigrbl
26
+ Description-Content-Type: text/markdown
27
+
28
+ # tigrbl_engine_dataframe
29
+
30
+ This file is a package-local distribution entry point.
31
+ It is not the authoritative location for repository governance, current target status, current state reporting, certification claims, or release evidence.
32
+
33
+ ## Canonical repository docs
34
+
35
+ - `README.md`
36
+ - `docs/README.md`
37
+ - `docs/conformance/CURRENT_TARGET.md`
38
+ - `docs/conformance/CURRENT_STATE.md`
39
+ - `docs/conformance/NEXT_STEPS.md`
40
+ - `docs/governance/DOC_POINTERS.md`
41
+ - `docs/developer/PACKAGE_CATALOG.md`
42
+ - `docs/developer/PACKAGE_LAYOUT.md`
43
+
44
+ ## Package identity
45
+
46
+ - workspace path: `pkgs/engines/tigrbl_engine_dataframe`
47
+ - workspace class: engine package
48
+ - implementation layout: `src/tigrbl_engine_dataframe/`
49
+
50
+ Long-form repository documentation is governed from `docs/`.
@@ -0,0 +1,8 @@
1
+ tigrbl_engine_dataframe/__init__.py,sha256=NfklNopxhOWn9m7O53l2mrWaHfPiyFstnnhGW5ReAtw,1295
2
+ tigrbl_engine_dataframe/df_engine.py,sha256=ltxVnXsPncT7LTVI_440MudQyCToZLe3xCwgp4BWRes,2932
3
+ tigrbl_engine_dataframe/df_session.py,sha256=C1o75BirKA4spgKkytvi2M0AjJFgQl5QtqVLveB11jU,16875
4
+ tigrbl_engine_dataframe-0.1.10.dev1.dist-info/METADATA,sha256=SQsL_AF8DlO6AACSqAISW_OZuqmyG-RgjyzAAq7r66M,1939
5
+ tigrbl_engine_dataframe-0.1.10.dev1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
6
+ tigrbl_engine_dataframe-0.1.10.dev1.dist-info/entry_points.txt,sha256=YR-M4grLtN3mqwES7xGKYSBEA1jZ_jStk3R_mcFXe-U,61
7
+ tigrbl_engine_dataframe-0.1.10.dev1.dist-info/licenses/LICENSE,sha256=sLrcRvv1U-v7jmeeYS7dKLlr39FU5lLnR-p6Swg9YdU,766
8
+ tigrbl_engine_dataframe-0.1.10.dev1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [tigrbl.engine]
2
+ dataframe = tigrbl_engine_dataframe:register
@@ -0,0 +1,19 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ Copyright 2025 Tigrbl
8
+
9
+ Licensed under the Apache License, Version 2.0 (the "License");
10
+ you may not use this file except in compliance with the License.
11
+ You may obtain a copy of the License at
12
+
13
+ http://www.apache.org/licenses/LICENSE-2.0
14
+
15
+ Unless required by applicable law or agreed to in writing, software
16
+ distributed under the License is distributed on an "AS IS" BASIS,
17
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
+ See the License for the specific language governing permissions and
19
+ limitations under the License.