tigrbl 0.0.1.dev1__py3-none-any.whl → 0.3.0.dev3__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.
Files changed (252) hide show
  1. tigrbl/README.md +94 -0
  2. tigrbl/__init__.py +139 -14
  3. tigrbl/api/__init__.py +6 -0
  4. tigrbl/api/_api.py +72 -0
  5. tigrbl/api/api_spec.py +30 -0
  6. tigrbl/api/mro_collect.py +43 -0
  7. tigrbl/api/shortcuts.py +56 -0
  8. tigrbl/api/tigrbl_api.py +286 -0
  9. tigrbl/app/__init__.py +0 -0
  10. tigrbl/app/_app.py +61 -0
  11. tigrbl/app/app_spec.py +42 -0
  12. tigrbl/app/mro_collect.py +67 -0
  13. tigrbl/app/shortcuts.py +65 -0
  14. tigrbl/app/tigrbl_app.py +314 -0
  15. tigrbl/bindings/__init__.py +73 -0
  16. tigrbl/bindings/api/__init__.py +12 -0
  17. tigrbl/bindings/api/common.py +109 -0
  18. tigrbl/bindings/api/include.py +256 -0
  19. tigrbl/bindings/api/resource_proxy.py +149 -0
  20. tigrbl/bindings/api/rpc.py +111 -0
  21. tigrbl/bindings/columns.py +49 -0
  22. tigrbl/bindings/handlers/__init__.py +11 -0
  23. tigrbl/bindings/handlers/builder.py +119 -0
  24. tigrbl/bindings/handlers/ctx.py +74 -0
  25. tigrbl/bindings/handlers/identifiers.py +228 -0
  26. tigrbl/bindings/handlers/namespaces.py +51 -0
  27. tigrbl/bindings/handlers/steps.py +276 -0
  28. tigrbl/bindings/hooks.py +311 -0
  29. tigrbl/bindings/model.py +194 -0
  30. tigrbl/bindings/model_helpers.py +139 -0
  31. tigrbl/bindings/model_registry.py +77 -0
  32. tigrbl/bindings/rest/__init__.py +7 -0
  33. tigrbl/bindings/rest/attach.py +34 -0
  34. tigrbl/bindings/rest/collection.py +265 -0
  35. tigrbl/bindings/rest/common.py +116 -0
  36. tigrbl/bindings/rest/fastapi.py +76 -0
  37. tigrbl/bindings/rest/helpers.py +119 -0
  38. tigrbl/bindings/rest/io.py +317 -0
  39. tigrbl/bindings/rest/member.py +367 -0
  40. tigrbl/bindings/rest/router.py +292 -0
  41. tigrbl/bindings/rest/routing.py +133 -0
  42. tigrbl/bindings/rpc.py +364 -0
  43. tigrbl/bindings/schemas/__init__.py +11 -0
  44. tigrbl/bindings/schemas/builder.py +348 -0
  45. tigrbl/bindings/schemas/defaults.py +260 -0
  46. tigrbl/bindings/schemas/utils.py +193 -0
  47. tigrbl/column/README.md +62 -0
  48. tigrbl/column/__init__.py +72 -0
  49. tigrbl/column/_column.py +96 -0
  50. tigrbl/column/column_spec.py +40 -0
  51. tigrbl/column/field_spec.py +31 -0
  52. tigrbl/column/infer/__init__.py +25 -0
  53. tigrbl/column/infer/core.py +92 -0
  54. tigrbl/column/infer/jsonhints.py +44 -0
  55. tigrbl/column/infer/planning.py +133 -0
  56. tigrbl/column/infer/types.py +102 -0
  57. tigrbl/column/infer/utils.py +59 -0
  58. tigrbl/column/io_spec.py +133 -0
  59. tigrbl/column/mro_collect.py +59 -0
  60. tigrbl/column/shortcuts.py +89 -0
  61. tigrbl/column/storage_spec.py +65 -0
  62. tigrbl/config/__init__.py +19 -0
  63. tigrbl/config/constants.py +224 -0
  64. tigrbl/config/defaults.py +29 -0
  65. tigrbl/config/resolver.py +295 -0
  66. tigrbl/core/__init__.py +47 -0
  67. tigrbl/core/crud/__init__.py +36 -0
  68. tigrbl/core/crud/bulk.py +168 -0
  69. tigrbl/core/crud/helpers/__init__.py +76 -0
  70. tigrbl/core/crud/helpers/db.py +92 -0
  71. tigrbl/core/crud/helpers/enum.py +86 -0
  72. tigrbl/core/crud/helpers/filters.py +162 -0
  73. tigrbl/core/crud/helpers/model.py +123 -0
  74. tigrbl/core/crud/helpers/normalize.py +99 -0
  75. tigrbl/core/crud/ops.py +235 -0
  76. tigrbl/ddl/__init__.py +344 -0
  77. tigrbl/decorators.py +17 -0
  78. tigrbl/deps/__init__.py +20 -0
  79. tigrbl/deps/fastapi.py +45 -0
  80. tigrbl/deps/favicon.svg +4 -0
  81. tigrbl/deps/jinja.py +27 -0
  82. tigrbl/deps/pydantic.py +10 -0
  83. tigrbl/deps/sqlalchemy.py +94 -0
  84. tigrbl/deps/starlette.py +36 -0
  85. tigrbl/engine/__init__.py +26 -0
  86. tigrbl/engine/_engine.py +130 -0
  87. tigrbl/engine/bind.py +33 -0
  88. tigrbl/engine/builders.py +236 -0
  89. tigrbl/engine/collect.py +111 -0
  90. tigrbl/engine/decorators.py +108 -0
  91. tigrbl/engine/engine_spec.py +261 -0
  92. tigrbl/engine/resolver.py +224 -0
  93. tigrbl/engine/shortcuts.py +216 -0
  94. tigrbl/hook/__init__.py +21 -0
  95. tigrbl/hook/_hook.py +22 -0
  96. tigrbl/hook/decorators.py +28 -0
  97. tigrbl/hook/hook_spec.py +24 -0
  98. tigrbl/hook/mro_collect.py +98 -0
  99. tigrbl/hook/shortcuts.py +44 -0
  100. tigrbl/hook/types.py +76 -0
  101. tigrbl/op/__init__.py +50 -0
  102. tigrbl/op/_op.py +31 -0
  103. tigrbl/op/canonical.py +31 -0
  104. tigrbl/op/collect.py +11 -0
  105. tigrbl/op/decorators.py +238 -0
  106. tigrbl/op/model_registry.py +301 -0
  107. tigrbl/op/mro_collect.py +99 -0
  108. tigrbl/op/resolver.py +216 -0
  109. tigrbl/op/types.py +136 -0
  110. tigrbl/orm/__init__.py +1 -0
  111. tigrbl/orm/mixins/_RowBound.py +83 -0
  112. tigrbl/orm/mixins/__init__.py +95 -0
  113. tigrbl/orm/mixins/bootstrappable.py +113 -0
  114. tigrbl/orm/mixins/bound.py +47 -0
  115. tigrbl/orm/mixins/edges.py +40 -0
  116. tigrbl/orm/mixins/fields.py +165 -0
  117. tigrbl/orm/mixins/hierarchy.py +54 -0
  118. tigrbl/orm/mixins/key_digest.py +44 -0
  119. tigrbl/orm/mixins/lifecycle.py +115 -0
  120. tigrbl/orm/mixins/locks.py +51 -0
  121. tigrbl/orm/mixins/markers.py +16 -0
  122. tigrbl/orm/mixins/operations.py +57 -0
  123. tigrbl/orm/mixins/ownable.py +337 -0
  124. tigrbl/orm/mixins/principals.py +98 -0
  125. tigrbl/orm/mixins/tenant_bound.py +301 -0
  126. tigrbl/orm/mixins/upsertable.py +111 -0
  127. tigrbl/orm/mixins/utils.py +49 -0
  128. tigrbl/orm/tables/__init__.py +72 -0
  129. tigrbl/orm/tables/_base.py +8 -0
  130. tigrbl/orm/tables/audit.py +56 -0
  131. tigrbl/orm/tables/client.py +25 -0
  132. tigrbl/orm/tables/group.py +29 -0
  133. tigrbl/orm/tables/org.py +30 -0
  134. tigrbl/orm/tables/rbac.py +76 -0
  135. tigrbl/orm/tables/status.py +106 -0
  136. tigrbl/orm/tables/tenant.py +22 -0
  137. tigrbl/orm/tables/user.py +39 -0
  138. tigrbl/response/README.md +34 -0
  139. tigrbl/response/__init__.py +33 -0
  140. tigrbl/response/bind.py +12 -0
  141. tigrbl/response/decorators.py +37 -0
  142. tigrbl/response/resolver.py +83 -0
  143. tigrbl/response/shortcuts.py +144 -0
  144. tigrbl/response/types.py +49 -0
  145. tigrbl/rest/__init__.py +27 -0
  146. tigrbl/runtime/README.md +129 -0
  147. tigrbl/runtime/__init__.py +20 -0
  148. tigrbl/runtime/atoms/__init__.py +102 -0
  149. tigrbl/runtime/atoms/emit/__init__.py +42 -0
  150. tigrbl/runtime/atoms/emit/paired_post.py +158 -0
  151. tigrbl/runtime/atoms/emit/paired_pre.py +106 -0
  152. tigrbl/runtime/atoms/emit/readtime_alias.py +120 -0
  153. tigrbl/runtime/atoms/out/__init__.py +38 -0
  154. tigrbl/runtime/atoms/out/masking.py +135 -0
  155. tigrbl/runtime/atoms/refresh/__init__.py +38 -0
  156. tigrbl/runtime/atoms/refresh/demand.py +130 -0
  157. tigrbl/runtime/atoms/resolve/__init__.py +40 -0
  158. tigrbl/runtime/atoms/resolve/assemble.py +167 -0
  159. tigrbl/runtime/atoms/resolve/paired_gen.py +147 -0
  160. tigrbl/runtime/atoms/response/__init__.py +17 -0
  161. tigrbl/runtime/atoms/response/negotiate.py +30 -0
  162. tigrbl/runtime/atoms/response/negotiation.py +43 -0
  163. tigrbl/runtime/atoms/response/render.py +36 -0
  164. tigrbl/runtime/atoms/response/renderer.py +116 -0
  165. tigrbl/runtime/atoms/response/template.py +44 -0
  166. tigrbl/runtime/atoms/response/templates.py +88 -0
  167. tigrbl/runtime/atoms/schema/__init__.py +40 -0
  168. tigrbl/runtime/atoms/schema/collect_in.py +21 -0
  169. tigrbl/runtime/atoms/schema/collect_out.py +21 -0
  170. tigrbl/runtime/atoms/storage/__init__.py +38 -0
  171. tigrbl/runtime/atoms/storage/to_stored.py +167 -0
  172. tigrbl/runtime/atoms/wire/__init__.py +45 -0
  173. tigrbl/runtime/atoms/wire/build_in.py +166 -0
  174. tigrbl/runtime/atoms/wire/build_out.py +87 -0
  175. tigrbl/runtime/atoms/wire/dump.py +206 -0
  176. tigrbl/runtime/atoms/wire/validate_in.py +227 -0
  177. tigrbl/runtime/context.py +206 -0
  178. tigrbl/runtime/errors/__init__.py +61 -0
  179. tigrbl/runtime/errors/converters.py +214 -0
  180. tigrbl/runtime/errors/exceptions.py +124 -0
  181. tigrbl/runtime/errors/mappings.py +71 -0
  182. tigrbl/runtime/errors/utils.py +150 -0
  183. tigrbl/runtime/events.py +209 -0
  184. tigrbl/runtime/executor/__init__.py +6 -0
  185. tigrbl/runtime/executor/guards.py +132 -0
  186. tigrbl/runtime/executor/helpers.py +88 -0
  187. tigrbl/runtime/executor/invoke.py +150 -0
  188. tigrbl/runtime/executor/types.py +84 -0
  189. tigrbl/runtime/kernel.py +628 -0
  190. tigrbl/runtime/labels.py +353 -0
  191. tigrbl/runtime/opview.py +87 -0
  192. tigrbl/runtime/ordering.py +256 -0
  193. tigrbl/runtime/system.py +279 -0
  194. tigrbl/runtime/trace.py +330 -0
  195. tigrbl/schema/__init__.py +38 -0
  196. tigrbl/schema/_schema.py +27 -0
  197. tigrbl/schema/builder/__init__.py +17 -0
  198. tigrbl/schema/builder/build_schema.py +209 -0
  199. tigrbl/schema/builder/cache.py +24 -0
  200. tigrbl/schema/builder/compat.py +16 -0
  201. tigrbl/schema/builder/extras.py +85 -0
  202. tigrbl/schema/builder/helpers.py +51 -0
  203. tigrbl/schema/builder/list_params.py +117 -0
  204. tigrbl/schema/builder/strip_parent_fields.py +70 -0
  205. tigrbl/schema/collect.py +55 -0
  206. tigrbl/schema/decorators.py +68 -0
  207. tigrbl/schema/get_schema.py +86 -0
  208. tigrbl/schema/schema_spec.py +20 -0
  209. tigrbl/schema/shortcuts.py +42 -0
  210. tigrbl/schema/types.py +34 -0
  211. tigrbl/schema/utils.py +143 -0
  212. tigrbl/shortcuts.py +22 -0
  213. tigrbl/specs.py +44 -0
  214. tigrbl/system/__init__.py +12 -0
  215. tigrbl/system/diagnostics/__init__.py +24 -0
  216. tigrbl/system/diagnostics/compat.py +31 -0
  217. tigrbl/system/diagnostics/healthz.py +41 -0
  218. tigrbl/system/diagnostics/hookz.py +51 -0
  219. tigrbl/system/diagnostics/kernelz.py +20 -0
  220. tigrbl/system/diagnostics/methodz.py +43 -0
  221. tigrbl/system/diagnostics/router.py +73 -0
  222. tigrbl/system/diagnostics/utils.py +43 -0
  223. tigrbl/table/__init__.py +9 -0
  224. tigrbl/table/_base.py +237 -0
  225. tigrbl/table/_table.py +54 -0
  226. tigrbl/table/mro_collect.py +69 -0
  227. tigrbl/table/shortcuts.py +57 -0
  228. tigrbl/table/table_spec.py +28 -0
  229. tigrbl/transport/__init__.py +74 -0
  230. tigrbl/transport/jsonrpc/__init__.py +19 -0
  231. tigrbl/transport/jsonrpc/dispatcher.py +352 -0
  232. tigrbl/transport/jsonrpc/helpers.py +115 -0
  233. tigrbl/transport/jsonrpc/models.py +41 -0
  234. tigrbl/transport/rest/__init__.py +25 -0
  235. tigrbl/transport/rest/aggregator.py +132 -0
  236. tigrbl/types/__init__.py +174 -0
  237. tigrbl/types/allow_anon_provider.py +19 -0
  238. tigrbl/types/authn_abc.py +30 -0
  239. tigrbl/types/nested_path_provider.py +22 -0
  240. tigrbl/types/op.py +35 -0
  241. tigrbl/types/op_config_provider.py +17 -0
  242. tigrbl/types/op_verb_alias_provider.py +33 -0
  243. tigrbl/types/request_extras_provider.py +22 -0
  244. tigrbl/types/response_extras_provider.py +22 -0
  245. tigrbl/types/table_config_provider.py +13 -0
  246. tigrbl-0.3.0.dev3.dist-info/LICENSE +201 -0
  247. tigrbl-0.3.0.dev3.dist-info/METADATA +501 -0
  248. tigrbl-0.3.0.dev3.dist-info/RECORD +249 -0
  249. tigrbl/ExampleAgent.py +0 -1
  250. tigrbl-0.0.1.dev1.dist-info/METADATA +0 -18
  251. tigrbl-0.0.1.dev1.dist-info/RECORD +0 -5
  252. {tigrbl-0.0.1.dev1.dist-info → tigrbl-0.3.0.dev3.dist-info}/WHEEL +0 -0
