tigrbl 0.4.2.dev3__py3-none-any.whl → 0.4.3.dev4__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/__init__.py +74 -7
- tigrbl/cli.py +11 -2
- tigrbl/hook/exceptions.py +2 -2
- tigrbl/middlewares/__init__.py +8 -6
- tigrbl/schema/jsonrpc.py +1 -0
- tigrbl/system/diagnostics/__init__.py +19 -12
- tigrbl/system/diagnostics/kernelz.py +5 -47
- tigrbl/system/diagnostics/methodz.py +2 -0
- tigrbl/system/diagnostics/utils.py +15 -61
- tigrbl/transport/jsonrpc/helpers.py +6 -0
- tigrbl/transport/jsonrpc/models.py +6 -0
- tigrbl/transport/rest/aggregator.py +6 -0
- tigrbl-0.4.3.dev4.dist-info/METADATA +707 -0
- {tigrbl-0.4.2.dev3.dist-info → tigrbl-0.4.3.dev4.dist-info}/RECORD +18 -16
- tigrbl-0.4.3.dev4.dist-info/licenses/NOTICE +7 -0
- tigrbl-0.4.2.dev3.dist-info/METADATA +0 -348
- {tigrbl-0.4.2.dev3.dist-info → tigrbl-0.4.3.dev4.dist-info}/WHEEL +0 -0
- {tigrbl-0.4.2.dev3.dist-info → tigrbl-0.4.3.dev4.dist-info}/entry_points.txt +0 -0
- {tigrbl-0.4.2.dev3.dist-info → tigrbl-0.4.3.dev4.dist-info}/licenses/LICENSE +0 -0
tigrbl/__init__.py
CHANGED
|
@@ -61,6 +61,18 @@ def _install_alias(alias: str, target: str) -> None:
|
|
|
61
61
|
for alias, target in _ALIAS_MODULES.items():
|
|
62
62
|
_install_alias(alias, target)
|
|
63
63
|
|
|
64
|
+
for alias, target in {
|
|
65
|
+
"runtime.events": "tigrbl_kernel.events",
|
|
66
|
+
"runtime.labels": "tigrbl_kernel.labels",
|
|
67
|
+
"runtime.status": "tigrbl_typing.status",
|
|
68
|
+
}.items():
|
|
69
|
+
_install_alias(alias, target)
|
|
70
|
+
parent_alias, attr_name = alias.rsplit(".", 1)
|
|
71
|
+
parent_module = sys.modules.get(f"{__name__}.{parent_alias}")
|
|
72
|
+
target_module = sys.modules.get(target) or _optional_import(target)
|
|
73
|
+
if parent_module is not None and target_module is not None:
|
|
74
|
+
setattr(parent_module, attr_name, target_module)
|
|
75
|
+
|
|
64
76
|
|
|
65
77
|
_spec = import_module("tigrbl_core._spec")
|
|
66
78
|
_base = import_module("tigrbl_base._base")
|
|
@@ -84,8 +96,11 @@ from tigrbl_concrete._concrete import ( # noqa: E402
|
|
|
84
96
|
BackgroundTask,
|
|
85
97
|
Binding,
|
|
86
98
|
BindingRegistry,
|
|
99
|
+
BulkCrudTable,
|
|
87
100
|
Column,
|
|
88
101
|
CORSMiddleware,
|
|
102
|
+
CrudTable,
|
|
103
|
+
EventStreamTable,
|
|
89
104
|
EventStreamResponse,
|
|
90
105
|
FileResponse,
|
|
91
106
|
ForeignKey,
|
|
@@ -93,23 +108,44 @@ from tigrbl_concrete._concrete import ( # noqa: E402
|
|
|
93
108
|
HTMLResponse,
|
|
94
109
|
HTTPBasic,
|
|
95
110
|
HTTPBearer,
|
|
111
|
+
JsonRpcOlapTable,
|
|
112
|
+
JsonRpcOltpTable,
|
|
113
|
+
JsonRpcTable,
|
|
96
114
|
JSONResponse,
|
|
97
115
|
MutualTLS,
|
|
98
116
|
OAuth2,
|
|
117
|
+
OlapTable,
|
|
118
|
+
OltpTable,
|
|
99
119
|
OpenIdConnect,
|
|
100
120
|
Op,
|
|
101
121
|
PlainTextResponse,
|
|
122
|
+
RealtimeTable,
|
|
123
|
+
RestJsonRpcOlapTable,
|
|
124
|
+
RestJsonRpcOltpTable,
|
|
125
|
+
RestJsonRpcTable,
|
|
126
|
+
RestOlapTable,
|
|
127
|
+
RestOltpTable,
|
|
102
128
|
RedirectResponse,
|
|
103
129
|
Request,
|
|
104
130
|
TransportResponse,
|
|
105
131
|
UploadedFile,
|
|
106
132
|
WebSocket,
|
|
133
|
+
WebSocketJsonRpcTable,
|
|
134
|
+
WebSocketTable,
|
|
135
|
+
WebTransportBidiTable,
|
|
136
|
+
WebTransportClientStreamTable,
|
|
137
|
+
WebTransportDatagramTable,
|
|
138
|
+
WebTransportServerStreamTable,
|
|
139
|
+
WebTransportTable,
|
|
107
140
|
Response,
|
|
108
141
|
Route,
|
|
109
142
|
Router,
|
|
110
143
|
Schema,
|
|
111
144
|
StorageTransform,
|
|
112
145
|
Middleware,
|
|
146
|
+
RestTable,
|
|
147
|
+
SseTable,
|
|
148
|
+
StreamTable,
|
|
113
149
|
StreamingResponse,
|
|
114
150
|
Table,
|
|
115
151
|
TableRegistry,
|
|
@@ -117,7 +153,7 @@ from tigrbl_concrete._concrete import ( # noqa: E402
|
|
|
117
153
|
TigrblRouter,
|
|
118
154
|
)
|
|
119
155
|
from tigrbl_concrete._concrete.dependencies import Depends # noqa: E402
|
|
120
|
-
from
|
|
156
|
+
from tigrbl_typing.status.exceptions import HTTPException # noqa: E402
|
|
121
157
|
from tigrbl.engine import resolver # noqa: E402
|
|
122
158
|
from tigrbl.system import mount_diagnostics # noqa: E402
|
|
123
159
|
from tigrbl_core.config.constants import DEFAULT_HTTP_METHODS, HOOK_DECLS_ATTR # noqa: E402
|
|
@@ -143,16 +179,22 @@ from tigrbl.decorators import ( # noqa: E402
|
|
|
143
179
|
websocket_ctx,
|
|
144
180
|
webtransport_ctx,
|
|
145
181
|
)
|
|
182
|
+
from tigrbl_concrete._decorators.middlewares import ( # noqa: E402
|
|
183
|
+
middleware as middleware,
|
|
184
|
+
middlewares as middlewares,
|
|
185
|
+
)
|
|
146
186
|
from tigrbl.factories.op import op # noqa: E402
|
|
147
187
|
from tigrbl.schema import _build_list_params, _build_schema, get_schema # noqa: E402
|
|
148
188
|
from tigrbl.ddl import bootstrap_dbschema, ensure_schemas, register_sqlite_attach # noqa: E402
|
|
149
189
|
|
|
150
190
|
from tigrbl_base._base import ( # noqa: E402
|
|
151
191
|
AppBase,
|
|
192
|
+
CrudTableBase,
|
|
152
193
|
EngineBase,
|
|
153
194
|
EngineProviderBase,
|
|
154
195
|
ForeignKeyBase,
|
|
155
196
|
HookBase,
|
|
197
|
+
RealtimeTableBase,
|
|
156
198
|
RouterBase,
|
|
157
199
|
TableBase,
|
|
158
200
|
TableRegistryBase,
|
|
@@ -196,21 +238,18 @@ from tigrbl_core._spec import ( # noqa: E402
|
|
|
196
238
|
SchemaRef,
|
|
197
239
|
SchemaSpec,
|
|
198
240
|
SessionSpec,
|
|
199
|
-
readonly,
|
|
200
|
-
session_spec,
|
|
201
241
|
StorageSpec,
|
|
202
242
|
StorageTransformSpec,
|
|
203
243
|
StorageTypeRef,
|
|
244
|
+
TableProfileSpec,
|
|
204
245
|
TableRegistrySpec,
|
|
205
246
|
TableSpec,
|
|
206
247
|
TargetOp,
|
|
207
248
|
TemplateSpec,
|
|
208
|
-
tx_read_committed,
|
|
209
|
-
tx_repeatable_read,
|
|
210
|
-
tx_serializable,
|
|
211
249
|
TxScope,
|
|
212
250
|
WebSocketBindingSpec,
|
|
213
251
|
WebTransportBindingSpec,
|
|
252
|
+
WellKnownResourceSpec,
|
|
214
253
|
WsBindingSpec,
|
|
215
254
|
canonical_binding_kind,
|
|
216
255
|
normalize_binding_spec,
|
|
@@ -218,7 +257,7 @@ from tigrbl_core._spec import ( # noqa: E402
|
|
|
218
257
|
validate_app_framing_for_binding,
|
|
219
258
|
validate_binding_profile_exchange,
|
|
220
259
|
)
|
|
221
|
-
from tigrbl_runtime.
|
|
260
|
+
from tigrbl_runtime.executors.invoke import _invoke # noqa: E402
|
|
222
261
|
|
|
223
262
|
|
|
224
263
|
def bind(*args, **kwargs):
|
|
@@ -332,6 +371,8 @@ __all__ = [
|
|
|
332
371
|
"Depends",
|
|
333
372
|
"HTTPException",
|
|
334
373
|
"TableBase",
|
|
374
|
+
"CrudTableBase",
|
|
375
|
+
"RealtimeTableBase",
|
|
335
376
|
"RouterBase",
|
|
336
377
|
"Op",
|
|
337
378
|
"op",
|
|
@@ -357,6 +398,7 @@ __all__ = [
|
|
|
357
398
|
"PathKind",
|
|
358
399
|
"PathSpec",
|
|
359
400
|
"path_for_binding",
|
|
401
|
+
"WellKnownResourceSpec",
|
|
360
402
|
"DocsPayloadKind",
|
|
361
403
|
"DocsPayloadSpec",
|
|
362
404
|
"DocsProjectionSelection",
|
|
@@ -428,6 +470,7 @@ __all__ = [
|
|
|
428
470
|
"BindingRegistrySpec",
|
|
429
471
|
"RouterSpec",
|
|
430
472
|
"TableSpec",
|
|
473
|
+
"TableProfileSpec",
|
|
431
474
|
"TableRegistrySpec",
|
|
432
475
|
"ColumnSpec",
|
|
433
476
|
"FieldSpec",
|
|
@@ -457,6 +500,30 @@ __all__ = [
|
|
|
457
500
|
"AppBase",
|
|
458
501
|
"App",
|
|
459
502
|
"Table",
|
|
503
|
+
"CrudTable",
|
|
504
|
+
"RealtimeTable",
|
|
505
|
+
"RestTable",
|
|
506
|
+
"RestJsonRpcTable",
|
|
507
|
+
"JsonRpcTable",
|
|
508
|
+
"BulkCrudTable",
|
|
509
|
+
"RestOltpTable",
|
|
510
|
+
"JsonRpcOltpTable",
|
|
511
|
+
"RestJsonRpcOltpTable",
|
|
512
|
+
"OltpTable",
|
|
513
|
+
"RestOlapTable",
|
|
514
|
+
"JsonRpcOlapTable",
|
|
515
|
+
"RestJsonRpcOlapTable",
|
|
516
|
+
"OlapTable",
|
|
517
|
+
"StreamTable",
|
|
518
|
+
"SseTable",
|
|
519
|
+
"EventStreamTable",
|
|
520
|
+
"WebSocketTable",
|
|
521
|
+
"WebSocketJsonRpcTable",
|
|
522
|
+
"WebTransportTable",
|
|
523
|
+
"WebTransportBidiTable",
|
|
524
|
+
"WebTransportClientStreamTable",
|
|
525
|
+
"WebTransportServerStreamTable",
|
|
526
|
+
"WebTransportDatagramTable",
|
|
460
527
|
"Column",
|
|
461
528
|
"CORSMiddleware",
|
|
462
529
|
"Route",
|
tigrbl/cli.py
CHANGED
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
6
|
import asyncio
|
|
7
|
+
import hashlib
|
|
7
8
|
import importlib
|
|
8
9
|
import importlib.util
|
|
9
10
|
import inspect
|
|
@@ -193,7 +194,9 @@ def _resolve_target(args: argparse.Namespace) -> str | None:
|
|
|
193
194
|
|
|
194
195
|
|
|
195
196
|
def _load_module_from_path(path: Path) -> Any:
|
|
196
|
-
|
|
197
|
+
resolved = str(path.resolve())
|
|
198
|
+
digest = hashlib.sha256(resolved.encode("utf-8")).hexdigest()[:16]
|
|
199
|
+
module_name = f"_tigrbl_cli_{path.stem}_{digest}"
|
|
197
200
|
spec = importlib.util.spec_from_file_location(module_name, path)
|
|
198
201
|
if spec is None or spec.loader is None:
|
|
199
202
|
raise CLIError(f"Could not load Python module from '{path}'")
|
|
@@ -208,7 +211,13 @@ def _load_target_object(target: str) -> Any:
|
|
|
208
211
|
if not raw:
|
|
209
212
|
raise CLIError("An app target is required. Use <module:attr> or <path.py:attr>.")
|
|
210
213
|
|
|
211
|
-
|
|
214
|
+
raw_path = Path(raw)
|
|
215
|
+
if raw.endswith(".py") or raw_path.exists():
|
|
216
|
+
source, attr = raw, "app"
|
|
217
|
+
elif ":" in raw:
|
|
218
|
+
source, attr = raw.rsplit(":", 1)
|
|
219
|
+
else:
|
|
220
|
+
source, attr = raw, "app"
|
|
212
221
|
source_path = Path(source)
|
|
213
222
|
if source.endswith(".py") or source_path.exists():
|
|
214
223
|
if not source_path.exists():
|
tigrbl/hook/exceptions.py
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"""Compatibility shim for
|
|
1
|
+
"""Compatibility shim for hook declaration exceptions."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from tigrbl_core._spec.exceptions import * # noqa: F403, F401
|
tigrbl/middlewares/__init__.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
"""Middleware declaration and composition helpers."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from tigrbl_concrete._decorators.middlewares import (
|
|
6
|
+
MiddlewareConfig,
|
|
7
|
+
middleware,
|
|
8
|
+
middlewares,
|
|
9
|
+
)
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
PHASES = tuple(h.value for h in HookPhases)
|
|
9
|
-
|
|
10
|
-
__all__ = ["HookPhase", "HookPhases", "PHASE", "PHASES", "StepFn", "HookPredicate"]
|
|
11
|
+
from .compose import apply_middlewares
|
|
11
12
|
|
|
13
|
+
__all__ = ["MiddlewareConfig", "apply_middlewares", "middleware", "middlewares"]
|
tigrbl/schema/jsonrpc.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from tigrbl_concrete.schema.jsonrpc import * # noqa: F401,F403
|
|
@@ -1,19 +1,27 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
from tigrbl_concrete.system.diagnostics import (
|
|
2
|
+
build_healthz_endpoint,
|
|
3
|
+
build_healthz_html,
|
|
4
|
+
build_hookz_endpoint,
|
|
5
|
+
build_methodz_endpoint,
|
|
6
|
+
mount_diagnostics,
|
|
7
|
+
mount_healthz_uix,
|
|
8
|
+
)
|
|
6
9
|
from .kernelz import build_kernelz_endpoint
|
|
7
|
-
from .
|
|
8
|
-
|
|
10
|
+
from tigrbl_concrete.system.diagnostics import (
|
|
11
|
+
build_hookz_endpoint as _build_hookz_endpoint,
|
|
12
|
+
)
|
|
9
13
|
from .kernelz import build_kernelz_endpoint as _build_kernelz_endpoint
|
|
10
|
-
from .
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
from tigrbl_concrete.system.diagnostics import (
|
|
15
|
+
build_methodz_endpoint as _build_methodz_endpoint,
|
|
16
|
+
)
|
|
17
|
+
from tigrbl_concrete.system.diagnostics.utils import (
|
|
14
18
|
label_callable as _label_callable,
|
|
15
19
|
label_hook as _label_hook,
|
|
20
|
+
model_iter as _model_iter,
|
|
21
|
+
opspecs as _opspecs,
|
|
22
|
+
table_iter as _table_iter,
|
|
16
23
|
)
|
|
24
|
+
from tigrbl_kernel import _default_kernel
|
|
17
25
|
|
|
18
26
|
__all__ = [
|
|
19
27
|
"mount_diagnostics",
|
|
@@ -31,6 +39,5 @@ __all__ = [
|
|
|
31
39
|
"_opspecs",
|
|
32
40
|
"_label_callable",
|
|
33
41
|
"_label_hook",
|
|
34
|
-
"build_phase_chains",
|
|
35
42
|
"_default_kernel",
|
|
36
43
|
]
|
|
@@ -1,57 +1,15 @@
|
|
|
1
|
-
"""Diagnostic endpoint exposing kernel phase plans."""
|
|
2
|
-
|
|
3
1
|
from __future__ import annotations
|
|
4
2
|
|
|
5
3
|
from typing import Any
|
|
6
4
|
|
|
7
|
-
from
|
|
5
|
+
from tigrbl_concrete.system.diagnostics.kernelz import (
|
|
6
|
+
build_kernelz_endpoint as _build_kernelz_endpoint,
|
|
7
|
+
)
|
|
8
|
+
from tigrbl_kernel import _default_kernel
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def build_kernelz_endpoint(router: Any):
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
async def _kernelz():
|
|
14
|
-
K.ensure_primed(router)
|
|
15
|
-
payload = K.kernelz_payload(router)
|
|
16
|
-
models = getattr(router, "models", {}) or {}
|
|
17
|
-
if not models:
|
|
18
|
-
return payload
|
|
19
|
-
|
|
20
|
-
out = payload
|
|
21
|
-
for model_name, model in models.items():
|
|
22
|
-
specs = getattr(getattr(model, "opspecs", None), "all", ()) or ()
|
|
23
|
-
if specs:
|
|
24
|
-
continue
|
|
25
|
-
|
|
26
|
-
model_map = payload.get(model_name)
|
|
27
|
-
if not isinstance(model_map, dict):
|
|
28
|
-
continue
|
|
29
|
-
|
|
30
|
-
if out is payload:
|
|
31
|
-
out = {
|
|
32
|
-
name: {alias: list(seq) for alias, seq in ops.items()}
|
|
33
|
-
for name, ops in payload.items()
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
enriched = out.get(model_name)
|
|
37
|
-
if not isinstance(enriched, dict):
|
|
38
|
-
continue
|
|
39
|
-
|
|
40
|
-
for alias, seq in list(enriched.items()):
|
|
41
|
-
extras: list[str] = []
|
|
42
|
-
seen = set(seq)
|
|
43
|
-
for label in seq:
|
|
44
|
-
if not isinstance(label, str) or ":" not in label:
|
|
45
|
-
continue
|
|
46
|
-
raw = label.split(":", 1)[1]
|
|
47
|
-
if raw not in seen:
|
|
48
|
-
extras.append(raw)
|
|
49
|
-
seen.add(raw)
|
|
50
|
-
if extras:
|
|
51
|
-
enriched[alias] = [*seq, *extras]
|
|
52
|
-
return out
|
|
53
|
-
|
|
54
|
-
return _kernelz
|
|
12
|
+
return _build_kernelz_endpoint(router, kernel=_default_kernel)
|
|
55
13
|
|
|
56
14
|
|
|
57
15
|
__all__ = ["build_kernelz_endpoint"]
|
|
@@ -37,6 +37,8 @@ def build_methodz_endpoint(router: Any):
|
|
|
37
37
|
methods.append(entry)
|
|
38
38
|
per_model.setdefault(mname, []).append(entry)
|
|
39
39
|
methods.sort(key=lambda x: (x["model"], x["alias"]))
|
|
40
|
+
for entries in per_model.values():
|
|
41
|
+
entries.sort(key=lambda x: x["alias"])
|
|
40
42
|
cache = {"methods": methods, **per_model}
|
|
41
43
|
return cache
|
|
42
44
|
|
|
@@ -1,63 +1,17 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
from sqlalchemy import text
|
|
9
|
-
|
|
10
|
-
from ...runtime.labels import (
|
|
11
|
-
label_callable as _kernel_label_callable,
|
|
12
|
-
label_hook as _kernel_label_hook,
|
|
1
|
+
from tigrbl_concrete.system.diagnostics.utils import (
|
|
2
|
+
label_callable,
|
|
3
|
+
label_hook,
|
|
4
|
+
maybe_execute,
|
|
5
|
+
model_iter,
|
|
6
|
+
opspecs,
|
|
7
|
+
table_iter,
|
|
13
8
|
)
|
|
14
9
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def model_iter(router: Any) -> Iterable[type]:
|
|
25
|
-
warnings.warn(
|
|
26
|
-
"model_iter is deprecated; use table_iter instead.",
|
|
27
|
-
DeprecationWarning,
|
|
28
|
-
stacklevel=2,
|
|
29
|
-
)
|
|
30
|
-
return table_iter(router)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def opspecs(model: type):
|
|
34
|
-
ops = getattr(getattr(model, "opspecs", SimpleNamespace()), "all", ()) or ()
|
|
35
|
-
if ops:
|
|
36
|
-
return ops
|
|
37
|
-
try:
|
|
38
|
-
from tigrbl_core._spec.op_spec import _mro_collect_decorated_ops
|
|
39
|
-
|
|
40
|
-
return tuple(_mro_collect_decorated_ops(model))
|
|
41
|
-
except Exception:
|
|
42
|
-
return ()
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
async def maybe_execute(db: Any, stmt: str):
|
|
46
|
-
try:
|
|
47
|
-
rv = db.execute(text(stmt)) # type: ignore[attr-defined]
|
|
48
|
-
if inspect.isawaitable(rv):
|
|
49
|
-
return await rv
|
|
50
|
-
return rv
|
|
51
|
-
except Exception:
|
|
52
|
-
rv = db.execute(text("select 1")) # type: ignore[attr-defined]
|
|
53
|
-
if inspect.isawaitable(rv):
|
|
54
|
-
return await rv
|
|
55
|
-
return rv
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def label_callable(fn: Any) -> str:
|
|
59
|
-
return _kernel_label_callable(fn)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def label_hook(fn: Any, phase: str) -> str:
|
|
63
|
-
return _kernel_label_hook(fn, phase)
|
|
10
|
+
__all__ = [
|
|
11
|
+
"label_callable",
|
|
12
|
+
"label_hook",
|
|
13
|
+
"maybe_execute",
|
|
14
|
+
"model_iter",
|
|
15
|
+
"opspecs",
|
|
16
|
+
"table_iter",
|
|
17
|
+
]
|