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.
- tigrbl/README.md +94 -0
- tigrbl/__init__.py +139 -14
- tigrbl/api/__init__.py +6 -0
- tigrbl/api/_api.py +97 -0
- tigrbl/api/api_spec.py +30 -0
- tigrbl/api/mro_collect.py +43 -0
- tigrbl/api/shortcuts.py +56 -0
- tigrbl/api/tigrbl_api.py +291 -0
- tigrbl/app/__init__.py +0 -0
- tigrbl/app/_app.py +86 -0
- tigrbl/app/_model_registry.py +41 -0
- tigrbl/app/app_spec.py +42 -0
- tigrbl/app/mro_collect.py +67 -0
- tigrbl/app/shortcuts.py +65 -0
- tigrbl/app/tigrbl_app.py +319 -0
- tigrbl/bindings/__init__.py +73 -0
- tigrbl/bindings/api/__init__.py +12 -0
- tigrbl/bindings/api/common.py +109 -0
- tigrbl/bindings/api/include.py +256 -0
- tigrbl/bindings/api/resource_proxy.py +149 -0
- tigrbl/bindings/api/rpc.py +111 -0
- tigrbl/bindings/columns.py +49 -0
- tigrbl/bindings/handlers/__init__.py +11 -0
- tigrbl/bindings/handlers/builder.py +119 -0
- tigrbl/bindings/handlers/ctx.py +74 -0
- tigrbl/bindings/handlers/identifiers.py +228 -0
- tigrbl/bindings/handlers/namespaces.py +51 -0
- tigrbl/bindings/handlers/steps.py +276 -0
- tigrbl/bindings/hooks.py +311 -0
- tigrbl/bindings/model.py +194 -0
- tigrbl/bindings/model_helpers.py +139 -0
- tigrbl/bindings/model_registry.py +77 -0
- tigrbl/bindings/rest/__init__.py +7 -0
- tigrbl/bindings/rest/attach.py +34 -0
- tigrbl/bindings/rest/collection.py +286 -0
- tigrbl/bindings/rest/common.py +120 -0
- tigrbl/bindings/rest/fastapi.py +76 -0
- tigrbl/bindings/rest/helpers.py +119 -0
- tigrbl/bindings/rest/io.py +317 -0
- tigrbl/bindings/rest/io_headers.py +49 -0
- tigrbl/bindings/rest/member.py +386 -0
- tigrbl/bindings/rest/router.py +296 -0
- tigrbl/bindings/rest/routing.py +153 -0
- tigrbl/bindings/rpc.py +364 -0
- tigrbl/bindings/schemas/__init__.py +11 -0
- tigrbl/bindings/schemas/builder.py +348 -0
- tigrbl/bindings/schemas/defaults.py +260 -0
- tigrbl/bindings/schemas/utils.py +193 -0
- tigrbl/column/README.md +62 -0
- tigrbl/column/__init__.py +72 -0
- tigrbl/column/_column.py +96 -0
- tigrbl/column/column_spec.py +40 -0
- tigrbl/column/field_spec.py +31 -0
- tigrbl/column/infer/__init__.py +25 -0
- tigrbl/column/infer/core.py +92 -0
- tigrbl/column/infer/jsonhints.py +44 -0
- tigrbl/column/infer/planning.py +133 -0
- tigrbl/column/infer/types.py +102 -0
- tigrbl/column/infer/utils.py +59 -0
- tigrbl/column/io_spec.py +136 -0
- tigrbl/column/mro_collect.py +59 -0
- tigrbl/column/shortcuts.py +89 -0
- tigrbl/column/storage_spec.py +65 -0
- tigrbl/config/__init__.py +19 -0
- tigrbl/config/constants.py +224 -0
- tigrbl/config/defaults.py +29 -0
- tigrbl/config/resolver.py +295 -0
- tigrbl/core/__init__.py +47 -0
- tigrbl/core/crud/__init__.py +36 -0
- tigrbl/core/crud/bulk.py +168 -0
- tigrbl/core/crud/helpers/__init__.py +76 -0
- tigrbl/core/crud/helpers/db.py +92 -0
- tigrbl/core/crud/helpers/enum.py +86 -0
- tigrbl/core/crud/helpers/filters.py +162 -0
- tigrbl/core/crud/helpers/model.py +123 -0
- tigrbl/core/crud/helpers/normalize.py +99 -0
- tigrbl/core/crud/ops.py +235 -0
- tigrbl/ddl/__init__.py +344 -0
- tigrbl/decorators.py +17 -0
- tigrbl/deps/__init__.py +20 -0
- tigrbl/deps/fastapi.py +45 -0
- tigrbl/deps/favicon.svg +4 -0
- tigrbl/deps/jinja.py +27 -0
- tigrbl/deps/pydantic.py +10 -0
- tigrbl/deps/sqlalchemy.py +94 -0
- tigrbl/deps/starlette.py +36 -0
- tigrbl/engine/__init__.py +45 -0
- tigrbl/engine/_engine.py +144 -0
- tigrbl/engine/bind.py +33 -0
- tigrbl/engine/builders.py +236 -0
- tigrbl/engine/capabilities.py +29 -0
- tigrbl/engine/collect.py +111 -0
- tigrbl/engine/decorators.py +110 -0
- tigrbl/engine/docs/PLUGINS.md +49 -0
- tigrbl/engine/engine_spec.py +355 -0
- tigrbl/engine/plugins.py +52 -0
- tigrbl/engine/registry.py +36 -0
- tigrbl/engine/resolver.py +224 -0
- tigrbl/engine/shortcuts.py +216 -0
- tigrbl/hook/__init__.py +21 -0
- tigrbl/hook/_hook.py +22 -0
- tigrbl/hook/decorators.py +28 -0
- tigrbl/hook/hook_spec.py +24 -0
- tigrbl/hook/mro_collect.py +98 -0
- tigrbl/hook/shortcuts.py +44 -0
- tigrbl/hook/types.py +76 -0
- tigrbl/op/__init__.py +50 -0
- tigrbl/op/_op.py +31 -0
- tigrbl/op/canonical.py +31 -0
- tigrbl/op/collect.py +11 -0
- tigrbl/op/decorators.py +238 -0
- tigrbl/op/model_registry.py +301 -0
- tigrbl/op/mro_collect.py +99 -0
- tigrbl/op/resolver.py +216 -0
- tigrbl/op/types.py +136 -0
- tigrbl/orm/__init__.py +1 -0
- tigrbl/orm/mixins/_RowBound.py +83 -0
- tigrbl/orm/mixins/__init__.py +95 -0
- tigrbl/orm/mixins/bootstrappable.py +113 -0
- tigrbl/orm/mixins/bound.py +47 -0
- tigrbl/orm/mixins/edges.py +40 -0
- tigrbl/orm/mixins/fields.py +165 -0
- tigrbl/orm/mixins/hierarchy.py +54 -0
- tigrbl/orm/mixins/key_digest.py +44 -0
- tigrbl/orm/mixins/lifecycle.py +115 -0
- tigrbl/orm/mixins/locks.py +51 -0
- tigrbl/orm/mixins/markers.py +16 -0
- tigrbl/orm/mixins/operations.py +57 -0
- tigrbl/orm/mixins/ownable.py +337 -0
- tigrbl/orm/mixins/principals.py +98 -0
- tigrbl/orm/mixins/tenant_bound.py +301 -0
- tigrbl/orm/mixins/upsertable.py +118 -0
- tigrbl/orm/mixins/utils.py +49 -0
- tigrbl/orm/tables/__init__.py +72 -0
- tigrbl/orm/tables/_base.py +8 -0
- tigrbl/orm/tables/audit.py +56 -0
- tigrbl/orm/tables/client.py +25 -0
- tigrbl/orm/tables/group.py +29 -0
- tigrbl/orm/tables/org.py +30 -0
- tigrbl/orm/tables/rbac.py +76 -0
- tigrbl/orm/tables/status.py +106 -0
- tigrbl/orm/tables/tenant.py +22 -0
- tigrbl/orm/tables/user.py +39 -0
- tigrbl/response/README.md +34 -0
- tigrbl/response/__init__.py +33 -0
- tigrbl/response/bind.py +12 -0
- tigrbl/response/decorators.py +37 -0
- tigrbl/response/resolver.py +83 -0
- tigrbl/response/shortcuts.py +171 -0
- tigrbl/response/types.py +49 -0
- tigrbl/rest/__init__.py +27 -0
- tigrbl/runtime/README.md +129 -0
- tigrbl/runtime/__init__.py +20 -0
- tigrbl/runtime/atoms/__init__.py +102 -0
- tigrbl/runtime/atoms/emit/__init__.py +42 -0
- tigrbl/runtime/atoms/emit/paired_post.py +158 -0
- tigrbl/runtime/atoms/emit/paired_pre.py +106 -0
- tigrbl/runtime/atoms/emit/readtime_alias.py +120 -0
- tigrbl/runtime/atoms/out/__init__.py +38 -0
- tigrbl/runtime/atoms/out/masking.py +135 -0
- tigrbl/runtime/atoms/refresh/__init__.py +38 -0
- tigrbl/runtime/atoms/refresh/demand.py +130 -0
- tigrbl/runtime/atoms/resolve/__init__.py +40 -0
- tigrbl/runtime/atoms/resolve/assemble.py +167 -0
- tigrbl/runtime/atoms/resolve/paired_gen.py +147 -0
- tigrbl/runtime/atoms/response/__init__.py +19 -0
- tigrbl/runtime/atoms/response/headers_from_payload.py +57 -0
- tigrbl/runtime/atoms/response/negotiate.py +30 -0
- tigrbl/runtime/atoms/response/negotiation.py +43 -0
- tigrbl/runtime/atoms/response/render.py +36 -0
- tigrbl/runtime/atoms/response/renderer.py +116 -0
- tigrbl/runtime/atoms/response/template.py +44 -0
- tigrbl/runtime/atoms/response/templates.py +88 -0
- tigrbl/runtime/atoms/schema/__init__.py +40 -0
- tigrbl/runtime/atoms/schema/collect_in.py +21 -0
- tigrbl/runtime/atoms/schema/collect_out.py +21 -0
- tigrbl/runtime/atoms/storage/__init__.py +38 -0
- tigrbl/runtime/atoms/storage/to_stored.py +167 -0
- tigrbl/runtime/atoms/wire/__init__.py +45 -0
- tigrbl/runtime/atoms/wire/build_in.py +166 -0
- tigrbl/runtime/atoms/wire/build_out.py +87 -0
- tigrbl/runtime/atoms/wire/dump.py +206 -0
- tigrbl/runtime/atoms/wire/validate_in.py +227 -0
- tigrbl/runtime/context.py +206 -0
- tigrbl/runtime/errors/__init__.py +61 -0
- tigrbl/runtime/errors/converters.py +214 -0
- tigrbl/runtime/errors/exceptions.py +124 -0
- tigrbl/runtime/errors/mappings.py +71 -0
- tigrbl/runtime/errors/utils.py +150 -0
- tigrbl/runtime/events.py +209 -0
- tigrbl/runtime/executor/__init__.py +6 -0
- tigrbl/runtime/executor/guards.py +132 -0
- tigrbl/runtime/executor/helpers.py +88 -0
- tigrbl/runtime/executor/invoke.py +150 -0
- tigrbl/runtime/executor/types.py +84 -0
- tigrbl/runtime/kernel.py +644 -0
- tigrbl/runtime/labels.py +353 -0
- tigrbl/runtime/opview.py +89 -0
- tigrbl/runtime/ordering.py +256 -0
- tigrbl/runtime/system.py +279 -0
- tigrbl/runtime/trace.py +330 -0
- tigrbl/schema/__init__.py +38 -0
- tigrbl/schema/_schema.py +27 -0
- tigrbl/schema/builder/__init__.py +17 -0
- tigrbl/schema/builder/build_schema.py +209 -0
- tigrbl/schema/builder/cache.py +24 -0
- tigrbl/schema/builder/compat.py +16 -0
- tigrbl/schema/builder/extras.py +85 -0
- tigrbl/schema/builder/helpers.py +51 -0
- tigrbl/schema/builder/list_params.py +117 -0
- tigrbl/schema/builder/strip_parent_fields.py +70 -0
- tigrbl/schema/collect.py +79 -0
- tigrbl/schema/decorators.py +68 -0
- tigrbl/schema/get_schema.py +86 -0
- tigrbl/schema/schema_spec.py +20 -0
- tigrbl/schema/shortcuts.py +42 -0
- tigrbl/schema/types.py +34 -0
- tigrbl/schema/utils.py +143 -0
- tigrbl/session/README.md +14 -0
- tigrbl/session/__init__.py +28 -0
- tigrbl/session/abc.py +76 -0
- tigrbl/session/base.py +151 -0
- tigrbl/session/decorators.py +43 -0
- tigrbl/session/default.py +118 -0
- tigrbl/session/shortcuts.py +50 -0
- tigrbl/session/spec.py +112 -0
- tigrbl/shortcuts.py +22 -0
- tigrbl/specs.py +44 -0
- tigrbl/system/__init__.py +13 -0
- tigrbl/system/diagnostics/__init__.py +24 -0
- tigrbl/system/diagnostics/compat.py +31 -0
- tigrbl/system/diagnostics/healthz.py +41 -0
- tigrbl/system/diagnostics/hookz.py +51 -0
- tigrbl/system/diagnostics/kernelz.py +20 -0
- tigrbl/system/diagnostics/methodz.py +43 -0
- tigrbl/system/diagnostics/router.py +73 -0
- tigrbl/system/diagnostics/utils.py +43 -0
- tigrbl/system/uvicorn.py +60 -0
- tigrbl/table/__init__.py +9 -0
- tigrbl/table/_base.py +260 -0
- tigrbl/table/_table.py +54 -0
- tigrbl/table/mro_collect.py +69 -0
- tigrbl/table/shortcuts.py +57 -0
- tigrbl/table/table_spec.py +28 -0
- tigrbl/transport/__init__.py +74 -0
- tigrbl/transport/jsonrpc/__init__.py +19 -0
- tigrbl/transport/jsonrpc/dispatcher.py +352 -0
- tigrbl/transport/jsonrpc/helpers.py +115 -0
- tigrbl/transport/jsonrpc/models.py +41 -0
- tigrbl/transport/rest/__init__.py +25 -0
- tigrbl/transport/rest/aggregator.py +132 -0
- tigrbl/types/__init__.py +170 -0
- tigrbl/types/allow_anon_provider.py +19 -0
- tigrbl/types/authn_abc.py +30 -0
- tigrbl/types/nested_path_provider.py +22 -0
- tigrbl/types/op.py +35 -0
- tigrbl/types/op_config_provider.py +17 -0
- tigrbl/types/op_verb_alias_provider.py +33 -0
- tigrbl/types/request_extras_provider.py +22 -0
- tigrbl/types/response_extras_provider.py +22 -0
- tigrbl/types/table_config_provider.py +13 -0
- tigrbl/types/uuid.py +55 -0
- tigrbl-0.3.0.dist-info/METADATA +516 -0
- tigrbl-0.3.0.dist-info/RECORD +266 -0
- {tigrbl-0.0.1.dev1.dist-info → tigrbl-0.3.0.dist-info}/WHEEL +1 -1
- tigrbl-0.3.0.dist-info/licenses/LICENSE +201 -0
- tigrbl/ExampleAgent.py +0 -1
- tigrbl-0.0.1.dev1.dist-info/METADATA +0 -18
- tigrbl-0.0.1.dev1.dist-info/RECORD +0 -5
tigrbl/api/tigrbl_api.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# tigrbl/v3/api/tigrbl_api.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import copy
|
|
5
|
+
from types import SimpleNamespace
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Callable,
|
|
9
|
+
Dict,
|
|
10
|
+
Iterable,
|
|
11
|
+
Mapping,
|
|
12
|
+
Optional,
|
|
13
|
+
Sequence,
|
|
14
|
+
Tuple,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from ._api import Api as _Api
|
|
18
|
+
from ..engine.engine_spec import EngineCfg
|
|
19
|
+
from ..engine import resolver as _resolver
|
|
20
|
+
from ..ddl import initialize as _ddl_initialize
|
|
21
|
+
from ..bindings.api import (
|
|
22
|
+
include_model as _include_model,
|
|
23
|
+
include_models as _include_models,
|
|
24
|
+
rpc_call as _rpc_call,
|
|
25
|
+
_seed_security_and_deps,
|
|
26
|
+
_mount_router,
|
|
27
|
+
_default_prefix,
|
|
28
|
+
AttrDict,
|
|
29
|
+
)
|
|
30
|
+
from ..bindings.model import rebind as _rebind, bind as _bind
|
|
31
|
+
from ..bindings.rest import build_router_and_attach as _build_router_and_attach
|
|
32
|
+
from ..transport import mount_jsonrpc as _mount_jsonrpc
|
|
33
|
+
from ..system import mount_diagnostics as _mount_diagnostics
|
|
34
|
+
from ..op import get_registry, OpSpec
|
|
35
|
+
from ..app._model_registry import initialize_model_registry
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TigrblApi(_Api):
|
|
39
|
+
"""
|
|
40
|
+
Canonical router-focused facade that owns:
|
|
41
|
+
• containers (models, schemas, handlers, hooks, rpc, rest, routers, columns, table_config, core proxies)
|
|
42
|
+
• model inclusion (REST + RPC wiring)
|
|
43
|
+
• JSON-RPC / diagnostics mounting
|
|
44
|
+
• (optional) auth knobs recognized by some middlewares/dispatchers
|
|
45
|
+
|
|
46
|
+
It composes v3 primitives; you can still use the functions directly if you prefer.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
PREFIX = ""
|
|
50
|
+
TAGS: Sequence[Any] = ()
|
|
51
|
+
APIS: Sequence[Any] = ()
|
|
52
|
+
MODELS: Sequence[Any] = ()
|
|
53
|
+
|
|
54
|
+
# --- optional auth knobs recognized by some middlewares/dispatchers (kept for back-compat) ---
|
|
55
|
+
_authn: Any = None
|
|
56
|
+
_allow_anon: bool = True
|
|
57
|
+
_authorize: Any = None
|
|
58
|
+
_optional_authn_dep: Any = None
|
|
59
|
+
_allow_anon_ops: set[str] = set()
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
*,
|
|
64
|
+
engine: EngineCfg | None = None,
|
|
65
|
+
jsonrpc_prefix: str = "/rpc",
|
|
66
|
+
system_prefix: str = "/system",
|
|
67
|
+
api_hooks: Mapping[str, Iterable[Callable]]
|
|
68
|
+
| Mapping[str, Mapping[str, Iterable[Callable]]]
|
|
69
|
+
| None = None,
|
|
70
|
+
**router_kwargs: Any,
|
|
71
|
+
) -> None:
|
|
72
|
+
_Api.__init__(self, engine=engine, **router_kwargs)
|
|
73
|
+
self.jsonrpc_prefix = jsonrpc_prefix
|
|
74
|
+
self.system_prefix = system_prefix
|
|
75
|
+
|
|
76
|
+
# public containers (mirrors used by bindings.api)
|
|
77
|
+
self.models = initialize_model_registry(getattr(self, "MODELS", ()))
|
|
78
|
+
self.schemas = SimpleNamespace()
|
|
79
|
+
self.handlers = SimpleNamespace()
|
|
80
|
+
self.hooks = SimpleNamespace()
|
|
81
|
+
self.rpc = SimpleNamespace()
|
|
82
|
+
self.rest = SimpleNamespace()
|
|
83
|
+
self.routers: Dict[str, Any] = {}
|
|
84
|
+
self.tables = AttrDict()
|
|
85
|
+
self.columns: Dict[str, Tuple[str, ...]] = {}
|
|
86
|
+
self.table_config: Dict[str, Dict[str, Any]] = {}
|
|
87
|
+
self.core = SimpleNamespace()
|
|
88
|
+
self.core_raw = SimpleNamespace()
|
|
89
|
+
|
|
90
|
+
# API-level hooks map (merged into each model at include-time; precedence handled in bindings.hooks)
|
|
91
|
+
self._api_hooks_map = copy.deepcopy(api_hooks) if api_hooks else None
|
|
92
|
+
|
|
93
|
+
# ------------------------- internal helpers -------------------------
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def _merge_api_hooks_into_model(model: type, hooks_map: Any) -> None:
|
|
97
|
+
"""
|
|
98
|
+
Install API-level hooks on the model so the binder can see them.
|
|
99
|
+
Accepted shapes:
|
|
100
|
+
{phase: [fn, ...]} # global, all aliases
|
|
101
|
+
{alias: {phase: [fn, ...]}, "*": {...}} # per-alias + wildcard
|
|
102
|
+
If the model already has __tigrbl_api_hooks__, we shallow-merge keys.
|
|
103
|
+
"""
|
|
104
|
+
if not hooks_map:
|
|
105
|
+
return
|
|
106
|
+
existing = getattr(model, "__tigrbl_api_hooks__", None)
|
|
107
|
+
if existing is None:
|
|
108
|
+
setattr(model, "__tigrbl_api_hooks__", copy.deepcopy(hooks_map))
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
# shallow merge (alias or phase keys); values are lists we extend
|
|
112
|
+
merged = copy.deepcopy(existing)
|
|
113
|
+
for k, v in (hooks_map or {}).items():
|
|
114
|
+
if k not in merged:
|
|
115
|
+
merged[k] = copy.deepcopy(v)
|
|
116
|
+
else:
|
|
117
|
+
# when both are dicts, merge phase lists
|
|
118
|
+
if isinstance(v, Mapping) and isinstance(merged[k], Mapping):
|
|
119
|
+
for ph, fns in v.items():
|
|
120
|
+
merged[k].setdefault(ph, [])
|
|
121
|
+
merged[k][ph] = list(merged[k][ph]) + list(fns or [])
|
|
122
|
+
else:
|
|
123
|
+
# fallback: prefer model-local value, then append api-level
|
|
124
|
+
if isinstance(merged[k], list):
|
|
125
|
+
merged[k] = list(merged[k]) + list(v or [])
|
|
126
|
+
else:
|
|
127
|
+
merged[k] = v
|
|
128
|
+
setattr(model, "__tigrbl_api_hooks__", merged)
|
|
129
|
+
|
|
130
|
+
# ------------------------- primary operations -------------------------
|
|
131
|
+
|
|
132
|
+
def include_model(
|
|
133
|
+
self, model: type, *, prefix: str | None = None, mount_router: bool = True
|
|
134
|
+
) -> Tuple[type, Any]:
|
|
135
|
+
"""
|
|
136
|
+
Bind a model, mount its REST router, and attach all namespaces to this facade.
|
|
137
|
+
"""
|
|
138
|
+
# inject API-level hooks so the binder merges them
|
|
139
|
+
self._merge_api_hooks_into_model(model, self._api_hooks_map)
|
|
140
|
+
return _include_model(
|
|
141
|
+
self, model, app=None, prefix=prefix, mount_router=mount_router
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def include_models(
|
|
145
|
+
self,
|
|
146
|
+
models: Sequence[type],
|
|
147
|
+
*,
|
|
148
|
+
base_prefix: str | None = None,
|
|
149
|
+
mount_router: bool = True,
|
|
150
|
+
) -> Dict[str, Any]:
|
|
151
|
+
for m in models:
|
|
152
|
+
self._merge_api_hooks_into_model(m, self._api_hooks_map)
|
|
153
|
+
return _include_models(
|
|
154
|
+
self,
|
|
155
|
+
models,
|
|
156
|
+
app=None,
|
|
157
|
+
base_prefix=base_prefix,
|
|
158
|
+
mount_router=mount_router,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
async def rpc_call(
|
|
162
|
+
self,
|
|
163
|
+
model_or_name: type | str,
|
|
164
|
+
method: str,
|
|
165
|
+
payload: Any = None,
|
|
166
|
+
*,
|
|
167
|
+
db: Any,
|
|
168
|
+
request: Any = None,
|
|
169
|
+
ctx: Optional[Dict[str, Any]] = None,
|
|
170
|
+
) -> Any:
|
|
171
|
+
return await _rpc_call(
|
|
172
|
+
self, model_or_name, method, payload, db=db, request=request, ctx=ctx
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# ------------------------- extras / mounting -------------------------
|
|
176
|
+
|
|
177
|
+
def mount_jsonrpc(self, *, prefix: str | None = None) -> Any:
|
|
178
|
+
"""Mount a JSON-RPC router onto this TigrblApi instance."""
|
|
179
|
+
px = prefix if prefix is not None else self.jsonrpc_prefix
|
|
180
|
+
prov = _resolver.resolve_provider(api=self)
|
|
181
|
+
get_db = prov.get_db if prov else None
|
|
182
|
+
router = _mount_jsonrpc(
|
|
183
|
+
self,
|
|
184
|
+
self,
|
|
185
|
+
prefix=px,
|
|
186
|
+
get_db=get_db,
|
|
187
|
+
)
|
|
188
|
+
return router
|
|
189
|
+
|
|
190
|
+
def attach_diagnostics(
|
|
191
|
+
self, *, prefix: str | None = None, app: Any | None = None
|
|
192
|
+
) -> Any:
|
|
193
|
+
"""Mount a diagnostics router onto this TigrblApi instance or ``app``."""
|
|
194
|
+
px = prefix if prefix is not None else self.system_prefix
|
|
195
|
+
prov = _resolver.resolve_provider(api=self)
|
|
196
|
+
get_db = prov.get_db if prov else None
|
|
197
|
+
router = _mount_diagnostics(self, get_db=get_db)
|
|
198
|
+
include_self = getattr(self, "include_router", None)
|
|
199
|
+
if callable(include_self):
|
|
200
|
+
include_self(router, prefix=px)
|
|
201
|
+
if app is not None and app is not self:
|
|
202
|
+
include_other = getattr(app, "include_router", None)
|
|
203
|
+
if callable(include_other):
|
|
204
|
+
include_other(router, prefix=px)
|
|
205
|
+
return router
|
|
206
|
+
|
|
207
|
+
# ------------------------- registry passthroughs -------------------------
|
|
208
|
+
|
|
209
|
+
def registry(self, model: type):
|
|
210
|
+
"""Return the per-model OpspecRegistry."""
|
|
211
|
+
return get_registry(model)
|
|
212
|
+
|
|
213
|
+
def bind(self, model: type) -> Tuple[OpSpec, ...]:
|
|
214
|
+
"""Bind/rebuild a model in place (without mounting)."""
|
|
215
|
+
self._merge_api_hooks_into_model(model, self._api_hooks_map)
|
|
216
|
+
return _bind(model)
|
|
217
|
+
|
|
218
|
+
def rebind(
|
|
219
|
+
self, model: type, *, changed_keys: Optional[set[tuple[str, str]]] = None
|
|
220
|
+
) -> Tuple[OpSpec, ...]:
|
|
221
|
+
"""Targeted rebuild of a bound model."""
|
|
222
|
+
return _rebind(model, changed_keys=changed_keys)
|
|
223
|
+
|
|
224
|
+
# Optional: let callers set auth knobs used by some middlewares/dispatchers
|
|
225
|
+
def set_auth(
|
|
226
|
+
self,
|
|
227
|
+
*,
|
|
228
|
+
authn: Any = None,
|
|
229
|
+
allow_anon: Optional[bool] = None,
|
|
230
|
+
authorize: Any = None,
|
|
231
|
+
optional_authn_dep: Any = None,
|
|
232
|
+
) -> None:
|
|
233
|
+
if authn is not None:
|
|
234
|
+
self._authn = authn
|
|
235
|
+
if allow_anon is None:
|
|
236
|
+
allow_anon = False
|
|
237
|
+
if allow_anon is not None:
|
|
238
|
+
self._allow_anon = bool(allow_anon)
|
|
239
|
+
if authorize is not None:
|
|
240
|
+
self._authorize = authorize
|
|
241
|
+
if optional_authn_dep is not None:
|
|
242
|
+
self._optional_authn_dep = optional_authn_dep
|
|
243
|
+
|
|
244
|
+
# Refresh already-included models so routers pick up new auth settings
|
|
245
|
+
if self.models:
|
|
246
|
+
self._refresh_security()
|
|
247
|
+
|
|
248
|
+
def _refresh_security(self) -> None:
|
|
249
|
+
"""Re-seed auth deps on models and rebuild routers."""
|
|
250
|
+
# Reset routes and allow_anon ops cache
|
|
251
|
+
self.routes = []
|
|
252
|
+
self._allow_anon_ops = set()
|
|
253
|
+
for model in self.models.values():
|
|
254
|
+
_seed_security_and_deps(self, model)
|
|
255
|
+
specs = getattr(getattr(model, "opspecs", SimpleNamespace()), "all", ())
|
|
256
|
+
if specs:
|
|
257
|
+
_build_router_and_attach(model, list(specs))
|
|
258
|
+
router = getattr(getattr(model, "rest", SimpleNamespace()), "router", None)
|
|
259
|
+
if router is None:
|
|
260
|
+
continue
|
|
261
|
+
# update api-level references
|
|
262
|
+
mname = model.__name__
|
|
263
|
+
rest_ns = getattr(self.rest, mname, SimpleNamespace())
|
|
264
|
+
rest_ns.router = router
|
|
265
|
+
setattr(self.rest, mname, rest_ns)
|
|
266
|
+
self.routers[mname] = router
|
|
267
|
+
prefix = _default_prefix(model)
|
|
268
|
+
_mount_router(self, router, prefix=prefix)
|
|
269
|
+
|
|
270
|
+
def _collect_tables(self):
|
|
271
|
+
# dedupe; handle multiple DeclarativeBases (multiple metadatas)
|
|
272
|
+
seen = set()
|
|
273
|
+
tables = []
|
|
274
|
+
for m in self.models.values():
|
|
275
|
+
t = getattr(m, "__table__", None)
|
|
276
|
+
if t is not None and not t.columns:
|
|
277
|
+
continue
|
|
278
|
+
if t is not None and t not in seen:
|
|
279
|
+
seen.add(t)
|
|
280
|
+
tables.append(t)
|
|
281
|
+
return tables
|
|
282
|
+
|
|
283
|
+
initialize = _ddl_initialize
|
|
284
|
+
|
|
285
|
+
# ------------------------- repr -------------------------
|
|
286
|
+
|
|
287
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
288
|
+
models = list(getattr(self, "models", {}))
|
|
289
|
+
rpc_ns = getattr(self, "rpc", None)
|
|
290
|
+
rpc_keys = list(getattr(rpc_ns, "__dict__", {}).keys()) if rpc_ns else []
|
|
291
|
+
return f"<TigrblApi models={models} rpc={rpc_keys}>"
|
tigrbl/app/__init__.py
ADDED
|
File without changes
|
tigrbl/app/_app.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# tigrbl/tigrbl/v3/app/_app.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ..deps.fastapi import FastAPI
|
|
6
|
+
from ..engine.engine_spec import EngineCfg
|
|
7
|
+
from ..engine import resolver as _resolver
|
|
8
|
+
from ..engine import install_from_objects
|
|
9
|
+
from ..ddl import initialize as _ddl_initialize
|
|
10
|
+
from ._model_registry import initialize_model_registry
|
|
11
|
+
from .app_spec import AppSpec
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class App(AppSpec, FastAPI):
|
|
15
|
+
def __init__(
|
|
16
|
+
self, *, engine: EngineCfg | None = None, **fastapi_kwargs: Any
|
|
17
|
+
) -> None:
|
|
18
|
+
# Manually mirror ``AppSpec`` fields so the dataclass-generated ``repr``
|
|
19
|
+
# and friends have expected attributes while runtime structures remain
|
|
20
|
+
# mutable dictionaries or lists as needed.
|
|
21
|
+
self.title = self.TITLE
|
|
22
|
+
self.version = self.VERSION
|
|
23
|
+
self.engine = engine if engine is not None else getattr(self, "ENGINE", None)
|
|
24
|
+
self.apis = tuple(getattr(self, "APIS", ()))
|
|
25
|
+
self.ops = tuple(getattr(self, "OPS", ()))
|
|
26
|
+
# Runtime registries use mutable containers (dict/namespace), but the
|
|
27
|
+
# dataclass fields expect sequences. Storing a dict here satisfies both.
|
|
28
|
+
self.models = initialize_model_registry(getattr(self, "MODELS", ()))
|
|
29
|
+
self.schemas = tuple(getattr(self, "SCHEMAS", ()))
|
|
30
|
+
self.hooks = tuple(getattr(self, "HOOKS", ()))
|
|
31
|
+
self.security_deps = tuple(getattr(self, "SECURITY_DEPS", ()))
|
|
32
|
+
self.deps = tuple(getattr(self, "DEPS", ()))
|
|
33
|
+
self.response = getattr(self, "RESPONSE", None)
|
|
34
|
+
self.jsonrpc_prefix = getattr(self, "JSONRPC_PREFIX", "/rpc")
|
|
35
|
+
self.system_prefix = getattr(self, "SYSTEM_PREFIX", "/system")
|
|
36
|
+
self.middlewares = tuple(getattr(self, "MIDDLEWARES", ()))
|
|
37
|
+
self.lifespan = self.LIFESPAN
|
|
38
|
+
|
|
39
|
+
FastAPI.__init__(
|
|
40
|
+
self,
|
|
41
|
+
title=self.title,
|
|
42
|
+
version=self.version,
|
|
43
|
+
lifespan=self.lifespan,
|
|
44
|
+
**fastapi_kwargs,
|
|
45
|
+
)
|
|
46
|
+
_engine_ctx = self.engine
|
|
47
|
+
if _engine_ctx is not None:
|
|
48
|
+
_resolver.set_default(_engine_ctx)
|
|
49
|
+
_resolver.resolve_provider()
|
|
50
|
+
for mw in getattr(self, "MIDDLEWARES", []):
|
|
51
|
+
self.add_middleware(mw.__class__, **getattr(mw, "kwargs", {}))
|
|
52
|
+
|
|
53
|
+
def install_engines(
|
|
54
|
+
self, *, api: Any = None, models: tuple[Any, ...] | None = None
|
|
55
|
+
) -> None:
|
|
56
|
+
# If class declared APIS/MODELS, use them unless explicit args are passed.
|
|
57
|
+
apis = (api,) if api is not None else self.APIS
|
|
58
|
+
models = models if models is not None else self.MODELS
|
|
59
|
+
if apis:
|
|
60
|
+
for a in apis:
|
|
61
|
+
install_from_objects(app=self, api=a, models=models)
|
|
62
|
+
else:
|
|
63
|
+
install_from_objects(app=self, api=None, models=models)
|
|
64
|
+
|
|
65
|
+
def _collect_tables(self) -> list[Any]:
|
|
66
|
+
seen = set()
|
|
67
|
+
tables = []
|
|
68
|
+
for model in self.models.values():
|
|
69
|
+
if not hasattr(model, "__table__"):
|
|
70
|
+
try: # pragma: no cover - defensive remap
|
|
71
|
+
from ..table import Base
|
|
72
|
+
from ..table._base import _materialize_colspecs_to_sqla
|
|
73
|
+
|
|
74
|
+
_materialize_colspecs_to_sqla(model)
|
|
75
|
+
Base.registry.map_declaratively(model)
|
|
76
|
+
except Exception:
|
|
77
|
+
pass
|
|
78
|
+
table = getattr(model, "__table__", None)
|
|
79
|
+
if table is not None and not table.columns:
|
|
80
|
+
continue
|
|
81
|
+
if table is not None and table not in seen:
|
|
82
|
+
seen.add(table)
|
|
83
|
+
tables.append(table)
|
|
84
|
+
return tables
|
|
85
|
+
|
|
86
|
+
initialize = _ddl_initialize
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Utilities for initializing model registries on App and API facades."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Iterable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def initialize_model_registry(models: Iterable[Any]) -> dict[str, Any]:
|
|
9
|
+
"""Build the default ``models`` mapping for an App or Api instance.
|
|
10
|
+
|
|
11
|
+
``defineAppSpec``/``defineApiSpec`` allow authors to declare default models
|
|
12
|
+
using bare model classes or ``("alias", Model)`` tuples. Runtime facades,
|
|
13
|
+
however, expect ``self.models`` to be a dictionary keyed by model name so
|
|
14
|
+
that lookups like ``app.models["Widget"]`` Just Work.
|
|
15
|
+
|
|
16
|
+
This helper normalizes the declared sequence into that dictionary shape and
|
|
17
|
+
preserves declaration order. When an alias is provided we register both the
|
|
18
|
+
alias and the model's ``__name__`` so either lookup style succeeds.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
registry: dict[str, Any] = {}
|
|
22
|
+
|
|
23
|
+
for entry in models or ():
|
|
24
|
+
# Support ``("Alias", Model)`` declarations in addition to bare models.
|
|
25
|
+
if isinstance(entry, tuple) and len(entry) == 2 and isinstance(entry[0], str):
|
|
26
|
+
alias, model = entry
|
|
27
|
+
registry[alias] = model
|
|
28
|
+
model_name = getattr(model, "__name__", alias)
|
|
29
|
+
registry.setdefault(model_name, model)
|
|
30
|
+
continue
|
|
31
|
+
|
|
32
|
+
model = entry
|
|
33
|
+
model_name = getattr(model, "__name__", None)
|
|
34
|
+
if model_name is None:
|
|
35
|
+
model_name = str(model)
|
|
36
|
+
registry[model_name] = model
|
|
37
|
+
|
|
38
|
+
return registry
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
__all__ = ["initialize_model_registry"]
|
tigrbl/app/app_spec.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# tigrbl/tigrbl/v3/app/app_spec.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Callable, Optional, Sequence
|
|
5
|
+
|
|
6
|
+
from ..engine.engine_spec import EngineCfg
|
|
7
|
+
from ..response.types import ResponseSpec
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(eq=False)
|
|
11
|
+
class AppSpec:
|
|
12
|
+
"""
|
|
13
|
+
Used to *produce an App subclass* via App.from_spec().
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
title: str = "Tigrbl"
|
|
17
|
+
version: str = "0.1.0"
|
|
18
|
+
engine: Optional[EngineCfg] = None
|
|
19
|
+
|
|
20
|
+
# NEW: multi-API composition (store API classes or instances)
|
|
21
|
+
apis: Sequence[Any] = field(default_factory=tuple)
|
|
22
|
+
|
|
23
|
+
# NEW: orchestration/topology knobs
|
|
24
|
+
ops: Sequence[Any] = field(default_factory=tuple) # op descriptors or specs
|
|
25
|
+
models: Sequence[Any] = field(default_factory=tuple) # ORM classes
|
|
26
|
+
schemas: Sequence[Any] = field(default_factory=tuple) # schema classes/defs
|
|
27
|
+
hooks: Sequence[Callable[..., Any]] = field(default_factory=tuple)
|
|
28
|
+
|
|
29
|
+
# security/dep stacks (FastAPI dependencies or callables)
|
|
30
|
+
security_deps: Sequence[Callable[..., Any]] = field(default_factory=tuple)
|
|
31
|
+
deps: Sequence[Callable[..., Any]] = field(default_factory=tuple)
|
|
32
|
+
|
|
33
|
+
# response defaults
|
|
34
|
+
response: Optional[ResponseSpec] = None
|
|
35
|
+
|
|
36
|
+
# system routing prefixes (REST/JSON-RPC namespaces)
|
|
37
|
+
jsonrpc_prefix: str = "/rpc"
|
|
38
|
+
system_prefix: str = "/system"
|
|
39
|
+
|
|
40
|
+
# optional framework bits
|
|
41
|
+
middlewares: Sequence[Any] = field(default_factory=tuple)
|
|
42
|
+
lifespan: Optional[Callable[..., Any]] = None
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from functools import lru_cache
|
|
5
|
+
from typing import Any, Tuple
|
|
6
|
+
|
|
7
|
+
from .app_spec import AppSpec
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("uvicorn")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _merge_seq_attr(app: type, attr: str) -> Tuple[Any, ...]:
|
|
13
|
+
values: list[Any] = []
|
|
14
|
+
for base in reversed(app.__mro__):
|
|
15
|
+
seq = getattr(base, attr, ()) or ()
|
|
16
|
+
try:
|
|
17
|
+
values.extend(seq)
|
|
18
|
+
except TypeError: # non-iterable
|
|
19
|
+
values.append(seq)
|
|
20
|
+
return tuple(values)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@lru_cache(maxsize=None)
|
|
24
|
+
def mro_collect_app_spec(app: type) -> AppSpec:
|
|
25
|
+
"""Collect AppSpec-like declarations across the app's MRO."""
|
|
26
|
+
logger.info("Collecting app spec for %s", app.__name__)
|
|
27
|
+
|
|
28
|
+
title = "Tigrbl"
|
|
29
|
+
version = "0.1.0"
|
|
30
|
+
engine: Any | None = None
|
|
31
|
+
response = None
|
|
32
|
+
jsonrpc_prefix = "/rpc"
|
|
33
|
+
system_prefix = "/system"
|
|
34
|
+
lifespan = None
|
|
35
|
+
|
|
36
|
+
for base in reversed(app.__mro__):
|
|
37
|
+
title = getattr(base, "TITLE", title)
|
|
38
|
+
version = getattr(base, "VERSION", version)
|
|
39
|
+
eng = getattr(base, "ENGINE", None)
|
|
40
|
+
if eng is not None:
|
|
41
|
+
engine = eng
|
|
42
|
+
response = getattr(base, "RESPONSE", response)
|
|
43
|
+
jsonrpc_prefix = getattr(base, "JSONRPC_PREFIX", jsonrpc_prefix)
|
|
44
|
+
system_prefix = getattr(base, "SYSTEM_PREFIX", system_prefix)
|
|
45
|
+
lifespan = getattr(base, "LIFESPAN", lifespan)
|
|
46
|
+
|
|
47
|
+
spec = AppSpec(
|
|
48
|
+
title=title,
|
|
49
|
+
version=version,
|
|
50
|
+
engine=engine,
|
|
51
|
+
apis=_merge_seq_attr(app, "APIS"),
|
|
52
|
+
ops=_merge_seq_attr(app, "OPS"),
|
|
53
|
+
models=_merge_seq_attr(app, "MODELS"),
|
|
54
|
+
schemas=_merge_seq_attr(app, "SCHEMAS"),
|
|
55
|
+
hooks=_merge_seq_attr(app, "HOOKS"),
|
|
56
|
+
security_deps=_merge_seq_attr(app, "SECURITY_DEPS"),
|
|
57
|
+
deps=_merge_seq_attr(app, "DEPS"),
|
|
58
|
+
response=response,
|
|
59
|
+
jsonrpc_prefix=jsonrpc_prefix,
|
|
60
|
+
system_prefix=system_prefix,
|
|
61
|
+
middlewares=_merge_seq_attr(app, "MIDDLEWARES"),
|
|
62
|
+
lifespan=lifespan,
|
|
63
|
+
)
|
|
64
|
+
return spec
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
__all__ = ["mro_collect_app_spec"]
|
tigrbl/app/shortcuts.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# tigrbl/tigrbl/v3/app/shortcuts.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Sequence, Type
|
|
5
|
+
|
|
6
|
+
from .app_spec import AppSpec
|
|
7
|
+
from ._app import App
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def defineAppSpec(
|
|
11
|
+
*,
|
|
12
|
+
title: str = "Tigrbl",
|
|
13
|
+
version: str = "0.1.0",
|
|
14
|
+
engine: Any = None,
|
|
15
|
+
# composition
|
|
16
|
+
apis: Sequence[Any] = (),
|
|
17
|
+
ops: Sequence[Any] = (),
|
|
18
|
+
models: Sequence[Any] = (),
|
|
19
|
+
schemas: Sequence[Any] = (),
|
|
20
|
+
hooks: Sequence[Any] = (),
|
|
21
|
+
# deps & security
|
|
22
|
+
security_deps: Sequence[Any] = (),
|
|
23
|
+
deps: Sequence[Any] = (),
|
|
24
|
+
# prefixes
|
|
25
|
+
jsonrpc_prefix: str = "/rpc",
|
|
26
|
+
system_prefix: str = "/system",
|
|
27
|
+
# framework bits
|
|
28
|
+
middlewares: Sequence[Any] = (),
|
|
29
|
+
lifespan: Any = None,
|
|
30
|
+
) -> Type[AppSpec]:
|
|
31
|
+
"""
|
|
32
|
+
Build an App-spec class with class attributes only (no instances).
|
|
33
|
+
Use it directly in your class MRO:
|
|
34
|
+
|
|
35
|
+
class MyApp(defineAppSpec(title="Svc", engine=...)):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
or pass it to `deriveApp(...)` to get a concrete App subclass.
|
|
39
|
+
"""
|
|
40
|
+
attrs = dict(
|
|
41
|
+
TITLE=title,
|
|
42
|
+
VERSION=version,
|
|
43
|
+
ENGINE=engine,
|
|
44
|
+
APIS=tuple(apis or ()),
|
|
45
|
+
OPS=tuple(ops or ()),
|
|
46
|
+
MODELS=tuple(models or ()),
|
|
47
|
+
SCHEMAS=tuple(schemas or ()),
|
|
48
|
+
HOOKS=tuple(hooks or ()),
|
|
49
|
+
SECURITY_DEPS=tuple(security_deps or ()),
|
|
50
|
+
DEPS=tuple(deps or ()),
|
|
51
|
+
JSONRPC_PREFIX=jsonrpc_prefix,
|
|
52
|
+
SYSTEM_PREFIX=system_prefix,
|
|
53
|
+
MIDDLEWARES=tuple(middlewares or ()),
|
|
54
|
+
LIFESPAN=lifespan,
|
|
55
|
+
)
|
|
56
|
+
return type("AppSpec", (AppSpec,), attrs)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def deriveApp(**kw: Any) -> Type[App]:
|
|
60
|
+
"""Produce a concrete :class:`App` subclass that inherits the spec."""
|
|
61
|
+
Spec = defineAppSpec(**kw)
|
|
62
|
+
return type("AppWithSpec", (Spec, App), {})
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
__all__ = ["defineAppSpec", "deriveApp"]
|