tigrbl/ddl/__init__.py ADDED
@@ -0,0 +1,344 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import inspect
5
+ from pathlib import Path
6
+ from typing import Any, Dict, Iterable, Mapping, Optional, Sequence
7
+ from types import SimpleNamespace
8
+
9
+ from ..engine import resolver as _resolver
10
+
11
+ try: # pragma: no cover
12
+ from sqlalchemy import event, text
13
+ from sqlalchemy.engine import Engine
14
+ from sqlalchemy.schema import CreateSchema # type: ignore
15
+ except Exception: # pragma: no cover
16
+ event = text = CreateSchema = None # type: ignore
17
+ Engine = object # type: ignore
18
+
19
+ from ..config.constants import __SAFE_IDENT__
20
+
21
+ __all__ = [
22
+ "register_sqlite_attach",
23
+ "ensure_schemas",
24
+ "bootstrap_dbschema",
25
+ "sqlite_default_attach_map",
26
+ "initialize",
27
+ ]
28
+
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # SQLite helpers
32
+ # ---------------------------------------------------------------------------
33
+
34
+
35
+ def _quote_ident_sqlite(name: str) -> str:
36
+ if __SAFE_IDENT__.match(name or ""):
37
+ return name
38
+ return '"' + (name or "").replace('"', '""') + '"'
39
+
40
+
41
+ def _attached_names_sqlite(dbapi_conn: Any) -> set[str]:
42
+ names: set[str] = set()
43
+ cur = None
44
+ try:
45
+ cur = dbapi_conn.cursor()
46
+ cur.execute("PRAGMA database_list")
47
+ for row in cur.fetchall():
48
+ names.add(str(row[1]))
49
+ finally:
50
+ try:
51
+ cur and cur.close()
52
+ except Exception:
53
+ pass
54
+ return names
55
+
56
+
57
+ def _attach_sqlite_dbapi(dbapi_conn: Any, attachments: Mapping[str, str]) -> None:
58
+ cur = None
59
+ try:
60
+ existing = _attached_names_sqlite(dbapi_conn)
61
+ cur = dbapi_conn.cursor()
62
+ try:
63
+ cur.execute("PRAGMA foreign_keys=ON")
64
+ except Exception:
65
+ pass
66
+ for schema, path in (attachments or {}).items():
67
+ if not path or schema in existing:
68
+ continue
69
+ ident = _quote_ident_sqlite(schema)
70
+ try:
71
+ cur.execute(f"ATTACH DATABASE ? AS {ident}", (path,))
72
+ except Exception:
73
+ cur.execute(f"ATTACH DATABASE '{path}' AS {ident}") # nosec: application-provided paths
74
+ finally:
75
+ try:
76
+ cur and cur.close()
77
+ except Exception:
78
+ pass
79
+
80
+
81
+ # ---------------------------------------------------------------------------
82
+ # Public helpers
83
+ # ---------------------------------------------------------------------------
84
+
85
+
86
+ def sqlite_default_attach_map(engine: Engine, schemas: Iterable[str]) -> Dict[str, str]:
87
+ """Return a deterministic SQLite ATTACH map for ``schemas``."""
88
+ db = getattr(getattr(engine, "url", None), "database", None) or ":memory:"
89
+ if db == ":memory:" or str(db).startswith("file::memory:"):
90
+ return {s: ":memory:" for s in schemas}
91
+ p = Path(db)
92
+ suffix = p.suffix if p.suffix else ".db"
93
+ return {s: str(p.with_name(f"{p.stem}__{s}{suffix}")) for s in schemas}
94
+
95
+
96
+ def register_sqlite_attach(engine: Engine, attachments: Mapping[str, str]) -> Any:
97
+ if (
98
+ not hasattr(engine, "dialect")
99
+ or getattr(engine.dialect, "name", "") != "sqlite"
100
+ ):
101
+ return None
102
+
103
+ def _connect_listener(dbapi_conn, _): # type: ignore[override]
104
+ try:
105
+ _attach_sqlite_dbapi(dbapi_conn, attachments)
106
+ except Exception:
107
+ pass
108
+
109
+ event.listen(engine, "connect", _connect_listener)
110
+ return _connect_listener
111
+
112
+
113
+ def ensure_schemas(engine: Engine, schemas: Iterable[str]) -> Sequence[str]:
114
+ if not schemas:
115
+ return tuple()
116
+ if not hasattr(engine, "dialect"):
117
+ return tuple(schemas)
118
+
119
+ dialect = getattr(engine.dialect, "name", "")
120
+ attempted: list[str] = []
121
+
122
+ if dialect == "sqlite":
123
+ return tuple()
124
+
125
+ if text is None: # pragma: no cover
126
+ raise RuntimeError("SQLAlchemy is required for ensure_schemas")
127
+
128
+ try:
129
+ with engine.begin() as conn:
130
+ for name in dict.fromkeys(schemas).keys():
131
+ if not name:
132
+ continue
133
+ attempted.append(name)
134
+ try:
135
+ conn.execute(CreateSchema(name)) # type: ignore[arg-type]
136
+ except Exception:
137
+ try:
138
+ if dialect in ("postgresql", "redshift"):
139
+ conn.execute(text(f'CREATE SCHEMA IF NOT EXISTS "{name}"'))
140
+ elif dialect in ("mysql", "mariadb"):
141
+ conn.execute(text(f"CREATE SCHEMA IF NOT EXISTS `{name}`"))
142
+ elif dialect in ("mssql", "sqlserver"):
143
+ conn.execute(
144
+ text(
145
+ "IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = :n) "
146
+ "EXEC('CREATE SCHEMA ' + QUOTENAME(:n))"
147
+ ),
148
+ {"n": name},
149
+ )
150
+ else:
151
+ conn.execute(text(f"CREATE SCHEMA IF NOT EXISTS {name}"))
152
+ except Exception:
153
+ pass
154
+ except Exception:
155
+ pass
156
+
157
+ return tuple(attempted)
158
+
159
+
160
+ def bootstrap_dbschema(
161
+ engine: Engine,
162
+ *,
163
+ schemas: Optional[Iterable[str]] = None,
164
+ sqlite_attachments: Optional[Mapping[str, str]] = None,
165
+ immediate: bool = True,
166
+ ) -> Dict[str, Any]:
167
+ attempted = tuple()
168
+ if schemas:
169
+ attempted = ensure_schemas(engine, schemas)
170
+
171
+ listener = None
172
+ if sqlite_attachments:
173
+ listener = register_sqlite_attach(engine, sqlite_attachments)
174
+ if immediate and getattr(engine.dialect, "name", "") == "sqlite":
175
+ try:
176
+ with engine.connect() as conn:
177
+ dbapi = getattr(conn, "connection", None)
178
+ if dbapi is not None:
179
+ _attach_sqlite_dbapi(dbapi, sqlite_attachments)
180
+ except Exception:
181
+ pass
182
+
183
+ return {
184
+ "attempted_schemas": attempted,
185
+ "sqlite_attachments": dict(sqlite_attachments or {}),
186
+ "listener": listener,
187
+ }
188
+
189
+
190
+ # ---------------------------------------------------------------------------
191
+ # Internal creation helper
192
+ # ---------------------------------------------------------------------------
193
+
194
+
195
+ def _create_all_on_bind(
196
+ bind,
197
+ *,
198
+ schemas: Iterable[str] | None = None,
199
+ sqlite_attachments: Mapping[str, str] | None = None,
200
+ tables: Iterable[Any] | None = None,
201
+ ) -> None:
202
+ engine = getattr(bind, "engine", bind)
203
+ tables = list(tables or [])
204
+
205
+ schema_names = set(schemas or [])
206
+ for t in tables:
207
+ if getattr(t, "schema", None):
208
+ schema_names.add(t.schema)
209
+
210
+ attachments = sqlite_attachments
211
+ if attachments is None and getattr(engine.dialect, "name", "") == "sqlite":
212
+ if schema_names:
213
+ attachments = sqlite_default_attach_map(engine, schema_names)
214
+
215
+ if attachments:
216
+ bootstrap_dbschema(
217
+ engine,
218
+ schemas=schema_names,
219
+ sqlite_attachments=attachments,
220
+ immediate=True,
221
+ )
222
+ else:
223
+ ensure_schemas(engine, schema_names)
224
+
225
+ by_meta: dict[Any, list[Any]] = {}
226
+ for t in tables:
227
+ by_meta.setdefault(t.metadata, []).append(t)
228
+ for md, group in by_meta.items():
229
+ md.create_all(bind=bind, checkfirst=True, tables=group)
230
+
231
+
232
+ # ---------------------------------------------------------------------------
233
+ # Public initialize
234
+ # ---------------------------------------------------------------------------
235
+
236
+
237
+ def initialize(
238
+ obj: Any,
239
+ *,
240
+ schemas: Iterable[str] | None = None,
241
+ sqlite_attachments: Mapping[str, str] | None = None,
242
+ tables: Iterable[Any] | None = None,
243
+ ):
244
+ if getattr(obj, "_ddl_executed", False):
245
+ return
246
+
247
+ ts = list(tables or [])
248
+ if not ts:
249
+ if hasattr(obj, "_collect_tables"):
250
+ ts = list(obj._collect_tables()) # type: ignore[attr-defined]
251
+ elif hasattr(obj, "__table__"):
252
+ ts = [obj.__table__] # type: ignore[attr-defined]
253
+
254
+ kwargs: Dict[str, Any] = {}
255
+ if hasattr(obj, "_collect_tables"):
256
+ kwargs["api"] = obj
257
+ elif hasattr(obj, "__table__"):
258
+ kwargs["model"] = obj
259
+
260
+ prov = _resolver.resolve_provider(**kwargs)
261
+ if prov is None:
262
+ raise ValueError("Engine provider is not configured")
263
+
264
+ def _bootstrap(db):
265
+ bind = db.get_bind() if hasattr(db, "get_bind") else db
266
+ _create_all_on_bind(
267
+ bind,
268
+ schemas=schemas,
269
+ sqlite_attachments=sqlite_attachments,
270
+ tables=ts,
271
+ )
272
+
273
+ if hasattr(obj, "models"):
274
+ tables_map = {
275
+ name: getattr(m, "__table__", None)
276
+ for name, m in getattr(obj, "models").items()
277
+ if hasattr(m, "__table__")
278
+ }
279
+ existing = getattr(obj, "tables", None)
280
+ if isinstance(existing, dict):
281
+ existing.update(tables_map)
282
+ elif existing is not None:
283
+ for k, v in tables_map.items():
284
+ setattr(existing, k, v)
285
+ else:
286
+ setattr(obj, "tables", SimpleNamespace(**tables_map))
287
+
288
+ try:
289
+ asyncio.get_running_loop()
290
+ except RuntimeError:
291
+ # No running event loop; fall back to fully synchronous bootstrap
292
+ with next(prov.get_db()) as db:
293
+ _bootstrap(db)
294
+ setattr(obj, "_ddl_executed", True)
295
+ return
296
+ else:
297
+ # If we're already inside an event loop but the provider is synchronous
298
+ # (i.e. ``get_db`` is neither coroutine nor async generator), we can
299
+ # bootstrap synchronously as well. This mirrors previous "initialize_sync"
300
+ # behaviour and allows ``initialize()`` to be invoked without ``await``
301
+ # from async contexts when using sync engines.
302
+ if not inspect.iscoroutinefunction(
303
+ prov.get_db
304
+ ) and not inspect.isasyncgenfunction(prov.get_db):
305
+ with next(prov.get_db()) as db:
306
+ _bootstrap(db)
307
+ setattr(obj, "_ddl_executed", True)
308
+
309
+ class _Completed:
310
+ def __await__(self): # pragma: no cover - trivial
311
+ if False:
312
+ yield None
313
+ return None
314
+
315
+ return _Completed()
316
+
317
+ async def _inner():
318
+ if inspect.isasyncgenfunction(prov.get_db):
319
+ async for adb in prov.get_db():
320
+ await adb.run_sync(_bootstrap)
321
+ break
322
+ else:
323
+ gen = prov.get_db()
324
+ db = next(gen)
325
+ try:
326
+ if hasattr(db, "run_sync"):
327
+ await db.run_sync(_bootstrap)
328
+ else:
329
+ bind = db.get_bind()
330
+ await asyncio.to_thread(
331
+ _create_all_on_bind,
332
+ bind,
333
+ schemas=schemas,
334
+ sqlite_attachments=sqlite_attachments,
335
+ tables=ts,
336
+ )
337
+ finally:
338
+ try:
339
+ next(gen)
340
+ except StopIteration:
341
+ pass
342
+ setattr(obj, "_ddl_executed", True)
343
+
344
+ return _inner()
tigrbl/decorators.py ADDED
@@ -0,0 +1,17 @@
1
+ """Compatibility layer for decorator imports."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .op.decorators import * # noqa: F401,F403
6
+ from .hook.decorators import * # noqa: F401,F403
7
+ from .schema.decorators import schema_ctx
8
+ from .engine.decorators import engine_ctx
9
+ from .op.decorators import __all__ as _op_all
10
+ from .hook.decorators import __all__ as _hook_all
11
+
12
+ __all__ = [
13
+ *_op_all,
14
+ *_hook_all,
15
+ "schema_ctx",
16
+ "engine_ctx",
17
+ ]
@@ -0,0 +1,20 @@
1
+ # ── Third-party Dependencies Re-exports ─────────────────────────────────
2
+ """
3
+ Centralized third-party dependency imports for tigrbl.
4
+
5
+ This module provides a single location for all third-party dependencies,
6
+ making it easier to manage versions and potential replacements.
7
+ """
8
+
9
+ # Re-export all SQLAlchemy dependencies
10
+ from .sqlalchemy import relationship # noqa: F401
11
+ from .sqlalchemy import * # noqa: F403, F401
12
+
13
+ # Re-export all Pydantic dependencies
14
+ from .pydantic import * # noqa: F403, F401
15
+
16
+ # Re-export all FastAPI dependencies
17
+ from .fastapi import * # noqa: F403, F401
18
+
19
+ # Note: starlette.py is reserved for future Starlette-specific imports
20
+ # if we need to import directly from Starlette rather than through FastAPI
tigrbl/deps/fastapi.py ADDED
@@ -0,0 +1,45 @@
1
+ # ── FastAPI Imports ─────────────────────────────────────────────────────
2
+ from fastapi import (
3
+ APIRouter,
4
+ FastAPI,
5
+ Security,
6
+ Depends,
7
+ Request,
8
+ Response,
9
+ Path as FastAPIPath,
10
+ Body,
11
+ HTTPException,
12
+ )
13
+ from fastapi.responses import FileResponse
14
+ from pathlib import Path as FilePath
15
+
16
+ Router = APIRouter
17
+ Path = FastAPIPath
18
+
19
+ FAVICON_PATH = FilePath(__file__).with_name("favicon.svg")
20
+
21
+
22
+ def App(*args, **kwargs):
23
+ app = FastAPI(*args, **kwargs)
24
+
25
+ @app.get("/favicon.ico", include_in_schema=False)
26
+ async def favicon() -> FileResponse: # pragma: no cover - simple static route
27
+ return FileResponse(FAVICON_PATH, media_type="image/svg+xml")
28
+
29
+ return app
30
+
31
+
32
+ # ── Public Exports ───────────────────────────────────────────────────────
33
+ __all__ = [
34
+ "APIRouter",
35
+ "Router",
36
+ "FastAPI",
37
+ "Security",
38
+ "Depends",
39
+ "Request",
40
+ "Response",
41
+ "Path",
42
+ "Body",
43
+ "HTTPException",
44
+ "App",
45
+ ]
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <rect width="32" height="32" fill="#4f46e5"/>
3
+ <text x="16" y="21" font-size="18" text-anchor="middle" fill="#fff">A</text>
4
+ </svg>
tigrbl/deps/jinja.py ADDED
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ try: # pragma: no cover - optional dependency
4
+ from jinja2 import (
5
+ Environment,
6
+ FileSystemLoader,
7
+ PackageLoader,
8
+ ChoiceLoader,
9
+ select_autoescape,
10
+ TemplateNotFound,
11
+ )
12
+ except Exception: # pragma: no cover
13
+ Environment = None # type: ignore
14
+ FileSystemLoader = None # type: ignore
15
+ PackageLoader = None # type: ignore
16
+ ChoiceLoader = None # type: ignore
17
+ select_autoescape = None # type: ignore
18
+ TemplateNotFound = None # type: ignore
19
+
20
+ __all__ = [
21
+ "Environment",
22
+ "FileSystemLoader",
23
+ "PackageLoader",
24
+ "ChoiceLoader",
25
+ "select_autoescape",
26
+ "TemplateNotFound",
27
+ ]
@@ -0,0 +1,10 @@
1
+ # ── Pydantic Imports ────────────────────────────────────────────────────
2
+ from pydantic import BaseModel, Field, ValidationError
3
+
4
+
5
+ # ── Public Exports ───────────────────────────────────────────────────────
6
+ __all__ = [
7
+ "BaseModel",
8
+ "Field",
9
+ "ValidationError",
10
+ ]
@@ -0,0 +1,94 @@
1
+ # ── SQLAlchemy Core ──────────────────────────────────────────────────────
2
+ from sqlalchemy import (
3
+ Boolean,
4
+ Column,
5
+ DateTime as _DateTime,
6
+ Enum as SAEnum,
7
+ Text,
8
+ ForeignKey,
9
+ Index,
10
+ Integer,
11
+ JSON,
12
+ Numeric,
13
+ String,
14
+ LargeBinary,
15
+ UniqueConstraint,
16
+ CheckConstraint,
17
+ create_engine,
18
+ event,
19
+ )
20
+
21
+ # ── SQLAlchemy PostgreSQL Dialect ────────────────────────────────────────
22
+ from sqlalchemy.dialects.postgresql import (
23
+ ARRAY,
24
+ ENUM as PgEnum,
25
+ JSONB,
26
+ UUID as _PgUUID,
27
+ TSVECTOR,
28
+ )
29
+
30
+ # ── SQLAlchemy ORM ───────────────────────────────────────────────────────
31
+ from sqlalchemy.orm import (
32
+ Mapped,
33
+ declarative_mixin,
34
+ declared_attr,
35
+ foreign,
36
+ mapped_column,
37
+ relationship,
38
+ remote,
39
+ column_property,
40
+ Session,
41
+ sessionmaker,
42
+ InstrumentedAttribute,
43
+ )
44
+
45
+ from sqlalchemy.pool import StaticPool
46
+
47
+ # ── SQLAlchemy Extensions ────────────────────────────────────────────────
48
+ from sqlalchemy.ext.mutable import MutableDict, MutableList
49
+ from sqlalchemy.ext.hybrid import hybrid_property
50
+
51
+
52
+ # ── Public Exports ───────────────────────────────────────────────────────
53
+ __all__ = [
54
+ # Core types
55
+ "Boolean",
56
+ "Column",
57
+ "_DateTime",
58
+ "SAEnum",
59
+ "Text",
60
+ "ForeignKey",
61
+ "Index",
62
+ "Integer",
63
+ "JSON",
64
+ "Numeric",
65
+ "String",
66
+ "LargeBinary",
67
+ "UniqueConstraint",
68
+ "CheckConstraint",
69
+ "create_engine",
70
+ "StaticPool",
71
+ "event",
72
+ # PostgreSQL dialect
73
+ "ARRAY",
74
+ "PgEnum",
75
+ "JSONB",
76
+ "_PgUUID",
77
+ "TSVECTOR",
78
+ # ORM
79
+ "Mapped",
80
+ "declarative_mixin",
81
+ "declared_attr",
82
+ "foreign",
83
+ "mapped_column",
84
+ "relationship",
85
+ "remote",
86
+ "column_property",
87
+ "Session",
88
+ "sessionmaker",
89
+ "InstrumentedAttribute",
90
+ # Extensions
91
+ "MutableDict",
92
+ "MutableList",
93
+ "hybrid_property",
94
+ ]
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ try: # pragma: no cover - optional runtime dependency
4
+ from starlette.background import BackgroundTask
5
+ from starlette.requests import Request
6
+ from starlette.responses import (
7
+ Response,
8
+ JSONResponse,
9
+ HTMLResponse,
10
+ PlainTextResponse,
11
+ StreamingResponse,
12
+ FileResponse,
13
+ RedirectResponse,
14
+ )
15
+ except Exception: # pragma: no cover
16
+ BackgroundTask = None # type: ignore
17
+ Request = None # type: ignore
18
+ Response = None # type: ignore
19
+ JSONResponse = None # type: ignore
20
+ HTMLResponse = None # type: ignore
21
+ PlainTextResponse = None # type: ignore
22
+ StreamingResponse = None # type: ignore
23
+ FileResponse = None # type: ignore
24
+ RedirectResponse = None # type: ignore
25
+
26
+ __all__ = [
27
+ "BackgroundTask",
28
+ "Request",
29
+ "Response",
30
+ "JSONResponse",
31
+ "HTMLResponse",
32
+ "PlainTextResponse",
33
+ "StreamingResponse",
34
+ "FileResponse",
35
+ "RedirectResponse",
36
+ ]
@@ -0,0 +1,26 @@
1
+ """Engine utilities for collecting and binding database providers."""
2
+
3
+ from .bind import bind, install_from_objects
4
+ from .collect import collect_engine_config
5
+ from .builders import (
6
+ async_postgres_engine,
7
+ async_sqlite_engine,
8
+ blocking_postgres_engine,
9
+ blocking_sqlite_engine,
10
+ HybridSession,
11
+ )
12
+ from ._engine import Engine
13
+ from .shortcuts import engine
14
+
15
+ __all__ = [
16
+ "collect_engine_config",
17
+ "bind",
18
+ "install_from_objects",
19
+ "blocking_sqlite_engine",
20
+ "blocking_postgres_engine",
21
+ "async_sqlite_engine",
22
+ "async_postgres_engine",
23
+ "HybridSession",
24
+ "Engine",
25
+ "engine",
26
+ ]