tigrbl 0.0.1.dev1__py3-none-any.whl → 0.3.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.
Files changed (269) 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 +97 -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 +291 -0
  9. tigrbl/app/__init__.py +0 -0
  10. tigrbl/app/_app.py +86 -0
  11. tigrbl/app/_model_registry.py +41 -0
  12. tigrbl/app/app_spec.py +42 -0
  13. tigrbl/app/mro_collect.py +67 -0
  14. tigrbl/app/shortcuts.py +65 -0
  15. tigrbl/app/tigrbl_app.py +319 -0
  16. tigrbl/bindings/__init__.py +73 -0
  17. tigrbl/bindings/api/__init__.py +12 -0
  18. tigrbl/bindings/api/common.py +109 -0
  19. tigrbl/bindings/api/include.py +256 -0
  20. tigrbl/bindings/api/resource_proxy.py +149 -0
  21. tigrbl/bindings/api/rpc.py +111 -0
  22. tigrbl/bindings/columns.py +49 -0
  23. tigrbl/bindings/handlers/__init__.py +11 -0
  24. tigrbl/bindings/handlers/builder.py +119 -0
  25. tigrbl/bindings/handlers/ctx.py +74 -0
  26. tigrbl/bindings/handlers/identifiers.py +228 -0
  27. tigrbl/bindings/handlers/namespaces.py +51 -0
  28. tigrbl/bindings/handlers/steps.py +276 -0
  29. tigrbl/bindings/hooks.py +311 -0
  30. tigrbl/bindings/model.py +194 -0
  31. tigrbl/bindings/model_helpers.py +139 -0
  32. tigrbl/bindings/model_registry.py +77 -0
  33. tigrbl/bindings/rest/__init__.py +7 -0
  34. tigrbl/bindings/rest/attach.py +34 -0
  35. tigrbl/bindings/rest/collection.py +286 -0
  36. tigrbl/bindings/rest/common.py +120 -0
  37. tigrbl/bindings/rest/fastapi.py +76 -0
  38. tigrbl/bindings/rest/helpers.py +119 -0
  39. tigrbl/bindings/rest/io.py +317 -0
  40. tigrbl/bindings/rest/io_headers.py +49 -0
  41. tigrbl/bindings/rest/member.py +386 -0
  42. tigrbl/bindings/rest/router.py +296 -0
  43. tigrbl/bindings/rest/routing.py +153 -0
  44. tigrbl/bindings/rpc.py +364 -0
  45. tigrbl/bindings/schemas/__init__.py +11 -0
  46. tigrbl/bindings/schemas/builder.py +348 -0
  47. tigrbl/bindings/schemas/defaults.py +260 -0
  48. tigrbl/bindings/schemas/utils.py +193 -0
  49. tigrbl/column/README.md +62 -0
  50. tigrbl/column/__init__.py +72 -0
  51. tigrbl/column/_column.py +96 -0
  52. tigrbl/column/column_spec.py +40 -0
  53. tigrbl/column/field_spec.py +31 -0
  54. tigrbl/column/infer/__init__.py +25 -0
  55. tigrbl/column/infer/core.py +92 -0
  56. tigrbl/column/infer/jsonhints.py +44 -0
  57. tigrbl/column/infer/planning.py +133 -0
  58. tigrbl/column/infer/types.py +102 -0
  59. tigrbl/column/infer/utils.py +59 -0
  60. tigrbl/column/io_spec.py +136 -0
  61. tigrbl/column/mro_collect.py +59 -0
  62. tigrbl/column/shortcuts.py +89 -0
  63. tigrbl/column/storage_spec.py +65 -0
  64. tigrbl/config/__init__.py +19 -0
  65. tigrbl/config/constants.py +224 -0
  66. tigrbl/config/defaults.py +29 -0
  67. tigrbl/config/resolver.py +295 -0
  68. tigrbl/core/__init__.py +47 -0
  69. tigrbl/core/crud/__init__.py +36 -0
  70. tigrbl/core/crud/bulk.py +168 -0
  71. tigrbl/core/crud/helpers/__init__.py +76 -0
  72. tigrbl/core/crud/helpers/db.py +92 -0
  73. tigrbl/core/crud/helpers/enum.py +86 -0
  74. tigrbl/core/crud/helpers/filters.py +162 -0
  75. tigrbl/core/crud/helpers/model.py +123 -0
  76. tigrbl/core/crud/helpers/normalize.py +99 -0
  77. tigrbl/core/crud/ops.py +235 -0
  78. tigrbl/ddl/__init__.py +344 -0
  79. tigrbl/decorators.py +17 -0
  80. tigrbl/deps/__init__.py +20 -0
  81. tigrbl/deps/fastapi.py +45 -0
  82. tigrbl/deps/favicon.svg +4 -0
  83. tigrbl/deps/jinja.py +27 -0
  84. tigrbl/deps/pydantic.py +10 -0
  85. tigrbl/deps/sqlalchemy.py +94 -0
  86. tigrbl/deps/starlette.py +36 -0
  87. tigrbl/engine/__init__.py +45 -0
  88. tigrbl/engine/_engine.py +144 -0
  89. tigrbl/engine/bind.py +33 -0
  90. tigrbl/engine/builders.py +236 -0
  91. tigrbl/engine/capabilities.py +29 -0
  92. tigrbl/engine/collect.py +111 -0
  93. tigrbl/engine/decorators.py +110 -0
  94. tigrbl/engine/docs/PLUGINS.md +49 -0
  95. tigrbl/engine/engine_spec.py +355 -0
  96. tigrbl/engine/plugins.py +52 -0
  97. tigrbl/engine/registry.py +36 -0
  98. tigrbl/engine/resolver.py +224 -0
  99. tigrbl/engine/shortcuts.py +216 -0
  100. tigrbl/hook/__init__.py +21 -0
  101. tigrbl/hook/_hook.py +22 -0
  102. tigrbl/hook/decorators.py +28 -0
  103. tigrbl/hook/hook_spec.py +24 -0
  104. tigrbl/hook/mro_collect.py +98 -0
  105. tigrbl/hook/shortcuts.py +44 -0
  106. tigrbl/hook/types.py +76 -0
  107. tigrbl/op/__init__.py +50 -0
  108. tigrbl/op/_op.py +31 -0
  109. tigrbl/op/canonical.py +31 -0
  110. tigrbl/op/collect.py +11 -0
  111. tigrbl/op/decorators.py +238 -0
  112. tigrbl/op/model_registry.py +301 -0
  113. tigrbl/op/mro_collect.py +99 -0
  114. tigrbl/op/resolver.py +216 -0
  115. tigrbl/op/types.py +136 -0
  116. tigrbl/orm/__init__.py +1 -0
  117. tigrbl/orm/mixins/_RowBound.py +83 -0
  118. tigrbl/orm/mixins/__init__.py +95 -0
  119. tigrbl/orm/mixins/bootstrappable.py +113 -0
  120. tigrbl/orm/mixins/bound.py +47 -0
  121. tigrbl/orm/mixins/edges.py +40 -0
  122. tigrbl/orm/mixins/fields.py +165 -0
  123. tigrbl/orm/mixins/hierarchy.py +54 -0
  124. tigrbl/orm/mixins/key_digest.py +44 -0
  125. tigrbl/orm/mixins/lifecycle.py +115 -0
  126. tigrbl/orm/mixins/locks.py +51 -0
  127. tigrbl/orm/mixins/markers.py +16 -0
  128. tigrbl/orm/mixins/operations.py +57 -0
  129. tigrbl/orm/mixins/ownable.py +337 -0
  130. tigrbl/orm/mixins/principals.py +98 -0
  131. tigrbl/orm/mixins/tenant_bound.py +301 -0
  132. tigrbl/orm/mixins/upsertable.py +118 -0
  133. tigrbl/orm/mixins/utils.py +49 -0
  134. tigrbl/orm/tables/__init__.py +72 -0
  135. tigrbl/orm/tables/_base.py +8 -0
  136. tigrbl/orm/tables/audit.py +56 -0
  137. tigrbl/orm/tables/client.py +25 -0
  138. tigrbl/orm/tables/group.py +29 -0
  139. tigrbl/orm/tables/org.py +30 -0
  140. tigrbl/orm/tables/rbac.py +76 -0
  141. tigrbl/orm/tables/status.py +106 -0
  142. tigrbl/orm/tables/tenant.py +22 -0
  143. tigrbl/orm/tables/user.py +39 -0
  144. tigrbl/response/README.md +34 -0
  145. tigrbl/response/__init__.py +33 -0
  146. tigrbl/response/bind.py +12 -0
  147. tigrbl/response/decorators.py +37 -0
  148. tigrbl/response/resolver.py +83 -0
  149. tigrbl/response/shortcuts.py +171 -0
  150. tigrbl/response/types.py +49 -0
  151. tigrbl/rest/__init__.py +27 -0
  152. tigrbl/runtime/README.md +129 -0
  153. tigrbl/runtime/__init__.py +20 -0
  154. tigrbl/runtime/atoms/__init__.py +102 -0
  155. tigrbl/runtime/atoms/emit/__init__.py +42 -0
  156. tigrbl/runtime/atoms/emit/paired_post.py +158 -0
  157. tigrbl/runtime/atoms/emit/paired_pre.py +106 -0
  158. tigrbl/runtime/atoms/emit/readtime_alias.py +120 -0
  159. tigrbl/runtime/atoms/out/__init__.py +38 -0
  160. tigrbl/runtime/atoms/out/masking.py +135 -0
  161. tigrbl/runtime/atoms/refresh/__init__.py +38 -0
  162. tigrbl/runtime/atoms/refresh/demand.py +130 -0
  163. tigrbl/runtime/atoms/resolve/__init__.py +40 -0
  164. tigrbl/runtime/atoms/resolve/assemble.py +167 -0
  165. tigrbl/runtime/atoms/resolve/paired_gen.py +147 -0
  166. tigrbl/runtime/atoms/response/__init__.py +19 -0
  167. tigrbl/runtime/atoms/response/headers_from_payload.py +57 -0
  168. tigrbl/runtime/atoms/response/negotiate.py +30 -0
  169. tigrbl/runtime/atoms/response/negotiation.py +43 -0
  170. tigrbl/runtime/atoms/response/render.py +36 -0
  171. tigrbl/runtime/atoms/response/renderer.py +116 -0
  172. tigrbl/runtime/atoms/response/template.py +44 -0
  173. tigrbl/runtime/atoms/response/templates.py +88 -0
  174. tigrbl/runtime/atoms/schema/__init__.py +40 -0
  175. tigrbl/runtime/atoms/schema/collect_in.py +21 -0
  176. tigrbl/runtime/atoms/schema/collect_out.py +21 -0
  177. tigrbl/runtime/atoms/storage/__init__.py +38 -0
  178. tigrbl/runtime/atoms/storage/to_stored.py +167 -0
  179. tigrbl/runtime/atoms/wire/__init__.py +45 -0
  180. tigrbl/runtime/atoms/wire/build_in.py +166 -0
  181. tigrbl/runtime/atoms/wire/build_out.py +87 -0
  182. tigrbl/runtime/atoms/wire/dump.py +206 -0
  183. tigrbl/runtime/atoms/wire/validate_in.py +227 -0
  184. tigrbl/runtime/context.py +206 -0
  185. tigrbl/runtime/errors/__init__.py +61 -0
  186. tigrbl/runtime/errors/converters.py +214 -0
  187. tigrbl/runtime/errors/exceptions.py +124 -0
  188. tigrbl/runtime/errors/mappings.py +71 -0
  189. tigrbl/runtime/errors/utils.py +150 -0
  190. tigrbl/runtime/events.py +209 -0
  191. tigrbl/runtime/executor/__init__.py +6 -0
  192. tigrbl/runtime/executor/guards.py +132 -0
  193. tigrbl/runtime/executor/helpers.py +88 -0
  194. tigrbl/runtime/executor/invoke.py +150 -0
  195. tigrbl/runtime/executor/types.py +84 -0
  196. tigrbl/runtime/kernel.py +644 -0
  197. tigrbl/runtime/labels.py +353 -0
  198. tigrbl/runtime/opview.py +89 -0
  199. tigrbl/runtime/ordering.py +256 -0
  200. tigrbl/runtime/system.py +279 -0
  201. tigrbl/runtime/trace.py +330 -0
  202. tigrbl/schema/__init__.py +38 -0
  203. tigrbl/schema/_schema.py +27 -0
  204. tigrbl/schema/builder/__init__.py +17 -0
  205. tigrbl/schema/builder/build_schema.py +209 -0
  206. tigrbl/schema/builder/cache.py +24 -0
  207. tigrbl/schema/builder/compat.py +16 -0
  208. tigrbl/schema/builder/extras.py +85 -0
  209. tigrbl/schema/builder/helpers.py +51 -0
  210. tigrbl/schema/builder/list_params.py +117 -0
  211. tigrbl/schema/builder/strip_parent_fields.py +70 -0
  212. tigrbl/schema/collect.py +79 -0
  213. tigrbl/schema/decorators.py +68 -0
  214. tigrbl/schema/get_schema.py +86 -0
  215. tigrbl/schema/schema_spec.py +20 -0
  216. tigrbl/schema/shortcuts.py +42 -0
  217. tigrbl/schema/types.py +34 -0
  218. tigrbl/schema/utils.py +143 -0
  219. tigrbl/session/README.md +14 -0
  220. tigrbl/session/__init__.py +28 -0
  221. tigrbl/session/abc.py +76 -0
  222. tigrbl/session/base.py +151 -0
  223. tigrbl/session/decorators.py +43 -0
  224. tigrbl/session/default.py +118 -0
  225. tigrbl/session/shortcuts.py +50 -0
  226. tigrbl/session/spec.py +112 -0
  227. tigrbl/shortcuts.py +22 -0
  228. tigrbl/specs.py +44 -0
  229. tigrbl/system/__init__.py +13 -0
  230. tigrbl/system/diagnostics/__init__.py +24 -0
  231. tigrbl/system/diagnostics/compat.py +31 -0
  232. tigrbl/system/diagnostics/healthz.py +41 -0
  233. tigrbl/system/diagnostics/hookz.py +51 -0
  234. tigrbl/system/diagnostics/kernelz.py +20 -0
  235. tigrbl/system/diagnostics/methodz.py +43 -0
  236. tigrbl/system/diagnostics/router.py +73 -0
  237. tigrbl/system/diagnostics/utils.py +43 -0
  238. tigrbl/system/uvicorn.py +60 -0
  239. tigrbl/table/__init__.py +9 -0
  240. tigrbl/table/_base.py +260 -0
  241. tigrbl/table/_table.py +54 -0
  242. tigrbl/table/mro_collect.py +69 -0
  243. tigrbl/table/shortcuts.py +57 -0
  244. tigrbl/table/table_spec.py +28 -0
  245. tigrbl/transport/__init__.py +74 -0
  246. tigrbl/transport/jsonrpc/__init__.py +19 -0
  247. tigrbl/transport/jsonrpc/dispatcher.py +352 -0
  248. tigrbl/transport/jsonrpc/helpers.py +115 -0
  249. tigrbl/transport/jsonrpc/models.py +41 -0
  250. tigrbl/transport/rest/__init__.py +25 -0
  251. tigrbl/transport/rest/aggregator.py +132 -0
  252. tigrbl/types/__init__.py +170 -0
  253. tigrbl/types/allow_anon_provider.py +19 -0
  254. tigrbl/types/authn_abc.py +30 -0
  255. tigrbl/types/nested_path_provider.py +22 -0
  256. tigrbl/types/op.py +35 -0
  257. tigrbl/types/op_config_provider.py +17 -0
  258. tigrbl/types/op_verb_alias_provider.py +33 -0
  259. tigrbl/types/request_extras_provider.py +22 -0
  260. tigrbl/types/response_extras_provider.py +22 -0
  261. tigrbl/types/table_config_provider.py +13 -0
  262. tigrbl/types/uuid.py +55 -0
  263. tigrbl-0.3.0.dist-info/METADATA +516 -0
  264. tigrbl-0.3.0.dist-info/RECORD +266 -0
  265. {tigrbl-0.0.1.dev1.dist-info → tigrbl-0.3.0.dist-info}/WHEEL +1 -1
  266. tigrbl-0.3.0.dist-info/licenses/LICENSE +201 -0
  267. tigrbl/ExampleAgent.py +0 -1
  268. tigrbl-0.0.1.dev1.dist-info/METADATA +0 -18
  269. tigrbl-0.0.1.dev1.dist-info/RECORD +0 -5
