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.
- tigrbl/README.md +94 -0
- tigrbl/__init__.py +139 -14
- tigrbl/api/__init__.py +6 -0
- tigrbl/api/_api.py +72 -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 +286 -0
- tigrbl/app/__init__.py +0 -0
- tigrbl/app/_app.py +61 -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 +314 -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 +265 -0
- tigrbl/bindings/rest/common.py +116 -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/member.py +367 -0
- tigrbl/bindings/rest/router.py +292 -0
- tigrbl/bindings/rest/routing.py +133 -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 +133 -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 +26 -0
- tigrbl/engine/_engine.py +130 -0
- tigrbl/engine/bind.py +33 -0
- tigrbl/engine/builders.py +236 -0
- tigrbl/engine/collect.py +111 -0
- tigrbl/engine/decorators.py +108 -0
- tigrbl/engine/engine_spec.py +261 -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 +111 -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 +144 -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 +17 -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 +628 -0
- tigrbl/runtime/labels.py +353 -0
- tigrbl/runtime/opview.py +87 -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 +55 -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/shortcuts.py +22 -0
- tigrbl/specs.py +44 -0
- tigrbl/system/__init__.py +12 -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/table/__init__.py +9 -0
- tigrbl/table/_base.py +237 -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 +174 -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-0.3.0.dev3.dist-info/LICENSE +201 -0
- tigrbl-0.3.0.dev3.dist-info/METADATA +501 -0
- tigrbl-0.3.0.dev3.dist-info/RECORD +249 -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-0.0.1.dev1.dist-info → tigrbl-0.3.0.dev3.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def build_methodz_endpoint(api: Any):
|
|
7
|
+
cache: Optional[Dict[str, List[Dict[str, Any]]]] = None
|
|
8
|
+
|
|
9
|
+
async def _methodz():
|
|
10
|
+
nonlocal cache
|
|
11
|
+
"""Ordered, canonical operation list."""
|
|
12
|
+
if cache is not None:
|
|
13
|
+
return cache
|
|
14
|
+
|
|
15
|
+
from . import _model_iter, _opspecs
|
|
16
|
+
|
|
17
|
+
methods: List[Dict[str, Any]] = []
|
|
18
|
+
for model in _model_iter(api):
|
|
19
|
+
mname = getattr(model, "__name__", "Model")
|
|
20
|
+
for sp in _opspecs(model):
|
|
21
|
+
if not getattr(sp, "expose_rpc", True):
|
|
22
|
+
continue
|
|
23
|
+
methods.append(
|
|
24
|
+
{
|
|
25
|
+
"method": f"{mname}.{sp.alias}",
|
|
26
|
+
"model": mname,
|
|
27
|
+
"alias": sp.alias,
|
|
28
|
+
"target": sp.target,
|
|
29
|
+
"arity": sp.arity,
|
|
30
|
+
"persist": sp.persist,
|
|
31
|
+
"request_model": getattr(sp, "request_model", None) is not None,
|
|
32
|
+
"response_model": getattr(sp, "response_model", None)
|
|
33
|
+
is not None,
|
|
34
|
+
"routes": bool(getattr(sp, "expose_routes", True)),
|
|
35
|
+
"rpc": bool(getattr(sp, "expose_rpc", True)),
|
|
36
|
+
"tags": list(getattr(sp, "tags", ()) or (mname,)),
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
methods.sort(key=lambda x: (x["model"], x["alias"]))
|
|
40
|
+
cache = {"methods": methods}
|
|
41
|
+
return cache
|
|
42
|
+
|
|
43
|
+
return _methodz
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Optional
|
|
4
|
+
|
|
5
|
+
from .compat import Router
|
|
6
|
+
from .healthz import build_healthz_endpoint
|
|
7
|
+
from .methodz import build_methodz_endpoint
|
|
8
|
+
from .hookz import build_hookz_endpoint
|
|
9
|
+
from .kernelz import build_kernelz_endpoint
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def mount_diagnostics(
|
|
13
|
+
api: Any,
|
|
14
|
+
*,
|
|
15
|
+
get_db: Optional[Callable[..., Any]] = None,
|
|
16
|
+
) -> Router:
|
|
17
|
+
"""
|
|
18
|
+
Create & return a Router that exposes:
|
|
19
|
+
GET /healthz
|
|
20
|
+
GET /methodz
|
|
21
|
+
GET /hookz
|
|
22
|
+
GET /kernelz
|
|
23
|
+
"""
|
|
24
|
+
router = Router()
|
|
25
|
+
|
|
26
|
+
dep = get_db
|
|
27
|
+
|
|
28
|
+
router.add_api_route(
|
|
29
|
+
"/healthz",
|
|
30
|
+
build_healthz_endpoint(dep),
|
|
31
|
+
methods=["GET"],
|
|
32
|
+
name="healthz",
|
|
33
|
+
tags=["system"],
|
|
34
|
+
summary="Health",
|
|
35
|
+
description="Database connectivity check.",
|
|
36
|
+
)
|
|
37
|
+
router.add_api_route(
|
|
38
|
+
"/methodz",
|
|
39
|
+
build_methodz_endpoint(api),
|
|
40
|
+
methods=["GET"],
|
|
41
|
+
name="methodz",
|
|
42
|
+
tags=["system"],
|
|
43
|
+
summary="Methods",
|
|
44
|
+
description="Ordered, canonical operation list.",
|
|
45
|
+
)
|
|
46
|
+
router.add_api_route(
|
|
47
|
+
"/hookz",
|
|
48
|
+
build_hookz_endpoint(api),
|
|
49
|
+
methods=["GET"],
|
|
50
|
+
name="hookz",
|
|
51
|
+
tags=["system"],
|
|
52
|
+
summary="Hooks",
|
|
53
|
+
description=(
|
|
54
|
+
"Expose hook execution order for each method.\n\n"
|
|
55
|
+
"Phases appear in runner order; error phases trail.\n"
|
|
56
|
+
"Within each phase, hooks are listed in execution order: "
|
|
57
|
+
"global (None) hooks, then method-specific hooks."
|
|
58
|
+
),
|
|
59
|
+
)
|
|
60
|
+
router.add_api_route(
|
|
61
|
+
"/kernelz",
|
|
62
|
+
build_kernelz_endpoint(api),
|
|
63
|
+
methods=["GET"],
|
|
64
|
+
name="kernelz",
|
|
65
|
+
tags=["system"],
|
|
66
|
+
summary="Kernel Plan",
|
|
67
|
+
description="Phase-chain plan as built by the kernel per operation.",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return router
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
__all__ = ["mount_diagnostics"]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from types import SimpleNamespace
|
|
5
|
+
from typing import Any, Iterable
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import text
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def model_iter(api: Any) -> Iterable[type]:
|
|
11
|
+
models = getattr(api, "models", {}) or {}
|
|
12
|
+
return models.values()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def opspecs(model: type):
|
|
16
|
+
return getattr(getattr(model, "opspecs", SimpleNamespace()), "all", ()) or ()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def label_callable(fn: Any) -> str:
|
|
20
|
+
n = getattr(fn, "__qualname__", getattr(fn, "__name__", repr(fn)))
|
|
21
|
+
m = getattr(fn, "__module__", None)
|
|
22
|
+
return f"{m}.{n}" if m else n
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def label_hook(fn: Any, phase: str) -> str:
|
|
26
|
+
label = getattr(fn, "__tigrbl_label", None)
|
|
27
|
+
if isinstance(label, str):
|
|
28
|
+
return label
|
|
29
|
+
subj = label_callable(fn).replace(".", ":")
|
|
30
|
+
return f"hook:wire:{subj}@{phase}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def maybe_execute(db: Any, stmt: str):
|
|
34
|
+
try:
|
|
35
|
+
rv = db.execute(text(stmt)) # type: ignore[attr-defined]
|
|
36
|
+
if inspect.isawaitable(rv):
|
|
37
|
+
return await rv
|
|
38
|
+
return rv
|
|
39
|
+
except Exception:
|
|
40
|
+
rv = db.execute(text("select 1")) # type: ignore[attr-defined]
|
|
41
|
+
if inspect.isawaitable(rv):
|
|
42
|
+
return await rv
|
|
43
|
+
return rv
|
tigrbl/table/__init__.py
ADDED
tigrbl/table/_base.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# tigrbl/tigrbl/v3/table/_base.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Optional, Union, get_args, get_origin
|
|
5
|
+
from enum import Enum as PyEnum
|
|
6
|
+
|
|
7
|
+
from sqlalchemy.orm import DeclarativeBase, declared_attr, mapped_column
|
|
8
|
+
from sqlalchemy import CheckConstraint, ForeignKey, MetaData
|
|
9
|
+
from sqlalchemy.types import Enum as SAEnum, String
|
|
10
|
+
|
|
11
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
12
|
+
# Helpers – type inference & SA type instantiation
|
|
13
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _unwrap_optional(t: Any) -> Any:
|
|
17
|
+
"""Optional[T] / Union[T, None] → T"""
|
|
18
|
+
if get_origin(t) is Union:
|
|
19
|
+
args = [a for a in get_args(t) if a is not type(None)]
|
|
20
|
+
return args[0] if args else t
|
|
21
|
+
return t
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _infer_py_type(cls, name: str, spec: Any) -> Optional[type]:
|
|
25
|
+
"""
|
|
26
|
+
Prefer FieldSpec.py_type if provided; otherwise unwrap Mapped[...] / Optional[...]
|
|
27
|
+
from the class' annotation to get the real Python type for the column.
|
|
28
|
+
"""
|
|
29
|
+
fld = getattr(spec, "field", None)
|
|
30
|
+
py = getattr(fld, "py_type", None)
|
|
31
|
+
if isinstance(py, type):
|
|
32
|
+
return py
|
|
33
|
+
|
|
34
|
+
ann = getattr(cls, "__annotations__", {}).get(name)
|
|
35
|
+
if ann is None:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
# Mapped[T] → T (then unwrap Optional)
|
|
39
|
+
try:
|
|
40
|
+
from ..types import Mapped
|
|
41
|
+
|
|
42
|
+
if get_origin(ann) is Mapped:
|
|
43
|
+
inner = get_args(ann)[0]
|
|
44
|
+
return _unwrap_optional(inner)
|
|
45
|
+
except Exception:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
# Optional[T]/Union[T, None] → T
|
|
49
|
+
return _unwrap_optional(ann)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _instantiate_dtype(
|
|
53
|
+
dtype: Any, py_type: Any, spec: Any, cls_name: str, col_name: str
|
|
54
|
+
):
|
|
55
|
+
"""
|
|
56
|
+
Create a SQLAlchemy TypeEngine instance from either a type CLASS or an instance.
|
|
57
|
+
- SAEnum: instantiate from the actual Enum class with a stable name
|
|
58
|
+
- String: honor FieldSpec.constraints['max_length'] if present
|
|
59
|
+
- UUID (PG): prefer as_uuid=True when available
|
|
60
|
+
"""
|
|
61
|
+
# Already an instance? keep it.
|
|
62
|
+
try:
|
|
63
|
+
from sqlalchemy.sql.type_api import TypeEngine
|
|
64
|
+
|
|
65
|
+
if isinstance(dtype, TypeEngine):
|
|
66
|
+
return dtype
|
|
67
|
+
except Exception:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
# SAEnum from a Python Enum class
|
|
71
|
+
if dtype is SAEnum and isinstance(py_type, type) and issubclass(py_type, PyEnum):
|
|
72
|
+
enum_name = f"{cls_name.lower()}_{col_name.lower()}"
|
|
73
|
+
return SAEnum(py_type, name=enum_name, native_enum=True, validate_strings=True)
|
|
74
|
+
|
|
75
|
+
# String – pick up max_length from FieldSpec
|
|
76
|
+
if dtype is String:
|
|
77
|
+
max_len = getattr(getattr(spec, "field", None), "constraints", {}).get(
|
|
78
|
+
"max_length"
|
|
79
|
+
)
|
|
80
|
+
return String(max_len) if max_len else String()
|
|
81
|
+
|
|
82
|
+
# PostgreSQL UUID (or similar) – try as_uuid=True first
|
|
83
|
+
try:
|
|
84
|
+
return dtype(as_uuid=True) # e.g., PG UUID
|
|
85
|
+
except TypeError:
|
|
86
|
+
try:
|
|
87
|
+
return dtype()
|
|
88
|
+
except TypeError:
|
|
89
|
+
# As a last resort, return the class; SQLA will raise clearly if unusable
|
|
90
|
+
return dtype
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _materialize_colspecs_to_sqla(cls) -> None:
|
|
94
|
+
"""
|
|
95
|
+
Replace ColumnSpec attributes with sqlalchemy.orm.mapped_column(...) BEFORE mapping.
|
|
96
|
+
Keep the original specs in __tigrbl_cols__ for downstream builders.
|
|
97
|
+
"""
|
|
98
|
+
try:
|
|
99
|
+
from tigrbl.column.column_spec import ColumnSpec
|
|
100
|
+
except Exception:
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
# Prefer explicit registry if present; otherwise collect specs from the
|
|
104
|
+
# entire MRO so mixins contribute their ColumnSpec definitions.
|
|
105
|
+
specs: dict[str, ColumnSpec] = {}
|
|
106
|
+
for base in reversed(cls.__mro__):
|
|
107
|
+
base_specs = getattr(base, "__tigrbl_cols__", None)
|
|
108
|
+
if isinstance(base_specs, dict) and base_specs:
|
|
109
|
+
specs.update(base_specs)
|
|
110
|
+
continue
|
|
111
|
+
for name, attr in getattr(base, "__dict__", {}).items():
|
|
112
|
+
if isinstance(attr, ColumnSpec):
|
|
113
|
+
specs.setdefault(name, attr)
|
|
114
|
+
|
|
115
|
+
if not specs:
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
# Ensure downstream code can find the spec map
|
|
119
|
+
setattr(cls, "__tigrbl_cols__", dict(specs))
|
|
120
|
+
|
|
121
|
+
for name, spec in specs.items():
|
|
122
|
+
storage = getattr(spec, "storage", None)
|
|
123
|
+
if not storage:
|
|
124
|
+
# Virtual (wire-only) column – no DB column
|
|
125
|
+
continue
|
|
126
|
+
|
|
127
|
+
dtype = getattr(storage, "type_", None)
|
|
128
|
+
if not dtype:
|
|
129
|
+
# No SA dtype specified – cannot materialize
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
py_type = _infer_py_type(cls, name, spec)
|
|
133
|
+
dtype_inst = _instantiate_dtype(dtype, py_type, spec, cls.__name__, name)
|
|
134
|
+
|
|
135
|
+
# Foreign key (if any)
|
|
136
|
+
fk = getattr(storage, "fk", None)
|
|
137
|
+
fk_arg = None
|
|
138
|
+
if fk is not None:
|
|
139
|
+
# ForeignKeySpec: target="table(col)", on_delete/on_update: "CASCADE"/...
|
|
140
|
+
fk_arg = ForeignKey(fk.target, ondelete=fk.on_delete, onupdate=fk.on_update)
|
|
141
|
+
|
|
142
|
+
check = getattr(storage, "check", None)
|
|
143
|
+
args: list[Any] = []
|
|
144
|
+
if fk_arg is not None:
|
|
145
|
+
args.append(fk_arg)
|
|
146
|
+
if check is not None:
|
|
147
|
+
cname = f"ck_{cls.__name__.lower()}_{name}"
|
|
148
|
+
args.append(CheckConstraint(check, name=cname))
|
|
149
|
+
|
|
150
|
+
# Build mapped_column from StorageSpec flags
|
|
151
|
+
mc = mapped_column(
|
|
152
|
+
dtype_inst,
|
|
153
|
+
*args,
|
|
154
|
+
primary_key=getattr(storage, "primary_key", False),
|
|
155
|
+
nullable=getattr(storage, "nullable", True),
|
|
156
|
+
unique=getattr(storage, "unique", False),
|
|
157
|
+
index=getattr(storage, "index", False),
|
|
158
|
+
default=getattr(storage, "default", None),
|
|
159
|
+
onupdate=getattr(storage, "onupdate", None),
|
|
160
|
+
server_default=getattr(storage, "server_default", None),
|
|
161
|
+
comment=getattr(storage, "comment", None),
|
|
162
|
+
autoincrement=getattr(storage, "autoincrement", None),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
setattr(cls, name, mc)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
169
|
+
# Declarative Base
|
|
170
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class Base(DeclarativeBase):
|
|
174
|
+
__allow_unmapped__ = True
|
|
175
|
+
|
|
176
|
+
def __init_subclass__(cls, **kw):
|
|
177
|
+
# 0) Remove any previously registered class with the same name.
|
|
178
|
+
try:
|
|
179
|
+
reg = Base.registry._class_registry
|
|
180
|
+
keys = [cls.__name__, f"{cls.__module__}.{cls.__name__}"]
|
|
181
|
+
existing = next((reg.get(k) for k in keys if reg.get(k) is not None), None)
|
|
182
|
+
if existing is not None:
|
|
183
|
+
try:
|
|
184
|
+
Base.registry._dispose_cls(existing)
|
|
185
|
+
except Exception:
|
|
186
|
+
pass
|
|
187
|
+
for k in keys:
|
|
188
|
+
reg.pop(k, None)
|
|
189
|
+
except Exception:
|
|
190
|
+
pass
|
|
191
|
+
|
|
192
|
+
# 1) BEFORE SQLAlchemy maps: turn ColumnSpecs into real mapped_column(...)
|
|
193
|
+
_materialize_colspecs_to_sqla(cls)
|
|
194
|
+
|
|
195
|
+
# 2) Let SQLAlchemy map the class (PK now exists)
|
|
196
|
+
super().__init_subclass__(**kw)
|
|
197
|
+
|
|
198
|
+
# 3) Seed model namespaces / index specs (ops/hooks/etc.) – idempotent
|
|
199
|
+
try:
|
|
200
|
+
from tigrbl.bindings import model as _model_bind
|
|
201
|
+
|
|
202
|
+
_model_bind.bind(cls)
|
|
203
|
+
except Exception:
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
# 3) AUTO-BUILD CRUD schemas from ColumnSpecs so /docs has them
|
|
207
|
+
try:
|
|
208
|
+
from tigrbl.schema.build import build_for_model as _build_schemas
|
|
209
|
+
|
|
210
|
+
_build_schemas(
|
|
211
|
+
cls
|
|
212
|
+
) # attaches request/response models to the model/registry
|
|
213
|
+
except Exception:
|
|
214
|
+
# Surface during development if needed:
|
|
215
|
+
# raise
|
|
216
|
+
pass
|
|
217
|
+
|
|
218
|
+
metadata = MetaData(
|
|
219
|
+
naming_convention={
|
|
220
|
+
"pk": "pk_%(table_name)s",
|
|
221
|
+
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
|
|
222
|
+
"ix": "ix_%(table_name)s_%(column_0_name)s",
|
|
223
|
+
"uq": "uq_%(table_name)s_%(column_0_name)s",
|
|
224
|
+
"ck": "ck_%(table_name)s_%(column_0_name)s_%(constraint_type)s",
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
@declared_attr.directive
|
|
229
|
+
def __tablename__(cls) -> str: # noqa: N805
|
|
230
|
+
return cls.__name__.lower()
|
|
231
|
+
|
|
232
|
+
def __getitem__(self, key: str) -> Any:
|
|
233
|
+
"""Allow dict-style access to model attributes."""
|
|
234
|
+
return getattr(self, key)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
__all__ = ["Base"]
|
tigrbl/table/_table.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# tigrbl/tigrbl/v3/table/_table.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from types import SimpleNamespace
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
from ..engine._engine import AsyncSession, Session
|
|
8
|
+
from ..engine import install_from_objects # reuse the collector
|
|
9
|
+
from ..engine import resolver as _resolver
|
|
10
|
+
from ..ddl import initialize as _ddl_initialize
|
|
11
|
+
from ._base import Base
|
|
12
|
+
from .table_spec import TableSpec
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Table(Base, TableSpec):
|
|
16
|
+
"""Declarative ORM table base.
|
|
17
|
+
|
|
18
|
+
This class now integrates :class:`Base` so ORM models and tables share
|
|
19
|
+
the same type. Column specifications are exposed via ``columns`` for
|
|
20
|
+
convenience.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
__abstract__ = True
|
|
24
|
+
columns: SimpleNamespace = SimpleNamespace()
|
|
25
|
+
|
|
26
|
+
def __init__(self, **kw: Any) -> None: # pragma: no cover - SQLA sets attrs
|
|
27
|
+
for k, v in kw.items():
|
|
28
|
+
setattr(self, k, v)
|
|
29
|
+
|
|
30
|
+
def __init_subclass__(cls, **kw: Any) -> None: # noqa: D401
|
|
31
|
+
super().__init_subclass__(**kw)
|
|
32
|
+
|
|
33
|
+
# expose ColumnSpecs under `columns` namespace
|
|
34
|
+
specs = getattr(cls, "__tigrbl_cols__", {})
|
|
35
|
+
cls.columns = SimpleNamespace(**specs)
|
|
36
|
+
|
|
37
|
+
# auto-register table-level bindings if declared
|
|
38
|
+
try:
|
|
39
|
+
install_from_objects(models=[cls])
|
|
40
|
+
except Exception: # pragma: no cover - best effort
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def install_engines(cls, *, api: Any | None = None) -> None:
|
|
45
|
+
install_from_objects(api=api, models=[cls])
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def acquire(
|
|
49
|
+
cls, *, op_alias: str | None = None
|
|
50
|
+
) -> tuple[Session | AsyncSession, Callable[[], None]]:
|
|
51
|
+
db, release = _resolver.acquire(model=cls, op_alias=op_alias)
|
|
52
|
+
return db, release
|
|
53
|
+
|
|
54
|
+
initialize = classmethod(_ddl_initialize)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from functools import lru_cache
|
|
5
|
+
from typing import Any, Mapping, Tuple
|
|
6
|
+
|
|
7
|
+
from .table_spec import TableSpec
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("uvicorn")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _merge_seq_attr(model: type, attr: str) -> Tuple[Any, ...]:
|
|
13
|
+
values: list[Any] = []
|
|
14
|
+
for base in model.__mro__:
|
|
15
|
+
seq = base.__dict__.get(attr, ()) or ()
|
|
16
|
+
try:
|
|
17
|
+
values.extend(seq)
|
|
18
|
+
except TypeError: # pragma: no cover - non-iterable
|
|
19
|
+
values.append(seq)
|
|
20
|
+
return tuple(values)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@lru_cache(maxsize=None)
|
|
24
|
+
def mro_collect_table_spec(model: type) -> TableSpec:
|
|
25
|
+
"""Collect TableSpec-like declarations across the model's MRO.
|
|
26
|
+
|
|
27
|
+
Merges common spec attributes (OPS, COLUMNS, SCHEMAS, HOOKS, SECURITY_DEPS,
|
|
28
|
+
DEPS) declared on the class or any mixins. Engine bindings declared via
|
|
29
|
+
``table_config`` use the same precedence: later classes in the MRO override
|
|
30
|
+
earlier ones.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
logger.info("Collecting table spec for %s", model.__name__)
|
|
34
|
+
|
|
35
|
+
engine: Any | None = None
|
|
36
|
+
for base in model.__mro__:
|
|
37
|
+
cfg = base.__dict__.get("table_config")
|
|
38
|
+
if isinstance(cfg, Mapping):
|
|
39
|
+
eng = (
|
|
40
|
+
cfg.get("engine")
|
|
41
|
+
or cfg.get("db")
|
|
42
|
+
or cfg.get("database")
|
|
43
|
+
or cfg.get("engine_provider")
|
|
44
|
+
or cfg.get("db_provider")
|
|
45
|
+
)
|
|
46
|
+
if eng is not None:
|
|
47
|
+
engine = eng
|
|
48
|
+
|
|
49
|
+
spec = TableSpec(
|
|
50
|
+
model=model,
|
|
51
|
+
engine=engine,
|
|
52
|
+
ops=_merge_seq_attr(model, "OPS"),
|
|
53
|
+
columns=_merge_seq_attr(model, "COLUMNS"),
|
|
54
|
+
schemas=_merge_seq_attr(model, "SCHEMAS"),
|
|
55
|
+
hooks=_merge_seq_attr(model, "HOOKS"),
|
|
56
|
+
security_deps=_merge_seq_attr(model, "SECURITY_DEPS"),
|
|
57
|
+
deps=_merge_seq_attr(model, "DEPS"),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
logger.debug(
|
|
61
|
+
"Collected table spec for %s: %d ops, %d columns",
|
|
62
|
+
model.__name__,
|
|
63
|
+
len(spec.ops),
|
|
64
|
+
len(spec.columns),
|
|
65
|
+
)
|
|
66
|
+
return spec
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
__all__ = ["mro_collect_table_spec"]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# tigrbl/tigrbl/v3/table/shortcuts.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Sequence, Type
|
|
5
|
+
|
|
6
|
+
from .table_spec import TableSpec
|
|
7
|
+
from ._table import Table
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def defineTableSpec(
|
|
11
|
+
*,
|
|
12
|
+
# engine binding
|
|
13
|
+
engine: Any = None,
|
|
14
|
+
# composition
|
|
15
|
+
ops: Sequence[Any] = (),
|
|
16
|
+
columns: Sequence[Any] = (),
|
|
17
|
+
schemas: Sequence[Any] = (),
|
|
18
|
+
hooks: Sequence[Any] = (),
|
|
19
|
+
# dependency stacks
|
|
20
|
+
security_deps: Sequence[Any] = (),
|
|
21
|
+
deps: Sequence[Any] = (),
|
|
22
|
+
) -> Type[TableSpec]:
|
|
23
|
+
"""
|
|
24
|
+
Build a Table-spec class with class attributes only (no instances).
|
|
25
|
+
Use directly in your ORM class MRO:
|
|
26
|
+
|
|
27
|
+
class User(defineTableSpec(engine=..., ops=(...)), Base, Table):
|
|
28
|
+
__tablename__ = "users"
|
|
29
|
+
|
|
30
|
+
or pass it to `deriveTable(Model, ...)` to get a configured subclass.
|
|
31
|
+
"""
|
|
32
|
+
attrs = {
|
|
33
|
+
# top-level mirrors read by collectors
|
|
34
|
+
"OPS": tuple(ops or ()),
|
|
35
|
+
"COLUMNS": tuple(columns or ()),
|
|
36
|
+
"SCHEMAS": tuple(schemas or ()),
|
|
37
|
+
"HOOKS": tuple(hooks or ()),
|
|
38
|
+
"SECURITY_DEPS": tuple(security_deps or ()),
|
|
39
|
+
"DEPS": tuple(deps or ()),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Engine binding is conventionally stored under table_config["engine"]
|
|
43
|
+
# (and legacy "db" for backward compatibility) so collectors can find it.
|
|
44
|
+
if engine is not None:
|
|
45
|
+
attrs["table_config"] = {"engine": engine, "db": engine}
|
|
46
|
+
|
|
47
|
+
return type("TableSpec", (TableSpec,), attrs)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def deriveTable(model: Type[Table], **kw: Any) -> Type[Table]:
|
|
51
|
+
"""Produce a concrete ORM subclass that inherits the spec."""
|
|
52
|
+
Spec = defineTableSpec(**kw)
|
|
53
|
+
name = f"{model.__name__}WithSpec"
|
|
54
|
+
return type(name, (Spec, model), {})
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
__all__ = ["defineTableSpec", "deriveTable"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# tigrbl/tigrbl/v3/table/table_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
|
|
11
|
+
class TableSpec:
|
|
12
|
+
"""
|
|
13
|
+
Declarative enrichments for an ORM class (model == table).
|
|
14
|
+
This does not construct an instance; it decorates/produces a class.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
model: Any # ORM class
|
|
18
|
+
engine: Optional[EngineCfg] = None
|
|
19
|
+
|
|
20
|
+
# NEW
|
|
21
|
+
ops: Sequence[Any] = field(default_factory=tuple) # OpSpec or shorthands
|
|
22
|
+
columns: Sequence[Any] = field(default_factory=tuple) # ColumnSpec or shorthands
|
|
23
|
+
schemas: Sequence[Any] = field(default_factory=tuple)
|
|
24
|
+
hooks: Sequence[Callable[..., Any]] = field(default_factory=tuple)
|
|
25
|
+
security_deps: Sequence[Callable[..., Any]] = field(default_factory=tuple)
|
|
26
|
+
deps: Sequence[Callable[..., Any]] = field(default_factory=tuple)
|
|
27
|
+
|
|
28
|
+
response: Optional[ResponseSpec] = None
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# tigrbl/v3/transport/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Tigrbl v3 – Transport package.
|
|
4
|
+
|
|
5
|
+
Routers & helpers for exposing your API over JSON-RPC and REST.
|
|
6
|
+
|
|
7
|
+
Quick usage:
|
|
8
|
+
from tigrbl.transport import (
|
|
9
|
+
build_jsonrpc_router, mount_jsonrpc,
|
|
10
|
+
build_rest_router, mount_rest,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
# JSON-RPC
|
|
14
|
+
app.include_router(build_jsonrpc_router(api), prefix="/rpc")
|
|
15
|
+
# or supply a DB dependency from an Engine or Provider:
|
|
16
|
+
mount_jsonrpc(api, app, prefix="/rpc", get_db=my_engine.get_db)
|
|
17
|
+
|
|
18
|
+
# REST (aggregate all model routers under one prefix)
|
|
19
|
+
# after you include models with mount_router=False
|
|
20
|
+
app.include_router(build_rest_router(api, base_prefix="/api"))
|
|
21
|
+
# or:
|
|
22
|
+
mount_rest(api, app, base_prefix="/api")
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from typing import Any, Callable, Optional, Sequence
|
|
28
|
+
|
|
29
|
+
# JSON-RPC transport
|
|
30
|
+
from .jsonrpc import build_jsonrpc_router
|
|
31
|
+
|
|
32
|
+
# REST transport (aggregator over per-model routers)
|
|
33
|
+
from .rest import build_rest_router, mount_rest
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def mount_jsonrpc(
|
|
37
|
+
api: Any,
|
|
38
|
+
app: Any,
|
|
39
|
+
*,
|
|
40
|
+
prefix: str = "/rpc",
|
|
41
|
+
get_db: Optional[Callable[..., Any]] = None,
|
|
42
|
+
tags: Sequence[str] | None = ("rpc",),
|
|
43
|
+
):
|
|
44
|
+
"""
|
|
45
|
+
Build a JSON-RPC router for `api` and include it on the given FastAPI `app`
|
|
46
|
+
(or any object exposing `include_router`).
|
|
47
|
+
|
|
48
|
+
Returns the created router so you can keep a reference if desired.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
tags:
|
|
53
|
+
Optional tags applied to the mounted "/rpc" endpoint. Defaults to
|
|
54
|
+
``("rpc",)``.
|
|
55
|
+
"""
|
|
56
|
+
router = build_jsonrpc_router(
|
|
57
|
+
api,
|
|
58
|
+
get_db=get_db,
|
|
59
|
+
tags=tags,
|
|
60
|
+
)
|
|
61
|
+
include_router = getattr(app, "include_router", None)
|
|
62
|
+
if callable(include_router):
|
|
63
|
+
include_router(router, prefix=prefix)
|
|
64
|
+
return router
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
__all__ = [
|
|
68
|
+
# JSON-RPC
|
|
69
|
+
"build_jsonrpc_router",
|
|
70
|
+
"mount_jsonrpc",
|
|
71
|
+
# REST
|
|
72
|
+
"build_rest_router",
|
|
73
|
+
"mount_rest",
|
|
74
|
+
]
|