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,348 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
# tigrbl/v3/bindings/schemas/builder.py
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from types import SimpleNamespace
|
|
7
|
+
from typing import Dict, Optional, Sequence, Type
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
from ...op import OpSpec
|
|
12
|
+
from ...schema import collect_decorated_schemas
|
|
13
|
+
from .defaults import _default_schemas_for_spec
|
|
14
|
+
from .utils import _alias_schema, _ensure_alias_namespace, _resolve_schema_arg, _Key
|
|
15
|
+
|
|
16
|
+
logging.getLogger("uvicorn").setLevel(logging.DEBUG)
|
|
17
|
+
logger = logging.getLogger("uvicorn")
|
|
18
|
+
logger.debug("Loaded module v3/bindings/schemas/builder")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_and_attach(
|
|
22
|
+
model: type, specs: Sequence[OpSpec], *, only_keys: Optional[Sequence[_Key]] = None
|
|
23
|
+
) -> None:
|
|
24
|
+
"""
|
|
25
|
+
Build request and response schemas per OpSpec and attach them under:
|
|
26
|
+
model.schemas.<alias>.in_ -> request model (or None)
|
|
27
|
+
model.schemas.<alias>.out -> response model (or None)
|
|
28
|
+
|
|
29
|
+
Two-pass strategy:
|
|
30
|
+
0) Seed namespaces from @schema_ctx declarations (so SchemaRef targets exist)
|
|
31
|
+
1) Attach DEFAULT schemas for canonical ops (custom stays raw)
|
|
32
|
+
2) Apply per-spec overrides (SchemaRef / 'alias.in'|'alias.out' / 'raw' / None)
|
|
33
|
+
|
|
34
|
+
If `only_keys` is provided, overrides are limited to those (alias,target) pairs.
|
|
35
|
+
Defaults are still ensured for all specs so cross-op SchemaRefs resolve reliably
|
|
36
|
+
for canonical ops.
|
|
37
|
+
"""
|
|
38
|
+
logger.debug(
|
|
39
|
+
"Building schemas for %s with %d specs (only_keys=%s)",
|
|
40
|
+
model.__name__,
|
|
41
|
+
len(specs),
|
|
42
|
+
only_keys,
|
|
43
|
+
)
|
|
44
|
+
if not hasattr(model, "schemas"):
|
|
45
|
+
model.schemas = SimpleNamespace()
|
|
46
|
+
logger.debug("Created new schemas namespace on %s", model.__name__)
|
|
47
|
+
else:
|
|
48
|
+
logger.debug("Using existing schemas namespace on %s", model.__name__)
|
|
49
|
+
|
|
50
|
+
wanted = set(only_keys or ())
|
|
51
|
+
if wanted:
|
|
52
|
+
logger.debug("Filtering overrides to keys: %s", wanted)
|
|
53
|
+
|
|
54
|
+
# Pass 0: attach schemas declared via @schema_ctx
|
|
55
|
+
declared = collect_decorated_schemas(model) # {alias: {"in": cls, "out": cls}}
|
|
56
|
+
for alias, kinds in (declared or {}).items():
|
|
57
|
+
logger.debug("Applying declared schemas for alias '%s'", alias)
|
|
58
|
+
ns = _ensure_alias_namespace(model, alias)
|
|
59
|
+
if "in" in kinds:
|
|
60
|
+
setattr(ns, "in_", kinds["in"])
|
|
61
|
+
logger.debug("Declared request schema for %s.%s", model.__name__, alias)
|
|
62
|
+
if "out" in kinds:
|
|
63
|
+
setattr(ns, "out", kinds["out"])
|
|
64
|
+
logger.debug("Declared response schema for %s.%s", model.__name__, alias)
|
|
65
|
+
|
|
66
|
+
# Ensure a namespace per op alias (even if empty)
|
|
67
|
+
for sp in specs:
|
|
68
|
+
_ = _ensure_alias_namespace(model, sp.alias)
|
|
69
|
+
logger.debug("Ensured namespace for alias '%s'", sp.alias)
|
|
70
|
+
|
|
71
|
+
# Pass 1: attach defaults for all specs and capture them so canonical
|
|
72
|
+
# defaults can be restored later if needed.
|
|
73
|
+
# Existing schemas that lack fields are treated as missing so they are
|
|
74
|
+
# replaced with freshly built defaults. This protects against earlier
|
|
75
|
+
# auto-binding passes that may have produced placeholder models.
|
|
76
|
+
defaults: Dict[_Key, Dict[str, Optional[Type[BaseModel]]]] = {}
|
|
77
|
+
for sp in specs:
|
|
78
|
+
ns = _ensure_alias_namespace(model, sp.alias)
|
|
79
|
+
shapes = _default_schemas_for_spec(model, sp)
|
|
80
|
+
defaults[(sp.alias, sp.target)] = shapes
|
|
81
|
+
|
|
82
|
+
if shapes.get("in_") is not None:
|
|
83
|
+
existing_in = getattr(ns, "in_", None)
|
|
84
|
+
if existing_in is None or not getattr(existing_in, "model_fields", None):
|
|
85
|
+
setattr(ns, "in_", shapes["in_"])
|
|
86
|
+
logger.debug(
|
|
87
|
+
"Attached default request schema for %s.%s",
|
|
88
|
+
model.__name__,
|
|
89
|
+
sp.alias,
|
|
90
|
+
)
|
|
91
|
+
else:
|
|
92
|
+
logger.debug(
|
|
93
|
+
"Keeping existing request schema for %s.%s",
|
|
94
|
+
model.__name__,
|
|
95
|
+
sp.alias,
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
logger.debug(
|
|
99
|
+
"No default request schema for %s.%s", model.__name__, sp.alias
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if shapes.get("in_item") is not None:
|
|
103
|
+
existing_in_item = getattr(ns, "in_item", None)
|
|
104
|
+
if existing_in_item is None or not getattr(
|
|
105
|
+
existing_in_item, "model_fields", None
|
|
106
|
+
):
|
|
107
|
+
setattr(ns, "in_item", shapes["in_item"])
|
|
108
|
+
logger.debug(
|
|
109
|
+
"Attached default request item schema for %s.%s",
|
|
110
|
+
model.__name__,
|
|
111
|
+
sp.alias,
|
|
112
|
+
)
|
|
113
|
+
else:
|
|
114
|
+
logger.debug(
|
|
115
|
+
"Keeping existing request item schema for %s.%s",
|
|
116
|
+
model.__name__,
|
|
117
|
+
sp.alias,
|
|
118
|
+
)
|
|
119
|
+
else:
|
|
120
|
+
logger.debug(
|
|
121
|
+
"No default request item schema for %s.%s", model.__name__, sp.alias
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if shapes.get("out") is not None:
|
|
125
|
+
existing_out = getattr(ns, "out", None)
|
|
126
|
+
if existing_out is None or not getattr(existing_out, "model_fields", None):
|
|
127
|
+
setattr(ns, "out", shapes["out"])
|
|
128
|
+
logger.debug(
|
|
129
|
+
"Attached default response schema for %s.%s",
|
|
130
|
+
model.__name__,
|
|
131
|
+
sp.alias,
|
|
132
|
+
)
|
|
133
|
+
else:
|
|
134
|
+
logger.debug(
|
|
135
|
+
"Keeping existing response schema for %s.%s",
|
|
136
|
+
model.__name__,
|
|
137
|
+
sp.alias,
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
logger.debug(
|
|
141
|
+
"No default response schema for %s.%s", model.__name__, sp.alias
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if shapes.get("out_item") is not None:
|
|
145
|
+
existing_out_item = getattr(ns, "out_item", None)
|
|
146
|
+
if existing_out_item is None or not getattr(
|
|
147
|
+
existing_out_item, "model_fields", None
|
|
148
|
+
):
|
|
149
|
+
setattr(ns, "out_item", shapes["out_item"])
|
|
150
|
+
logger.debug(
|
|
151
|
+
"Attached default response item schema for %s.%s",
|
|
152
|
+
model.__name__,
|
|
153
|
+
sp.alias,
|
|
154
|
+
)
|
|
155
|
+
else:
|
|
156
|
+
logger.debug(
|
|
157
|
+
"Keeping existing response item schema for %s.%s",
|
|
158
|
+
model.__name__,
|
|
159
|
+
sp.alias,
|
|
160
|
+
)
|
|
161
|
+
else:
|
|
162
|
+
logger.debug(
|
|
163
|
+
"No default response item schema for %s.%s", model.__name__, sp.alias
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
logger.debug(
|
|
167
|
+
"schemas(default): %s.%s -> in=%s out=%s",
|
|
168
|
+
model.__name__,
|
|
169
|
+
sp.alias,
|
|
170
|
+
getattr(ns, "in_", None).__name__ if getattr(ns, "in_", None) else None,
|
|
171
|
+
getattr(ns, "out", None).__name__ if getattr(ns, "out", None) else None,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Pass 2: apply per-spec overrides (respect only_keys if provided)
|
|
175
|
+
for sp in specs:
|
|
176
|
+
key = (sp.alias, sp.target)
|
|
177
|
+
if wanted and key not in wanted:
|
|
178
|
+
logger.debug(
|
|
179
|
+
"Skipping overrides for %s.%s not in only_keys",
|
|
180
|
+
model.__name__,
|
|
181
|
+
sp.alias,
|
|
182
|
+
)
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
ns = _ensure_alias_namespace(model, sp.alias)
|
|
186
|
+
|
|
187
|
+
if sp.request_model is not None:
|
|
188
|
+
logger.debug(
|
|
189
|
+
"Resolving request override for %s.%s", model.__name__, sp.alias
|
|
190
|
+
)
|
|
191
|
+
try:
|
|
192
|
+
resolved_in = _resolve_schema_arg(
|
|
193
|
+
model, sp.request_model
|
|
194
|
+
) # Optional[Type[BaseModel]]
|
|
195
|
+
except Exception as e:
|
|
196
|
+
logger.exception(
|
|
197
|
+
"Failed resolving request schema for %s.%s: %s",
|
|
198
|
+
model.__name__,
|
|
199
|
+
sp.alias,
|
|
200
|
+
e,
|
|
201
|
+
)
|
|
202
|
+
raise
|
|
203
|
+
setattr(ns, "in_", resolved_in)
|
|
204
|
+
logger.debug(
|
|
205
|
+
"Request override for %s.%s resolved to %s",
|
|
206
|
+
model.__name__,
|
|
207
|
+
sp.alias,
|
|
208
|
+
getattr(resolved_in, "__name__", None),
|
|
209
|
+
)
|
|
210
|
+
else:
|
|
211
|
+
logger.debug("No request override for %s.%s", model.__name__, sp.alias)
|
|
212
|
+
|
|
213
|
+
if sp.response_model is not None:
|
|
214
|
+
logger.debug(
|
|
215
|
+
"Resolving response override for %s.%s", model.__name__, sp.alias
|
|
216
|
+
)
|
|
217
|
+
try:
|
|
218
|
+
resolved_out = _resolve_schema_arg(
|
|
219
|
+
model, sp.response_model
|
|
220
|
+
) # Optional[Type[BaseModel]]
|
|
221
|
+
except Exception as e:
|
|
222
|
+
logger.exception(
|
|
223
|
+
"Failed resolving response schema for %s.%s: %s",
|
|
224
|
+
model.__name__,
|
|
225
|
+
sp.alias,
|
|
226
|
+
e,
|
|
227
|
+
)
|
|
228
|
+
raise
|
|
229
|
+
setattr(ns, "out", resolved_out)
|
|
230
|
+
logger.debug(
|
|
231
|
+
"Response override for %s.%s resolved to %s",
|
|
232
|
+
model.__name__,
|
|
233
|
+
sp.alias,
|
|
234
|
+
getattr(resolved_out, "__name__", None),
|
|
235
|
+
)
|
|
236
|
+
else:
|
|
237
|
+
logger.debug("No response override for %s.%s", model.__name__, sp.alias)
|
|
238
|
+
|
|
239
|
+
logger.debug(
|
|
240
|
+
"schemas(override): %s.%s -> in=%s out=%s",
|
|
241
|
+
model.__name__,
|
|
242
|
+
sp.alias,
|
|
243
|
+
getattr(ns, "in_", None).__name__ if getattr(ns, "in_", None) else None,
|
|
244
|
+
getattr(ns, "out", None).__name__ if getattr(ns, "out", None) else None,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# Pass 2b: restore canonical defaults if overrides cleared them
|
|
248
|
+
for sp in specs:
|
|
249
|
+
if sp.target == "custom":
|
|
250
|
+
logger.debug(
|
|
251
|
+
"Skipping default restoration for custom target %s.%s",
|
|
252
|
+
model.__name__,
|
|
253
|
+
sp.alias,
|
|
254
|
+
)
|
|
255
|
+
continue
|
|
256
|
+
ns = _ensure_alias_namespace(model, sp.alias)
|
|
257
|
+
shapes = defaults.get((sp.alias, sp.target)) or {}
|
|
258
|
+
if getattr(ns, "in_", None) is None and shapes.get("in_") is not None:
|
|
259
|
+
setattr(ns, "in_", shapes["in_"])
|
|
260
|
+
logger.debug(
|
|
261
|
+
"Restored default request schema for %s.%s", model.__name__, sp.alias
|
|
262
|
+
)
|
|
263
|
+
else:
|
|
264
|
+
logger.debug(
|
|
265
|
+
"No request schema restoration needed for %s.%s",
|
|
266
|
+
model.__name__,
|
|
267
|
+
sp.alias,
|
|
268
|
+
)
|
|
269
|
+
if getattr(ns, "in_item", None) is None and shapes.get("in_item") is not None:
|
|
270
|
+
setattr(ns, "in_item", shapes["in_item"])
|
|
271
|
+
logger.debug(
|
|
272
|
+
"Restored default request item schema for %s.%s",
|
|
273
|
+
model.__name__,
|
|
274
|
+
sp.alias,
|
|
275
|
+
)
|
|
276
|
+
else:
|
|
277
|
+
logger.debug(
|
|
278
|
+
"No request item schema restoration needed for %s.%s",
|
|
279
|
+
model.__name__,
|
|
280
|
+
sp.alias,
|
|
281
|
+
)
|
|
282
|
+
if getattr(ns, "out", None) is None and shapes.get("out") is not None:
|
|
283
|
+
setattr(ns, "out", shapes["out"])
|
|
284
|
+
logger.debug(
|
|
285
|
+
"Restored default response schema for %s.%s", model.__name__, sp.alias
|
|
286
|
+
)
|
|
287
|
+
else:
|
|
288
|
+
logger.debug(
|
|
289
|
+
"No response schema restoration needed for %s.%s",
|
|
290
|
+
model.__name__,
|
|
291
|
+
sp.alias,
|
|
292
|
+
)
|
|
293
|
+
if getattr(ns, "out_item", None) is None and shapes.get("out_item") is not None:
|
|
294
|
+
setattr(ns, "out_item", shapes["out_item"])
|
|
295
|
+
logger.debug(
|
|
296
|
+
"Restored default response item schema for %s.%s",
|
|
297
|
+
model.__name__,
|
|
298
|
+
sp.alias,
|
|
299
|
+
)
|
|
300
|
+
else:
|
|
301
|
+
logger.debug(
|
|
302
|
+
"No response item schema restoration needed for %s.%s",
|
|
303
|
+
model.__name__,
|
|
304
|
+
sp.alias,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# Pass 3: ensure alias-specific request/response schema names
|
|
308
|
+
for sp in specs:
|
|
309
|
+
ns = _ensure_alias_namespace(model, sp.alias)
|
|
310
|
+
in_model = getattr(ns, "in_", None)
|
|
311
|
+
if (
|
|
312
|
+
isinstance(in_model, type)
|
|
313
|
+
and issubclass(in_model, BaseModel)
|
|
314
|
+
and getattr(in_model, "__tigrbl_schema_decl__", None) is None
|
|
315
|
+
):
|
|
316
|
+
logger.debug("Aliasing request schema for %s.%s", model.__name__, sp.alias)
|
|
317
|
+
setattr(
|
|
318
|
+
ns,
|
|
319
|
+
"in_",
|
|
320
|
+
_alias_schema(in_model, model=model, alias=sp.alias, kind="Request"),
|
|
321
|
+
)
|
|
322
|
+
else:
|
|
323
|
+
logger.debug(
|
|
324
|
+
"Request schema for %s.%s already aliased or missing",
|
|
325
|
+
model.__name__,
|
|
326
|
+
sp.alias,
|
|
327
|
+
)
|
|
328
|
+
out_model = getattr(ns, "out", None)
|
|
329
|
+
if (
|
|
330
|
+
isinstance(out_model, type)
|
|
331
|
+
and issubclass(out_model, BaseModel)
|
|
332
|
+
and getattr(out_model, "__tigrbl_schema_decl__", None) is None
|
|
333
|
+
):
|
|
334
|
+
logger.debug("Aliasing response schema for %s.%s", model.__name__, sp.alias)
|
|
335
|
+
setattr(
|
|
336
|
+
ns,
|
|
337
|
+
"out",
|
|
338
|
+
_alias_schema(out_model, model=model, alias=sp.alias, kind="Response"),
|
|
339
|
+
)
|
|
340
|
+
else:
|
|
341
|
+
logger.debug(
|
|
342
|
+
"Response schema for %s.%s already aliased or missing",
|
|
343
|
+
model.__name__,
|
|
344
|
+
sp.alias,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
__all__ = ["build_and_attach"]
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
# tigrbl/v3/bindings/schemas/defaults.py
|
|
5
|
+
|
|
6
|
+
from typing import Dict, Optional, Type
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from ...op import OpSpec
|
|
11
|
+
from ...schema import (
|
|
12
|
+
_build_schema,
|
|
13
|
+
_build_list_params,
|
|
14
|
+
_make_bulk_rows_model,
|
|
15
|
+
_make_bulk_rows_response_model,
|
|
16
|
+
_make_bulk_ids_model,
|
|
17
|
+
_make_deleted_response_model,
|
|
18
|
+
_make_pk_model,
|
|
19
|
+
)
|
|
20
|
+
from .utils import _pk_info
|
|
21
|
+
|
|
22
|
+
logging.getLogger("uvicorn").setLevel(logging.DEBUG)
|
|
23
|
+
logger = logging.getLogger("uvicorn")
|
|
24
|
+
logger.debug("Loaded module v3/bindings/schemas/defaults")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _default_schemas_for_spec(
|
|
28
|
+
model: type, sp: OpSpec
|
|
29
|
+
) -> Dict[str, Optional[Type[BaseModel]]]:
|
|
30
|
+
"""
|
|
31
|
+
Decide default IN/OUT schemas for a given OpSpec (ignores sp.request_model/response_model).
|
|
32
|
+
|
|
33
|
+
New rules:
|
|
34
|
+
• Canonical targets → provide canonical defaults.
|
|
35
|
+
• Custom target → no defaults (raw) unless explicitly overridden.
|
|
36
|
+
"""
|
|
37
|
+
target = sp.target
|
|
38
|
+
result: Dict[str, Optional[Type[BaseModel]]] = {
|
|
39
|
+
"in_": None,
|
|
40
|
+
"out": None,
|
|
41
|
+
"in_item": None,
|
|
42
|
+
"out_item": None,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Element schema for many OUT shapes
|
|
46
|
+
read_schema: Optional[Type[BaseModel]] = _build_schema(model, verb="read")
|
|
47
|
+
logger.debug(
|
|
48
|
+
"Resolved base read schema for %s as %s",
|
|
49
|
+
model.__name__,
|
|
50
|
+
read_schema.__name__ if read_schema else None,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Canonical targets
|
|
54
|
+
logger.debug(
|
|
55
|
+
"Building default schemas for %s.%s (target=%s)",
|
|
56
|
+
model.__name__,
|
|
57
|
+
sp.alias,
|
|
58
|
+
target,
|
|
59
|
+
)
|
|
60
|
+
if target == "create":
|
|
61
|
+
logger.debug("Using create defaults for %s.%s", model.__name__, sp.alias)
|
|
62
|
+
item_in = _build_schema(model, verb="create")
|
|
63
|
+
result["in_"] = item_in
|
|
64
|
+
result["out"] = read_schema
|
|
65
|
+
|
|
66
|
+
elif target == "read":
|
|
67
|
+
logger.debug("Using read defaults for %s.%s", model.__name__, sp.alias)
|
|
68
|
+
pk_name, pk_type = _pk_info(model)
|
|
69
|
+
result["in_"] = _make_pk_model(model, "read", pk_name, pk_type)
|
|
70
|
+
result["out"] = read_schema
|
|
71
|
+
|
|
72
|
+
elif target == "update":
|
|
73
|
+
logger.debug("Using update defaults for %s.%s", model.__name__, sp.alias)
|
|
74
|
+
pk_name, _ = _pk_info(model)
|
|
75
|
+
result["in_"] = _build_schema(model, verb="update", exclude={pk_name})
|
|
76
|
+
result["out"] = read_schema
|
|
77
|
+
|
|
78
|
+
elif target == "replace":
|
|
79
|
+
logger.debug("Using replace defaults for %s.%s", model.__name__, sp.alias)
|
|
80
|
+
pk_name, _ = _pk_info(model)
|
|
81
|
+
result["in_"] = _build_schema(model, verb="replace", exclude={pk_name})
|
|
82
|
+
result["out"] = read_schema
|
|
83
|
+
|
|
84
|
+
elif target == "merge":
|
|
85
|
+
logger.debug("Using merge defaults for %s.%s", model.__name__, sp.alias)
|
|
86
|
+
pk_name, _ = _pk_info(model)
|
|
87
|
+
result["in_"] = _build_schema(model, verb="update", exclude={pk_name})
|
|
88
|
+
result["out"] = read_schema
|
|
89
|
+
|
|
90
|
+
elif target == "delete":
|
|
91
|
+
logger.debug("Using delete defaults for %s.%s", model.__name__, sp.alias)
|
|
92
|
+
# For RPC delete, a body with PK is allowed; REST delete ignores body.
|
|
93
|
+
result["in_"] = _build_schema(model, verb="delete")
|
|
94
|
+
result["out"] = read_schema
|
|
95
|
+
|
|
96
|
+
elif target == "list":
|
|
97
|
+
logger.debug("Using list defaults for %s.%s", model.__name__, sp.alias)
|
|
98
|
+
params = _build_list_params(model)
|
|
99
|
+
result["in_"] = params
|
|
100
|
+
result["out"] = read_schema
|
|
101
|
+
|
|
102
|
+
elif target == "clear":
|
|
103
|
+
logger.debug("Using clear defaults for %s.%s", model.__name__, sp.alias)
|
|
104
|
+
params = _build_list_params(model)
|
|
105
|
+
result["in_"] = params
|
|
106
|
+
result["out"] = _make_deleted_response_model(model, "clear")
|
|
107
|
+
|
|
108
|
+
elif target == "bulk_create":
|
|
109
|
+
logger.debug("Using bulk_create defaults for %s.%s", model.__name__, sp.alias)
|
|
110
|
+
item_in = _build_schema(
|
|
111
|
+
model,
|
|
112
|
+
verb="create",
|
|
113
|
+
name=f"{model.__name__}BulkCreateItem",
|
|
114
|
+
)
|
|
115
|
+
result["in_"] = _make_bulk_rows_model(model, "bulk_create", item_in)
|
|
116
|
+
result["in_item"] = item_in
|
|
117
|
+
if read_schema:
|
|
118
|
+
result["out"] = _make_bulk_rows_response_model(
|
|
119
|
+
model, "bulk_create", read_schema
|
|
120
|
+
)
|
|
121
|
+
result["out_item"] = read_schema
|
|
122
|
+
logger.debug(
|
|
123
|
+
"Built bulk_create response schemas for %s.%s", model.__name__, sp.alias
|
|
124
|
+
)
|
|
125
|
+
else:
|
|
126
|
+
result["out"] = None
|
|
127
|
+
result["out_item"] = None
|
|
128
|
+
logger.debug(
|
|
129
|
+
"No read schema available for bulk_create %s.%s",
|
|
130
|
+
model.__name__,
|
|
131
|
+
sp.alias,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
elif target == "bulk_update":
|
|
135
|
+
logger.debug("Using bulk_update defaults for %s.%s", model.__name__, sp.alias)
|
|
136
|
+
item_in = _build_schema(
|
|
137
|
+
model,
|
|
138
|
+
verb="update",
|
|
139
|
+
name=f"{model.__name__}BulkUpdateItem",
|
|
140
|
+
)
|
|
141
|
+
result["in_"] = _make_bulk_rows_model(model, "bulk_update", item_in)
|
|
142
|
+
result["in_item"] = item_in
|
|
143
|
+
if read_schema:
|
|
144
|
+
result["out"] = _make_bulk_rows_response_model(
|
|
145
|
+
model, "bulk_update", read_schema
|
|
146
|
+
)
|
|
147
|
+
result["out_item"] = read_schema
|
|
148
|
+
logger.debug(
|
|
149
|
+
"Built bulk_update response schemas for %s.%s", model.__name__, sp.alias
|
|
150
|
+
)
|
|
151
|
+
else:
|
|
152
|
+
result["out"] = None
|
|
153
|
+
result["out_item"] = None
|
|
154
|
+
logger.debug(
|
|
155
|
+
"No read schema available for bulk_update %s.%s",
|
|
156
|
+
model.__name__,
|
|
157
|
+
sp.alias,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
elif target == "bulk_replace":
|
|
161
|
+
logger.debug("Using bulk_replace defaults for %s.%s", model.__name__, sp.alias)
|
|
162
|
+
item_in = _build_schema(
|
|
163
|
+
model,
|
|
164
|
+
verb="replace",
|
|
165
|
+
name=f"{model.__name__}BulkReplaceItem",
|
|
166
|
+
)
|
|
167
|
+
result["in_"] = _make_bulk_rows_model(model, "bulk_replace", item_in)
|
|
168
|
+
result["in_item"] = item_in
|
|
169
|
+
if read_schema:
|
|
170
|
+
result["out"] = _make_bulk_rows_response_model(
|
|
171
|
+
model, "bulk_replace", read_schema
|
|
172
|
+
)
|
|
173
|
+
result["out_item"] = read_schema
|
|
174
|
+
logger.debug(
|
|
175
|
+
"Built bulk_replace response schemas for %s.%s",
|
|
176
|
+
model.__name__,
|
|
177
|
+
sp.alias,
|
|
178
|
+
)
|
|
179
|
+
else:
|
|
180
|
+
result["out"] = None
|
|
181
|
+
result["out_item"] = None
|
|
182
|
+
logger.debug(
|
|
183
|
+
"No read schema available for bulk_replace %s.%s",
|
|
184
|
+
model.__name__,
|
|
185
|
+
sp.alias,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
elif target == "bulk_merge":
|
|
189
|
+
logger.debug("Using bulk_merge defaults for %s.%s", model.__name__, sp.alias)
|
|
190
|
+
item_in = _build_schema(
|
|
191
|
+
model,
|
|
192
|
+
verb="update",
|
|
193
|
+
name=f"{model.__name__}BulkMergeItem",
|
|
194
|
+
)
|
|
195
|
+
result["in_"] = _make_bulk_rows_model(model, "bulk_merge", item_in)
|
|
196
|
+
result["in_item"] = item_in
|
|
197
|
+
if read_schema:
|
|
198
|
+
result["out"] = _make_bulk_rows_response_model(
|
|
199
|
+
model, "bulk_merge", read_schema
|
|
200
|
+
)
|
|
201
|
+
result["out_item"] = read_schema
|
|
202
|
+
logger.debug(
|
|
203
|
+
"Built bulk_merge response schemas for %s.%s", model.__name__, sp.alias
|
|
204
|
+
)
|
|
205
|
+
else:
|
|
206
|
+
result["out"] = None
|
|
207
|
+
result["out_item"] = None
|
|
208
|
+
logger.debug(
|
|
209
|
+
"No read schema available for bulk_merge %s.%s",
|
|
210
|
+
model.__name__,
|
|
211
|
+
sp.alias,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
elif target == "bulk_delete":
|
|
215
|
+
logger.debug("Using bulk_delete defaults for %s.%s", model.__name__, sp.alias)
|
|
216
|
+
pk_name, pk_type = _pk_info(model)
|
|
217
|
+
result["in_"] = _make_bulk_ids_model(model, "bulk_delete", pk_type)
|
|
218
|
+
result["out"] = _make_deleted_response_model(model, "bulk_delete")
|
|
219
|
+
|
|
220
|
+
elif target == "custom":
|
|
221
|
+
logger.debug("Using custom defaults for %s.%s", model.__name__, sp.alias)
|
|
222
|
+
# Build schemas for custom operations based on verb-specific IO specs
|
|
223
|
+
alias = sp.alias
|
|
224
|
+
specs = getattr(model, "__tigrbl_cols__", {})
|
|
225
|
+
in_fields = {
|
|
226
|
+
name
|
|
227
|
+
for name, spec in specs.items()
|
|
228
|
+
if alias in set(getattr(getattr(spec, "io", None), "in_verbs", []) or [])
|
|
229
|
+
}
|
|
230
|
+
out_fields = {
|
|
231
|
+
name
|
|
232
|
+
for name, spec in specs.items()
|
|
233
|
+
if alias in set(getattr(getattr(spec, "io", None), "out_verbs", []) or [])
|
|
234
|
+
}
|
|
235
|
+
result["in_"] = (
|
|
236
|
+
_build_schema(model, verb=alias, include=in_fields) if in_fields else None
|
|
237
|
+
)
|
|
238
|
+
result["out"] = (
|
|
239
|
+
_build_schema(model, verb=alias, include=out_fields) if out_fields else None
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
else:
|
|
243
|
+
logger.debug(
|
|
244
|
+
"Target '%s' unknown for %s.%s, leaving schemas raw",
|
|
245
|
+
target,
|
|
246
|
+
model.__name__,
|
|
247
|
+
sp.alias,
|
|
248
|
+
)
|
|
249
|
+
# Defensive default: treat unknown like custom (raw)
|
|
250
|
+
result["in_"] = None
|
|
251
|
+
result["out"] = None
|
|
252
|
+
|
|
253
|
+
logger.debug(
|
|
254
|
+
"Built default schemas for %s.%s -> in=%s out=%s",
|
|
255
|
+
model.__name__,
|
|
256
|
+
sp.alias,
|
|
257
|
+
result["in_"].__name__ if result["in_"] else None,
|
|
258
|
+
result["out"].__name__ if result["out"] else None,
|
|
259
|
+
)
|
|
260
|
+
return result
|