@@ -0,0 +1,132 @@
1
+ # tigrbl/v3/transport/rest/aggregator.py
2
+ """
3
+ Aggregates per-model REST routers into a single Router.
4
+
5
+ This does not build endpoints by itself — it simply collects the routers that
6
+ `tigrbl.bindings.rest` attached to each model at `model.rest.router`.
7
+
8
+ Recommended workflow:
9
+ 1) Include models with `mount_router=False` so you don't double-mount:
10
+ api.include_model(User, mount_router=False)
11
+ api.include_model(Team, mount_router=False)
12
+ 2) Aggregate and mount once:
13
+ app.include_router(build_rest_router(api, base_prefix="/api"))
14
+ or:
15
+ mount_rest(api, app, base_prefix="/api")
16
+
17
+ Notes:
18
+ • Router paths already include `/{resource}`; we only add `base_prefix`.
19
+ • Model-level auth/db deps and extra REST deps are already attached to each
20
+ model router by `bindings.rest`; this wrapper can add *additional* top-level
21
+ dependencies if you pass them in.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from typing import Any, Mapping, Optional, Sequence
27
+
28
+ try:
29
+ from ...types import Router, Depends
30
+ except Exception: # pragma: no cover
31
+ # Minimal shim to keep importable without FastAPI
32
+ class Router: # type: ignore
33
+ def __init__(self, *a, dependencies: Optional[Sequence[Any]] = None, **kw):
34
+ self.routes = []
35
+ self.includes = []
36
+ self.dependencies = list(dependencies or [])
37
+
38
+ def add_api_route(self, path: str, endpoint, methods: Sequence[str], **opts):
39
+ self.routes.append((path, methods, endpoint, opts))
40
+
41
+ def include_router(self, router: "Router", *, prefix: str = "", **opts):
42
+ self.includes.append((router, prefix, opts))
43
+
44
+ def Depends(fn): # type: ignore
45
+ return fn
46
+
47
+
48
+ def _norm_prefix(p: Optional[str]) -> str:
49
+ if not p:
50
+ return ""
51
+ if not p.startswith("/"):
52
+ p = "/" + p
53
+ # Avoid double trailing slashes; FastAPI is lenient but keep it clean
54
+ return p.rstrip("/")
55
+
56
+
57
+ def _normalize_deps(deps: Optional[Sequence[Any]]) -> list:
58
+ """Accept either Depends(...) objects or plain callables."""
59
+ out = []
60
+ for d in deps or ():
61
+ try:
62
+ is_dep_obj = hasattr(d, "dependency")
63
+ except Exception:
64
+ is_dep_obj = False
65
+ out.append(d if is_dep_obj else Depends(d))
66
+ return out
67
+
68
+
69
+ def _iter_models(api: Any, only: Optional[Sequence[type]] = None) -> Sequence[type]:
70
+ if only:
71
+ return list(only)
72
+ models: Mapping[str, type] = getattr(api, "models", {}) or {}
73
+ # deterministic iteration
74
+ return [models[k] for k in sorted(models.keys())]
75
+
76
+
77
+ def build_rest_router(
78
+ api: Any,
79
+ *,
80
+ models: Optional[Sequence[type]] = None,
81
+ base_prefix: str = "",
82
+ dependencies: Optional[Sequence[Any]] = None,
83
+ ) -> Router:
84
+ """
85
+ Build a top-level Router that includes each model's router under `base_prefix`.
86
+
87
+ Args:
88
+ api: your Tigrbl facade (or any object with `.models` dict).
89
+ models: optional subset of models to include; defaults to all bound models.
90
+ base_prefix: prefix applied once for all included routers (e.g., "/api").
91
+ dependencies: additional router-level dependencies (Depends(...) or callables).
92
+
93
+ Returns:
94
+ Router ready to be mounted on your FastAPI app.
95
+ """
96
+ root = Router(dependencies=_normalize_deps(dependencies))
97
+ prefix = _norm_prefix(base_prefix)
98
+
99
+ for model in _iter_models(api, models):
100
+ rest_ns = getattr(model, "rest", None)
101
+ router = getattr(rest_ns, "router", None) if rest_ns is not None else None
102
+ if router is None:
103
+ # Nothing to include for this model (not bound or no routes)
104
+ continue
105
+ # Include with only the base prefix; the model router already has /{resource} in its paths
106
+ root.include_router(router, prefix=prefix or "")
107
+ return root
108
+
109
+
110
+ def mount_rest(
111
+ api: Any,
112
+ app: Any,
113
+ *,
114
+ models: Optional[Sequence[type]] = None,
115
+ base_prefix: str = "",
116
+ dependencies: Optional[Sequence[Any]] = None,
117
+ ) -> Router:
118
+ """
119
+ Convenience helper: build the aggregated router and include it on `app`.
120
+
121
+ Returns the created router so you can keep a reference if desired.
122
+ """
123
+ router = build_rest_router(
124
+ api, models=models, base_prefix=base_prefix, dependencies=dependencies
125
+ )
126
+ include = getattr(app, "include_router", None)
127
+ if callable(include):
128
+ include(router)
129
+ return router
130
+
131
+
132
+ __all__ = ["build_rest_router", "mount_rest"]
@@ -0,0 +1,170 @@
1
+ # ── Standard Library ─────────────────────────────────────────────────────
2
+ from types import MethodType, SimpleNamespace
3
+ from uuid import uuid4, UUID
4
+
5
+ # ── Third-party Dependencies (via deps module) ───────────────────────────
6
+ from ..deps.sqlalchemy import (
7
+ # Core SQLAlchemy
8
+ Boolean,
9
+ Column,
10
+ _DateTime,
11
+ SAEnum,
12
+ Text,
13
+ ForeignKey,
14
+ Index,
15
+ Integer,
16
+ JSON,
17
+ Numeric,
18
+ String,
19
+ LargeBinary,
20
+ UniqueConstraint,
21
+ CheckConstraint,
22
+ create_engine,
23
+ event,
24
+ # PostgreSQL dialect
25
+ ARRAY,
26
+ PgEnum,
27
+ JSONB,
28
+ TSVECTOR,
29
+ # ORM
30
+ Mapped,
31
+ declarative_mixin,
32
+ declared_attr,
33
+ foreign,
34
+ mapped_column,
35
+ relationship,
36
+ remote,
37
+ column_property,
38
+ Session,
39
+ sessionmaker,
40
+ InstrumentedAttribute,
41
+ # Extensions
42
+ MutableDict,
43
+ MutableList,
44
+ hybrid_property,
45
+ StaticPool,
46
+ )
47
+
48
+ from ..deps.pydantic import (
49
+ BaseModel,
50
+ Field,
51
+ ValidationError,
52
+ )
53
+
54
+ from ..deps.fastapi import (
55
+ APIRouter,
56
+ Router,
57
+ Security,
58
+ Depends,
59
+ Request,
60
+ Response,
61
+ Path,
62
+ Body,
63
+ HTTPException,
64
+ App,
65
+ )
66
+
67
+ # ── Local Package ─────────────────────────────────────────────────────────
68
+ from .op import _Op, _SchemaVerb
69
+ from .uuid import PgUUID, SqliteUUID
70
+ from .authn_abc import AuthNProvider
71
+ from .table_config_provider import TableConfigProvider
72
+ from .nested_path_provider import NestedPathProvider
73
+ from .allow_anon_provider import AllowAnonProvider
74
+ from .request_extras_provider import (
75
+ RequestExtrasProvider,
76
+ list_request_extras_providers,
77
+ )
78
+ from .response_extras_provider import (
79
+ ResponseExtrasProvider,
80
+ list_response_extras_providers,
81
+ )
82
+
83
+ from .op_verb_alias_provider import OpVerbAliasProvider, list_verb_alias_providers
84
+ from .op_config_provider import OpConfigProvider
85
+
86
+ # ── Generics / Extensions ─────────────────────────────────────────────────
87
+ DateTime = _DateTime(timezone=False)
88
+ TZDateTime = _DateTime(timezone=True)
89
+
90
+
91
+ # ── Public Re-exports (Backwards Compatibility) ──────────────────────────
92
+ __all__: list[str] = [
93
+ # local
94
+ "_Op",
95
+ "_SchemaVerb",
96
+ "AuthNProvider",
97
+ "TableConfigProvider",
98
+ "NestedPathProvider",
99
+ "AllowAnonProvider",
100
+ "RequestExtrasProvider",
101
+ "ResponseExtrasProvider",
102
+ "OpVerbAliasProvider",
103
+ "list_verb_alias_providers",
104
+ "list_request_extras_providers",
105
+ "list_response_extras_providers",
106
+ "OpConfigProvider",
107
+ # add ons
108
+ "SqliteUUID",
109
+ # builtin types
110
+ "MethodType",
111
+ "SimpleNamespace",
112
+ "uuid4",
113
+ "UUID",
114
+ # sqlalchemy core (from deps.sqlalchemy)
115
+ "Boolean",
116
+ "Column",
117
+ "DateTime",
118
+ "TZDateTime",
119
+ "Text",
120
+ "SAEnum",
121
+ "ForeignKey",
122
+ "Index",
123
+ "Integer",
124
+ "JSON",
125
+ "Numeric",
126
+ "String",
127
+ "LargeBinary",
128
+ "UniqueConstraint",
129
+ "CheckConstraint",
130
+ "create_engine",
131
+ "event",
132
+ # sqlalchemy.dialects.postgresql (from deps.sqlalchemy)
133
+ "ARRAY",
134
+ "PgEnum",
135
+ "JSONB",
136
+ "PgUUID",
137
+ "TSVECTOR",
138
+ # sqlalchemy.orm (from deps.sqlalchemy)
139
+ "Mapped",
140
+ "declarative_mixin",
141
+ "declared_attr",
142
+ "foreign",
143
+ "mapped_column",
144
+ "column_property",
145
+ "hybrid_property",
146
+ "relationship",
147
+ "remote",
148
+ "Session",
149
+ "sessionmaker",
150
+ "InstrumentedAttribute",
151
+ # sqlalchemy.ext.mutable (from deps.sqlalchemy)
152
+ "MutableDict",
153
+ "MutableList",
154
+ "StaticPool",
155
+ # pydantic schema support (from deps.pydantic)
156
+ "BaseModel",
157
+ "Field",
158
+ "ValidationError",
159
+ # fastapi support (from deps.fastapi)
160
+ "Request",
161
+ "Response",
162
+ "APIRouter",
163
+ "Router",
164
+ "App",
165
+ "Security",
166
+ "Depends",
167
+ "Path",
168
+ "Body",
169
+ "HTTPException",
170
+ ]
@@ -0,0 +1,19 @@
1
+ from typing import Callable, ClassVar, Iterable
2
+
3
+ from .table_config_provider import TableConfigProvider
4
+
5
+ _ALLOW_ANON_PROVIDERS: set[type] = set()
6
+
7
+
8
+ class AllowAnonProvider(TableConfigProvider):
9
+ """Models that expose operations without authentication."""
10
+
11
+ __tigrbl_allow_anon__: ClassVar[Iterable[str] | Callable[[], Iterable[str]]] = ()
12
+
13
+ def __init_subclass__(cls, **kw):
14
+ super().__init_subclass__(**kw)
15
+ _ALLOW_ANON_PROVIDERS.add(cls)
16
+
17
+
18
+ def list_allow_anon_providers():
19
+ return sorted(_ALLOW_ANON_PROVIDERS, key=lambda c: c.__name__)
@@ -0,0 +1,30 @@
1
+ # tigrbl/v3/types/authn_abc.py
2
+ from __future__ import annotations
3
+ from abc import ABC, abstractmethod
4
+ from fastapi import Request
5
+
6
+
7
+ class AuthNProvider(ABC):
8
+ """
9
+ Marker‑interface that any AuthN extension must implement
10
+ so that Tigrbl can plug itself in at run‑time.
11
+ """
12
+
13
+ # ---------- FastAPI dependency ----------
14
+ @abstractmethod
15
+ async def get_principal(self, request: Request): # -> dict[str, str]
16
+ """Return {"sub": user_id, "tid": tenant_id, ...} or raise HTTP 401."""
17
+
18
+
19
+ __all__ = ["AuthNProvider"]
20
+
21
+
22
+ for _name in list(globals()):
23
+ if _name not in __all__ and not _name.startswith("__"):
24
+ del globals()[_name]
25
+
26
+
27
+ def __dir__():
28
+ """Tighten ``dir()`` output for interactive sessions."""
29
+
30
+ return sorted(__all__)
@@ -0,0 +1,22 @@
1
+ from abc import abstractmethod
2
+
3
+ from .table_config_provider import TableConfigProvider
4
+
5
+ _NESTED_PATH_PROVIDERS: set[type] = set()
6
+
7
+
8
+ class NestedPathProvider(TableConfigProvider):
9
+ """Models that supply nested route prefixes."""
10
+
11
+ @classmethod
12
+ @abstractmethod
13
+ def __tigrbl_nested_paths__(cls) -> str | None:
14
+ """Return hierarchical path prefix for nested routes."""
15
+
16
+ def __init_subclass__(cls, **kw):
17
+ super().__init_subclass__(**kw)
18
+ _NESTED_PATH_PROVIDERS.add(cls)
19
+
20
+
21
+ def list_nested_path_providers():
22
+ return sorted(_NESTED_PATH_PROVIDERS, key=lambda c: c.__name__)
tigrbl/types/op.py ADDED
@@ -0,0 +1,35 @@
1
+ """
2
+ tigrbl/v3/types/_op.py
3
+ Pure structural helpers"""
4
+
5
+ from typing import Any, Callable, NamedTuple, Type, Literal, TypeAlias
6
+
7
+ _SchemaVerb: TypeAlias = Literal[
8
+ "create",
9
+ "read",
10
+ "update",
11
+ "replace",
12
+ "merge",
13
+ "delete",
14
+ "list",
15
+ "clear",
16
+ ]
17
+
18
+ # need to add clear
19
+ # need to add support for bulk create, update, delete
20
+
21
+
22
+ class _Op(NamedTuple):
23
+ """
24
+ Metadata for one REST/RPC operation registered by Tigrbl.
25
+ """
26
+
27
+ verb: str # e.g. "create", "list"
28
+ http: str # "POST" | "GET" | "PATCH" | …
29
+ path: str # URL suffix, e.g. "/{item_id}"
30
+ In: Type | None # Pydantic input model (or None)
31
+ Out: Type # Pydantic output model
32
+ core: Callable[..., Any] # The actual implementation
33
+
34
+
35
+ __all__ = ["_Op", "_SchemaVerb"]
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+ from typing import Iterable, Literal
3
+ from .table_config_provider import TableConfigProvider
4
+ from ..op import OpSpec
5
+ from ..op.canonical import ( # noqa: F401
6
+ DEFAULT_CANON_VERBS,
7
+ should_wire_canonical,
8
+ )
9
+
10
+
11
+ class OpConfigProvider(TableConfigProvider):
12
+ __tigrbl_ops__: Iterable[OpSpec] = ()
13
+
14
+ # Default canonical verbs wiring policy
15
+ __tigrbl_defaults_mode__: Literal["all", "none", "some"] = "all"
16
+ __tigrbl_defaults_include__: set[str] = set()
17
+ __tigrbl_defaults_exclude__: set[str] = set()
@@ -0,0 +1,33 @@
1
+ from typing import Callable, ClassVar, Mapping, Literal
2
+
3
+ from .table_config_provider import TableConfigProvider
4
+
5
+ _VERB_ALIAS_PROVIDERS: set[type] = set()
6
+
7
+
8
+ class OpVerbAliasProvider(TableConfigProvider):
9
+ """
10
+ Table-level config provider to rename operation verbs for RPC exposure.
11
+ Does not change REST routes or internal canonical semantics.
12
+ """
13
+
14
+ # Map canonical → alias (e.g., {"create": "register"})
15
+ __tigrbl_verb_aliases__: ClassVar[
16
+ Mapping[str, str] | Callable[[], Mapping[str, str]]
17
+ ] = {}
18
+
19
+ # How to expose names:
20
+ # - "both" (default): expose canonical & alias
21
+ # - "alias_only": expose alias only
22
+ # - "canonical_only": ignore aliases, keep canonical only
23
+ __tigrbl_verb_alias_policy__: ClassVar[
24
+ Literal["both", "alias_only", "canonical_only"]
25
+ ] = "both"
26
+
27
+ def __init_subclass__(cls, **kw):
28
+ super().__init_subclass__(**kw)
29
+ _VERB_ALIAS_PROVIDERS.add(cls)
30
+
31
+
32
+ def list_verb_alias_providers():
33
+ return sorted(_VERB_ALIAS_PROVIDERS, key=lambda c: c.__name__)
@@ -0,0 +1,22 @@
1
+ from typing import Callable, ClassVar, Mapping
2
+
3
+ from .table_config_provider import TableConfigProvider
4
+
5
+ _REQUEST_EXTRAS_PROVIDERS: set[type] = set()
6
+
7
+
8
+ class RequestExtrasProvider(TableConfigProvider):
9
+ """Models that expose request-only virtual fields."""
10
+
11
+ __tigrbl_request_extras__: ClassVar[
12
+ Mapping[str, Mapping[str, object]]
13
+ | Callable[[], Mapping[str, Mapping[str, object]]]
14
+ ] = {}
15
+
16
+ def __init_subclass__(cls, **kw):
17
+ super().__init_subclass__(**kw)
18
+ _REQUEST_EXTRAS_PROVIDERS.add(cls)
19
+
20
+
21
+ def list_request_extras_providers():
22
+ return sorted(_REQUEST_EXTRAS_PROVIDERS, key=lambda c: c.__name__)
@@ -0,0 +1,22 @@
1
+ from typing import Callable, ClassVar, Mapping
2
+
3
+ from .table_config_provider import TableConfigProvider
4
+
5
+ _RESPONSE_EXTRAS_PROVIDERS: set[type] = set()
6
+
7
+
8
+ class ResponseExtrasProvider(TableConfigProvider):
9
+ """Models that expose response-only virtual fields."""
10
+
11
+ __tigrbl_response_extras__: ClassVar[
12
+ Mapping[str, Mapping[str, object]]
13
+ | Callable[[], Mapping[str, Mapping[str, object]]]
14
+ ] = {}
15
+
16
+ def __init_subclass__(cls, **kw):
17
+ super().__init_subclass__(**kw)
18
+ _RESPONSE_EXTRAS_PROVIDERS.add(cls)
19
+
20
+
21
+ def list_response_extras_providers():
22
+ return sorted(_RESPONSE_EXTRAS_PROVIDERS, key=lambda c: c.__name__)
@@ -0,0 +1,13 @@
1
+ _TABLE_CONFIG_PROVIDERS: set[type] = set()
2
+
3
+
4
+ class TableConfigProvider:
5
+ """Marker base for table-level configuration providers."""
6
+
7
+ def __init_subclass__(cls, **kw):
8
+ super().__init_subclass__(**kw)
9
+ _TABLE_CONFIG_PROVIDERS.add(cls)
10
+
11
+
12
+ def list_table_config_providers():
13
+ return sorted(_TABLE_CONFIG_PROVIDERS, key=lambda c: c.__name__)
tigrbl/types/uuid.py ADDED
@@ -0,0 +1,55 @@
1
+ # ── Standard Library ─────────────────────────────────────────────────────
2
+ from __future__ import annotations
3
+
4
+ from typing import Any
5
+ import uuid
6
+
7
+ # ── Third-party Dependencies ────────────────────────────────────────────
8
+ from sqlalchemy.types import TypeDecorator
9
+
10
+ # ── Local Package ───────────────────────────────────────────────────────
11
+ from ..deps.sqlalchemy import String, _PgUUID
12
+
13
+
14
+ class PgUUID(_PgUUID):
15
+ @property
16
+ def hex(self):
17
+ return self.as_uuid.hex
18
+
19
+
20
+ class SqliteUUID(TypeDecorator):
21
+ """UUID type that stores hyphenated strings on SQLite to avoid numeric coercion."""
22
+
23
+ impl = String(36)
24
+ cache_ok = True
25
+
26
+ def __init__(self, as_uuid: bool = True):
27
+ super().__init__()
28
+ self.as_uuid = as_uuid
29
+
30
+ @property
31
+ def python_type(self) -> type:
32
+ return uuid.UUID if self.as_uuid else str
33
+
34
+ def load_dialect_impl(self, dialect) -> Any:
35
+ if dialect.name == "postgresql":
36
+ return dialect.type_descriptor(PgUUID(as_uuid=self.as_uuid))
37
+ return dialect.type_descriptor(String(36))
38
+
39
+ def process_bind_param(self, value: Any, dialect) -> Any:
40
+ if value is None:
41
+ return None
42
+ if self.as_uuid:
43
+ if not isinstance(value, uuid.UUID):
44
+ value = uuid.UUID(str(value))
45
+ return value if dialect.name == "postgresql" else str(value)
46
+ return str(value)
47
+
48
+ def process_result_value(self, value: Any, dialect) -> Any:
49
+ if value is None:
50
+ return None
51
+ if self.as_uuid:
52
+ if isinstance(value, uuid.UUID):
53
+ return value
54
+ return uuid.UUID(str(value))
55
+ return str(value)