tigrbl 0.3.3.dev2__py3-none-any.whl → 0.3.4.dev2__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/deps/__init__.py CHANGED
@@ -16,5 +16,5 @@ from .pydantic import * # noqa: F403, F401
16
16
  # Re-export all FastAPI dependencies
17
17
  from .fastapi import * # noqa: F403, F401
18
18
 
19
- # Note: starlette.py is reserved for future Starlette-specific imports
20
- # if we need to import directly from Starlette rather than through FastAPI
19
+ # Re-export Starlette dependencies (Request/Response live here)
20
+ from .starlette import * # noqa: F403, F401
tigrbl/deps/fastapi.py CHANGED
@@ -4,8 +4,6 @@ from fastapi import (
4
4
  FastAPI,
5
5
  Security,
6
6
  Depends,
7
- Request,
8
- Response,
9
7
  Path as FastAPIPath,
10
8
  Body,
11
9
  HTTPException,
@@ -36,8 +34,6 @@ __all__ = [
36
34
  "FastAPI",
37
35
  "Security",
38
36
  "Depends",
39
- "Request",
40
- "Response",
41
37
  "Path",
42
38
  "Body",
43
39
  "HTTPException",
@@ -8,23 +8,17 @@ from typing import (
8
8
  Mapping,
9
9
  MutableMapping,
10
10
  Optional,
11
+ Protocol,
11
12
  Sequence,
12
13
  Union,
13
- Protocol,
14
14
  runtime_checkable,
15
15
  )
16
16
 
17
- try:
18
- from fastapi import Request # type: ignore
19
- except Exception: # pragma: no cover
20
- Request = Any # type: ignore
17
+ from ...deps.starlette import Request as StarletteRequest
18
+ from sqlalchemy.ext.asyncio import AsyncSession
19
+ from sqlalchemy.orm import Session
21
20
 
22
- try:
23
- from sqlalchemy.orm import Session # type: ignore
24
- from sqlalchemy.ext.asyncio import AsyncSession # type: ignore
25
- except Exception: # pragma: no cover
26
- Session = Any # type: ignore
27
- AsyncSession = Any # type: ignore
21
+ Request = StarletteRequest if StarletteRequest is not None else Any # type: ignore
28
22
 
29
23
 
30
24
  @runtime_checkable
@@ -27,7 +27,7 @@ from __future__ import annotations
27
27
  from typing import Any, Callable, Optional, Sequence
28
28
 
29
29
  # JSON-RPC transport
30
- from .jsonrpc import build_jsonrpc_router
30
+ from .jsonrpc import build_jsonrpc_router, build_openrpc_spec
31
31
 
32
32
  # REST transport (aggregator over per-model routers)
33
33
  from .rest import build_rest_router, mount_rest
@@ -67,6 +67,7 @@ def mount_jsonrpc(
67
67
  __all__ = [
68
68
  # JSON-RPC
69
69
  "build_jsonrpc_router",
70
+ "build_openrpc_spec",
70
71
  "mount_jsonrpc",
71
72
  # REST
72
73
  "build_rest_router",
@@ -6,14 +6,18 @@ Public helper:
6
6
  - build_jsonrpc_router(
7
7
  api, *, get_db=None, tags=("rpc",)
8
8
  ) -> Router
9
+ - build_openrpc_spec(api) -> dict
9
10
 
10
11
  Usage:
11
12
  from tigrbl.transport.jsonrpc import build_jsonrpc_router
12
13
  app.include_router(build_jsonrpc_router(api), prefix="/rpc")
14
+ # OpenRPC schema (JSON-RPC equivalent of OpenAPI)
15
+ build_openrpc_spec(api)
13
16
  """
14
17
 
15
18
  from __future__ import annotations
16
19
 
17
20
  from .dispatcher import build_jsonrpc_router
21
+ from .openrpc import build_openrpc_spec
18
22
 
19
- __all__ = ["build_jsonrpc_router"]
23
+ __all__ = ["build_jsonrpc_router", "build_openrpc_spec"]
@@ -78,6 +78,7 @@ except Exception: # pragma: no cover
78
78
  from ...runtime.errors import ERROR_MESSAGES, http_exc_to_rpc
79
79
  from ...config.constants import TIGRBL_AUTH_CONTEXT_ATTR
80
80
  from .models import RPCRequest, RPCResponse
81
+ from .openrpc import build_openrpc_spec
81
82
  from .helpers import (
82
83
  _authorize,
83
84
  _err,
@@ -337,6 +338,19 @@ def build_jsonrpc_router(
337
338
  # extra router deps already applied via Router(dependencies=...)
338
339
  )
339
340
 
341
+ def _openrpc_endpoint():
342
+ return build_openrpc_spec(api)
343
+
344
+ router.add_api_route(
345
+ path="/openrpc.json",
346
+ endpoint=_openrpc_endpoint,
347
+ methods=["GET"],
348
+ name="openrpc_json",
349
+ tags=list(tags) if tags else None,
350
+ summary="OpenRPC",
351
+ description="OpenRPC 1.2.6 schema for JSON-RPC methods.",
352
+ )
353
+
340
354
  # Compatibility: serve same endpoint without trailing slash
341
355
  router.add_api_route(
342
356
  path="/",
@@ -0,0 +1,116 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, Iterable, List, Mapping, Sequence
4
+
5
+ from pydantic import BaseModel
6
+
7
+ from ...op import OpSpec
8
+
9
+ JsonObject = Dict[str, Any]
10
+
11
+
12
+ def _iter_models(api: Any) -> List[type]:
13
+ seen: set[type] = set()
14
+ models: List[type] = []
15
+
16
+ def _add_from(container: Any) -> None:
17
+ if isinstance(container, Mapping):
18
+ items: Iterable[Any] = container.values()
19
+ elif isinstance(container, Sequence):
20
+ items = container
21
+ else:
22
+ return
23
+ for model in items:
24
+ if isinstance(model, type) and model not in seen:
25
+ seen.add(model)
26
+ models.append(model)
27
+
28
+ _add_from(getattr(api, "models", None) or {})
29
+ for child in getattr(api, "apis", ()) or ():
30
+ _add_from(getattr(child, "models", None) or {})
31
+
32
+ return models
33
+
34
+
35
+ def _iter_ops(model: type) -> Sequence[OpSpec]:
36
+ ops = getattr(model, "ops", None)
37
+ if ops is not None:
38
+ all_ops = getattr(ops, "all", None)
39
+ if all_ops is not None:
40
+ return all_ops
41
+ opspecs = getattr(model, "opspecs", None)
42
+ if opspecs is not None:
43
+ all_ops = getattr(opspecs, "all", None)
44
+ if all_ops is not None:
45
+ return all_ops
46
+ return ()
47
+
48
+
49
+ def _schema_with_defs(schema: type[BaseModel]) -> tuple[JsonObject, JsonObject]:
50
+ raw = schema.model_json_schema(ref_template="#/components/schemas/{model}")
51
+ defs = raw.pop("$defs", {})
52
+ return raw, defs
53
+
54
+
55
+ def _describe_method(model: type, spec: OpSpec) -> str | None:
56
+ rpc_ns = getattr(model, "rpc", None)
57
+ rpc_call = getattr(rpc_ns, spec.alias, None)
58
+ if rpc_call is None:
59
+ return None
60
+ doc = getattr(rpc_call, "__doc__", None)
61
+ if doc:
62
+ return doc.strip()
63
+ return None
64
+
65
+
66
+ def build_openrpc_spec(api: Any) -> JsonObject:
67
+ info_title = getattr(api, "title", None) or getattr(api, "name", None) or "API"
68
+ info_version = getattr(api, "version", None) or "0.1.0"
69
+ spec: JsonObject = {
70
+ "openrpc": "1.2.6",
71
+ "info": {"title": f"{info_title} JSON-RPC API", "version": info_version},
72
+ "methods": [],
73
+ "components": {"schemas": {}},
74
+ }
75
+
76
+ components = spec["components"]["schemas"]
77
+ methods: List[JsonObject] = []
78
+
79
+ for model in _iter_models(api):
80
+ for op in _iter_ops(model):
81
+ if not getattr(op, "expose_rpc", True):
82
+ continue
83
+
84
+ method: JsonObject = {"name": f"{model.__name__}.{op.alias}"}
85
+ description = _describe_method(model, op)
86
+ if description:
87
+ method["description"] = description
88
+
89
+ alias_ns = getattr(getattr(model, "schemas", None), op.alias, None)
90
+ in_schema = getattr(alias_ns, "in_", None)
91
+ out_schema = getattr(alias_ns, "out", None)
92
+
93
+ if in_schema is not None:
94
+ in_json, defs = _schema_with_defs(in_schema)
95
+ for key, value in defs.items():
96
+ components.setdefault(key, value)
97
+ method["params"] = [
98
+ {"name": "params", "schema": in_json, "required": True}
99
+ ]
100
+ method["paramStructure"] = "by-name"
101
+ else:
102
+ method["params"] = []
103
+
104
+ if out_schema is not None:
105
+ out_json, defs = _schema_with_defs(out_schema)
106
+ for key, value in defs.items():
107
+ components.setdefault(key, value)
108
+ method["result"] = {"name": "result", "schema": out_json}
109
+
110
+ methods.append(method)
111
+
112
+ spec["methods"] = sorted(methods, key=lambda item: item["name"])
113
+ return spec
114
+
115
+
116
+ __all__ = ["build_openrpc_spec"]
tigrbl/types/__init__.py CHANGED
@@ -57,13 +57,12 @@ from ..deps.fastapi import (
57
57
  Router,
58
58
  Security,
59
59
  Depends,
60
- Request,
61
- Response,
62
60
  Path,
63
61
  Body,
64
62
  HTTPException,
65
63
  App,
66
64
  )
65
+ from ..deps.starlette import Request, Response
67
66
 
68
67
  # ── Local Package ─────────────────────────────────────────────────────────
69
68
  from .op import _Op, _SchemaVerb
tigrbl/types/authn_abc.py CHANGED
@@ -1,7 +1,8 @@
1
1
  # tigrbl/v3/types/authn_abc.py
2
2
  from __future__ import annotations
3
3
  from abc import ABC, abstractmethod
4
- from fastapi import Request
4
+
5
+ from ..deps.starlette import Request
5
6
 
6
7
 
7
8
  class AuthNProvider(ABC):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tigrbl
3
- Version: 0.3.3.dev2
3
+ Version: 0.3.4.dev2
4
4
  Summary: Automatic API generation tools by Swarmauri.
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -77,8 +77,8 @@ tigrbl/core/crud/helpers/normalize.py,sha256=aDavCusuBDRltFX4baBlztDZ6dvgAG4Qs-D
77
77
  tigrbl/core/crud/ops.py,sha256=Qu0A4KNX0LuQdUXC5NcqzaN1aP49_ank4czEHArXcsQ,7900
78
78
  tigrbl/ddl/__init__.py,sha256=C9RQLef1l5XCE96CxCs0pN24fNATvOglK1zRYE7BdKY,11352
79
79
  tigrbl/decorators.py,sha256=U4iss3iN--4PPGJ90doAcYn4WuDx1dQMw-5Ar4SE7TU,445
80
- tigrbl/deps/__init__.py,sha256=yzhFPLJX8eXMIV41ar6IXh6ZXdjSlZ-ZYA_FKl-ZxFY,795
81
- tigrbl/deps/fastapi.py,sha256=qyRcu1c5Xy3RUgNEUxizxQbaZ8i4EdnBmQwJPDBdbCo,1181
80
+ tigrbl/deps/__init__.py,sha256=2SJWOvz7cEMFSXFkeiLS3cOqjzm00nd_uxWKnQCEEYw,758
81
+ tigrbl/deps/fastapi.py,sha256=SnrTwE7taghrkXEjQ8ZCGS8OYsv3E8s9A6u5s9nh5b8,1123
82
82
  tigrbl/deps/favicon.svg,sha256=C_S8UPLuLx4QIdP84OpzKw7IiwWjrHIzyKotoUKRLB4,195
83
83
  tigrbl/deps/jinja.py,sha256=dau8Y2v9UY3sptLhzP_8MpjKwlU-34_QraNgQXr4OCM,698
84
84
  tigrbl/deps/pydantic.py,sha256=cLReo5Gd3V7TDJ9L3sMOFD93FWMd5QnZZoCaOJ6ip14,497
@@ -193,7 +193,7 @@ tigrbl/runtime/executor/__init__.py,sha256=JuFfQo0xo9_C8XW014vdEtMvzNKaugXhLg8tW
193
193
  tigrbl/runtime/executor/guards.py,sha256=HiE9J_Y-AslRJZYBrjZmGFecq9Tx4nn6HMqL1jrezLY,4210
194
194
  tigrbl/runtime/executor/helpers.py,sha256=PU7Bj7VTdGaldNYuBRsrV4408G4UqxJIWwPUwtUgNaA,2239
195
195
  tigrbl/runtime/executor/invoke.py,sha256=IUSkjXXqRDX1cOmDcpXloKHgwI_HRYW2SsqOrKheB8U,4754
196
- tigrbl/runtime/executor/types.py,sha256=oxgyv5EI2hs2MixjXQAdhNu5nWpQ2F0kXC6pXNU_pSE,2386
196
+ tigrbl/runtime/executor/types.py,sha256=4zQnvW7h3-8f67c2DF7rXGgRanjuGuHqHNo3XMyuS5g,2247
197
197
  tigrbl/runtime/kernel.py,sha256=cL1bnw2glp33AjiFchMg8MZsJ3lb7c2JPhz26JejP1I,22895
198
198
  tigrbl/runtime/labels.py,sha256=aBXx65lMPlR60wXH4M_xeuXN4w3z07ep6NxICAiSy68,13134
199
199
  tigrbl/runtime/opview.py,sha256=UHbQbGg5GGi2AWFip8qBUD6fe6h8VNU232GRly1aT-I,2792
@@ -243,16 +243,17 @@ tigrbl/table/_table.py,sha256=B7ft2SMnbp3aTWKO44M4EWTHmzFKyQlpdj-3QULRaGk,1740
243
243
  tigrbl/table/mro_collect.py,sha256=PbuSZnUvVbs3NCe2otie7pvcjxeF54hSiVp_VywclSA,2733
244
244
  tigrbl/table/shortcuts.py,sha256=-IZAZyMTsiCdKV0w7nq1C2YBsB6iE_uNGJb-PatlO8I,1716
245
245
  tigrbl/table/table_spec.py,sha256=dvilrGWX7fVc6ThTbAqJKxxl3r6_MKNFY0cs_wuyvC8,1001
246
- tigrbl/transport/__init__.py,sha256=Hq2yob_mvMOQdd8Ts04-rzL282rRpIXo2Prortk0fL4,1896
247
- tigrbl/transport/jsonrpc/__init__.py,sha256=YZKk9W1GSPkx98G0cshEcDKLWvmeJGjW90PeTJ0z_r4,438
248
- tigrbl/transport/jsonrpc/dispatcher.py,sha256=Eb-M_0n3npAWkw5NSIDDFklgj_J3p8-46yaXPtaiktI,12620
246
+ tigrbl/transport/__init__.py,sha256=QanSh_0kxBFBg32KNSrrzsQjfV8Lp1BljsLiSnE8cbc,1942
247
+ tigrbl/transport/jsonrpc/__init__.py,sha256=yIuAjf3zQsqezvS0TdzDlfIhtuzMi9_ykSIqjn-w8EM,618
248
+ tigrbl/transport/jsonrpc/dispatcher.py,sha256=KKvN2_Ct1AYiQdww2VO_sgaSkNmL7hLcDEFIpq_ou1s,13018
249
249
  tigrbl/transport/jsonrpc/helpers.py,sha256=oyqx36m8n7EofciPVvTEM9Pz1l51zJwsI224AXkE7q8,3010
250
250
  tigrbl/transport/jsonrpc/models.py,sha256=omtjb-NN8HyWgIZ5tHafEsbwC7f1XlttAFHFA41Xn2k,973
251
+ tigrbl/transport/jsonrpc/openrpc.py,sha256=FmczSrHkiS17Hdk6nVv_S8bi4oFFG8dPCdGSkJsNKqg,3706
251
252
  tigrbl/transport/rest/__init__.py,sha256=AU_twrP0A958FtXvLSf1i60Jn-UZSRUkAZ1Gd2TeYaw,764
252
253
  tigrbl/transport/rest/aggregator.py,sha256=V1zDvv1bwpNyt6rUPmEUEV5nORjb5sHU5LJ00m1ybYY,4454
253
- tigrbl/types/__init__.py,sha256=TSmKjMPDqn_AdZEfYKuGPRkBFsT2P7fqC-BSUXRwZlA,4120
254
+ tigrbl/types/__init__.py,sha256=4Io80FQh3L_gkfsv-TwPnK2wNtEAOYQJHGg-UEEfOAQ,4140
254
255
  tigrbl/types/allow_anon_provider.py,sha256=5mWvSfk_eCY_o6oMm1gSEqz6cKyJyoZ1-DcVYm0KmpA,565
255
- tigrbl/types/authn_abc.py,sha256=GtlXkMb59GEEXNEfeRX_ZfNzu-S4hcLsESzBAaPT2Fg,769
256
+ tigrbl/types/authn_abc.py,sha256=OrKSQQmeaCVh8vqnFM5oL--wdvhdKGocGpQKJjJoG8w,779
256
257
  tigrbl/types/nested_path_provider.py,sha256=1z-4Skz_X_hy-XGEAQnNv6vyrfFNsPIvlhBqf457Sjc,609
257
258
  tigrbl/types/op.py,sha256=-kdDABEyIBEAMMtatOXVPeMEaGFUiXtJSStZkNu3cnA,769
258
259
  tigrbl/types/op_config_provider.py,sha256=tYH_py9niHvLQF9c9OD13Wt8U4hoYxUatJO_Zp5Cs8Q,551
@@ -261,7 +262,7 @@ tigrbl/types/request_extras_provider.py,sha256=JOIpzx1PYA2AYYvkMiXrxlwpBLOPD2cQa
261
262
  tigrbl/types/response_extras_provider.py,sha256=sFB0R3vyUqmpT-o8983hH9FAlOq6-wwNVK6vfuCPHCg,653
262
263
  tigrbl/types/table_config_provider.py,sha256=EkfOhy9UDfy7EgiZddw9KIl5tRujRjXJlr4cSk1Rm5k,361
263
264
  tigrbl/types/uuid.py,sha256=pD-JrhS0L2GXeJ0Hv_oKzRuiXmxHDTVoMqExO48iqZE,1993
264
- tigrbl-0.3.3.dev2.dist-info/METADATA,sha256=HQ0hReGPk-P9BXAaggKyO3TqZgkOebhiqx2sBCvVSkc,17853
265
- tigrbl-0.3.3.dev2.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
266
- tigrbl-0.3.3.dev2.dist-info/licenses/LICENSE,sha256=djUXOlCxLVszShEpZXshZ7v33G-2qIC_j9KXpWKZSzQ,11359
267
- tigrbl-0.3.3.dev2.dist-info/RECORD,,
265
+ tigrbl-0.3.4.dev2.dist-info/METADATA,sha256=7i4VQfEZPkC92J1q3LXsvLrff5ICX-xVYaXARKDGrT8,17853
266
+ tigrbl-0.3.4.dev2.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
267
+ tigrbl-0.3.4.dev2.dist-info/licenses/LICENSE,sha256=djUXOlCxLVszShEpZXshZ7v33G-2qIC_j9KXpWKZSzQ,11359
268
+ tigrbl-0.3.4.dev2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.3.0
2
+ Generator: poetry-core 2.3.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any