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
tigrbl/runtime/events.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# tigrbl/v3/runtime/events.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Dict, Iterable, List, Literal, Tuple
|
|
6
|
+
|
|
7
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
# Phases
|
|
9
|
+
# - PRE_TX is a synthetic phase for security/dependency checks.
|
|
10
|
+
# - START_TX / END_TX are reserved for system steps (no atom anchors there).
|
|
11
|
+
# - Atoms bind only to the event anchors below.
|
|
12
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
Phase = Literal[
|
|
15
|
+
"PRE_TX",
|
|
16
|
+
"START_TX",
|
|
17
|
+
"PRE_HANDLER",
|
|
18
|
+
"HANDLER",
|
|
19
|
+
"POST_HANDLER",
|
|
20
|
+
"END_TX",
|
|
21
|
+
"POST_RESPONSE",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
PHASES: Tuple[Phase, ...] = (
|
|
25
|
+
"PRE_TX",
|
|
26
|
+
"START_TX", # system-only
|
|
27
|
+
"PRE_HANDLER",
|
|
28
|
+
"HANDLER",
|
|
29
|
+
"POST_HANDLER",
|
|
30
|
+
"END_TX", # system-only
|
|
31
|
+
"POST_RESPONSE",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
35
|
+
# Canonical anchors (events) — the only moments atoms can bind to
|
|
36
|
+
# Keep these names stable; labels use them directly: step_kind:domain:subject@ANCHOR
|
|
37
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
# PRE_HANDLER
|
|
40
|
+
SCHEMA_COLLECT_IN = "schema:collect_in"
|
|
41
|
+
IN_VALIDATE = "in:validate"
|
|
42
|
+
|
|
43
|
+
# HANDLER
|
|
44
|
+
RESOLVE_VALUES = "resolve:values"
|
|
45
|
+
PRE_FLUSH = "pre:flush"
|
|
46
|
+
EMIT_ALIASES_PRE = "emit:aliases:pre_flush"
|
|
47
|
+
|
|
48
|
+
# POST_HANDLER
|
|
49
|
+
POST_FLUSH = "post:flush"
|
|
50
|
+
EMIT_ALIASES_POST = "emit:aliases:post_refresh"
|
|
51
|
+
SCHEMA_COLLECT_OUT = "schema:collect_out"
|
|
52
|
+
OUT_BUILD = "out:build"
|
|
53
|
+
|
|
54
|
+
# POST_RESPONSE
|
|
55
|
+
EMIT_ALIASES_READ = "emit:aliases:readtime"
|
|
56
|
+
OUT_DUMP = "out:dump"
|
|
57
|
+
|
|
58
|
+
# Canonical order of event anchors within the request lifecycle.
|
|
59
|
+
# This ordering is global and stable; use it to produce deterministic plans/traces.
|
|
60
|
+
_EVENT_ORDER: Tuple[str, ...] = (
|
|
61
|
+
# PRE_HANDLER
|
|
62
|
+
SCHEMA_COLLECT_IN,
|
|
63
|
+
IN_VALIDATE,
|
|
64
|
+
# HANDLER
|
|
65
|
+
RESOLVE_VALUES,
|
|
66
|
+
PRE_FLUSH,
|
|
67
|
+
EMIT_ALIASES_PRE,
|
|
68
|
+
# POST_HANDLER
|
|
69
|
+
POST_FLUSH,
|
|
70
|
+
EMIT_ALIASES_POST,
|
|
71
|
+
SCHEMA_COLLECT_OUT,
|
|
72
|
+
OUT_BUILD,
|
|
73
|
+
# POST_RESPONSE
|
|
74
|
+
EMIT_ALIASES_READ,
|
|
75
|
+
OUT_DUMP,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Map each anchor to its phase and persistence tie.
|
|
80
|
+
# "persist_tied=True" means the anchor is pruned for non-persisting ops
|
|
81
|
+
# (e.g., read/list) and whenever an op is executed with persist=False.
|
|
82
|
+
@dataclass(frozen=True)
|
|
83
|
+
class AnchorInfo:
|
|
84
|
+
name: str
|
|
85
|
+
phase: Phase
|
|
86
|
+
ordinal: int
|
|
87
|
+
persist_tied: bool
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
_ANCHORS: Dict[str, AnchorInfo] = {
|
|
91
|
+
# PRE_HANDLER (not persist-tied)
|
|
92
|
+
SCHEMA_COLLECT_IN: AnchorInfo(SCHEMA_COLLECT_IN, "PRE_HANDLER", 0, False),
|
|
93
|
+
IN_VALIDATE: AnchorInfo(IN_VALIDATE, "PRE_HANDLER", 1, False),
|
|
94
|
+
RESOLVE_VALUES: AnchorInfo(RESOLVE_VALUES, "PRE_HANDLER", 2, True),
|
|
95
|
+
PRE_FLUSH: AnchorInfo(PRE_FLUSH, "PRE_HANDLER", 3, True),
|
|
96
|
+
EMIT_ALIASES_PRE: AnchorInfo(EMIT_ALIASES_PRE, "PRE_HANDLER", 4, True),
|
|
97
|
+
# POST_HANDLER (mixed)
|
|
98
|
+
POST_FLUSH: AnchorInfo(POST_FLUSH, "POST_HANDLER", 5, True),
|
|
99
|
+
EMIT_ALIASES_POST: AnchorInfo(EMIT_ALIASES_POST, "POST_HANDLER", 6, True),
|
|
100
|
+
SCHEMA_COLLECT_OUT: AnchorInfo(SCHEMA_COLLECT_OUT, "POST_HANDLER", 7, False),
|
|
101
|
+
OUT_BUILD: AnchorInfo(OUT_BUILD, "POST_HANDLER", 8, False),
|
|
102
|
+
# POST_RESPONSE (not persist-tied)
|
|
103
|
+
EMIT_ALIASES_READ: AnchorInfo(EMIT_ALIASES_READ, "POST_RESPONSE", 9, False),
|
|
104
|
+
OUT_DUMP: AnchorInfo(OUT_DUMP, "POST_RESPONSE", 10, False),
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
108
|
+
# Public helpers
|
|
109
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def is_valid_event(anchor: str) -> bool:
|
|
113
|
+
"""True if the given anchor is one of the canonical events."""
|
|
114
|
+
return anchor in _ANCHORS
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def phase_for_event(anchor: str) -> Phase:
|
|
118
|
+
"""Return the Phase for a canonical event; raises on unknown anchors."""
|
|
119
|
+
try:
|
|
120
|
+
return _ANCHORS[anchor].phase
|
|
121
|
+
except KeyError as e:
|
|
122
|
+
raise ValueError(f"Unknown event anchor: {anchor!r}") from e
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def is_persist_tied(anchor: str) -> bool:
|
|
126
|
+
"""Return True if this event is pruned for non-persisting ops."""
|
|
127
|
+
try:
|
|
128
|
+
return _ANCHORS[anchor].persist_tied
|
|
129
|
+
except KeyError as e:
|
|
130
|
+
raise ValueError(f"Unknown event anchor: {anchor!r}") from e
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_anchor_info(anchor: str) -> AnchorInfo:
|
|
134
|
+
"""Return the full :class:`AnchorInfo` for a canonical event."""
|
|
135
|
+
try:
|
|
136
|
+
return _ANCHORS[anchor]
|
|
137
|
+
except KeyError as e:
|
|
138
|
+
raise ValueError(f"Unknown event anchor: {anchor!r}") from e
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def all_events_ordered() -> List[str]:
|
|
142
|
+
"""Return all canonical events in deterministic, lifecycle order."""
|
|
143
|
+
return list(_EVENT_ORDER)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def events_for_phase(phase: Phase) -> List[str]:
|
|
147
|
+
"""Return the subset of events that belong to the given phase."""
|
|
148
|
+
if phase not in PHASES:
|
|
149
|
+
raise ValueError(f"Unknown phase: {phase!r}")
|
|
150
|
+
return [a for a, info in _ANCHORS.items() if info.phase == phase]
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def prune_events_for_persist(anchors: Iterable[str], *, persist: bool) -> List[str]:
|
|
154
|
+
"""
|
|
155
|
+
Given a sequence of anchors, return a new list with persistence-tied events
|
|
156
|
+
removed when persist=False. Unknown anchors raise ValueError.
|
|
157
|
+
"""
|
|
158
|
+
out: List[str] = []
|
|
159
|
+
for a in anchors:
|
|
160
|
+
if not is_valid_event(a):
|
|
161
|
+
raise ValueError(f"Unknown event anchor: {a!r}")
|
|
162
|
+
if not persist and _ANCHORS[a].persist_tied:
|
|
163
|
+
continue
|
|
164
|
+
out.append(a)
|
|
165
|
+
# keep canonical order irrespective of input order
|
|
166
|
+
out.sort(key=lambda x: _ANCHORS[x].ordinal)
|
|
167
|
+
return out
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def order_events(anchors: Iterable[str]) -> List[str]:
|
|
171
|
+
"""
|
|
172
|
+
Sort a set/list of anchors into canonical lifecycle order.
|
|
173
|
+
Raises on unknown anchors.
|
|
174
|
+
"""
|
|
175
|
+
anchors = list(anchors)
|
|
176
|
+
for a in anchors:
|
|
177
|
+
if a not in _ANCHORS:
|
|
178
|
+
raise ValueError(f"Unknown event anchor: {a!r}")
|
|
179
|
+
anchors.sort(key=lambda x: _ANCHORS[x].ordinal)
|
|
180
|
+
return anchors
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
__all__ = [
|
|
184
|
+
# Phases
|
|
185
|
+
"Phase",
|
|
186
|
+
"PHASES",
|
|
187
|
+
# Anchors (constants)
|
|
188
|
+
"SCHEMA_COLLECT_IN",
|
|
189
|
+
"IN_VALIDATE",
|
|
190
|
+
"RESOLVE_VALUES",
|
|
191
|
+
"PRE_FLUSH",
|
|
192
|
+
"EMIT_ALIASES_PRE",
|
|
193
|
+
"POST_FLUSH",
|
|
194
|
+
"EMIT_ALIASES_POST",
|
|
195
|
+
"SCHEMA_COLLECT_OUT",
|
|
196
|
+
"OUT_BUILD",
|
|
197
|
+
"EMIT_ALIASES_READ",
|
|
198
|
+
"OUT_DUMP",
|
|
199
|
+
# Types / helpers
|
|
200
|
+
"AnchorInfo",
|
|
201
|
+
"is_valid_event",
|
|
202
|
+
"phase_for_event",
|
|
203
|
+
"is_persist_tied",
|
|
204
|
+
"get_anchor_info",
|
|
205
|
+
"all_events_ordered",
|
|
206
|
+
"events_for_phase",
|
|
207
|
+
"prune_events_for_persist",
|
|
208
|
+
"order_events",
|
|
209
|
+
]
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Database session guard utilities for the runtime executor.
|
|
2
|
+
|
|
3
|
+
This module temporarily replaces ``commit`` and ``flush`` on SQLAlchemy
|
|
4
|
+
sessions to enforce phase-specific policies. Each guard returns a handle that
|
|
5
|
+
restores the original methods once the phase completes and provides helpers to
|
|
6
|
+
rollback when the runtime owns the transaction.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Any, Optional, Union
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from sqlalchemy.orm import Session # type: ignore
|
|
16
|
+
from sqlalchemy.ext.asyncio import AsyncSession # type: ignore
|
|
17
|
+
except Exception: # pragma: no cover
|
|
18
|
+
Session = Any # type: ignore
|
|
19
|
+
AsyncSession = Any # type: ignore
|
|
20
|
+
|
|
21
|
+
from .types import _Ctx, PhaseChains
|
|
22
|
+
from .helpers import _is_async_db, _run_chain, _g
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _GuardHandle:
|
|
28
|
+
"""Stores original ``commit``/``flush`` methods for later restoration."""
|
|
29
|
+
|
|
30
|
+
__slots__ = ("db", "orig_commit", "orig_flush")
|
|
31
|
+
|
|
32
|
+
def __init__(self, db: Any, orig_commit: Any, orig_flush: Any) -> None:
|
|
33
|
+
self.db = db
|
|
34
|
+
self.orig_commit = orig_commit
|
|
35
|
+
self.orig_flush = orig_flush
|
|
36
|
+
|
|
37
|
+
def restore(self) -> None:
|
|
38
|
+
if self.orig_commit is not None:
|
|
39
|
+
try:
|
|
40
|
+
setattr(self.db, "commit", self.orig_commit)
|
|
41
|
+
except Exception:
|
|
42
|
+
pass # pragma: no cover
|
|
43
|
+
if self.orig_flush is not None:
|
|
44
|
+
try:
|
|
45
|
+
setattr(self.db, "flush", self.orig_flush)
|
|
46
|
+
except Exception:
|
|
47
|
+
pass # pragma: no cover
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _install_db_guards(
|
|
51
|
+
db: Union[Session, AsyncSession, None],
|
|
52
|
+
*,
|
|
53
|
+
phase: str,
|
|
54
|
+
allow_flush: bool,
|
|
55
|
+
allow_commit: bool,
|
|
56
|
+
require_owned_tx_for_commit: bool,
|
|
57
|
+
owns_tx: bool,
|
|
58
|
+
) -> _GuardHandle:
|
|
59
|
+
"""Install guards that restrict ``commit``/``flush`` during a phase.
|
|
60
|
+
|
|
61
|
+
Parameters:
|
|
62
|
+
db: SQLAlchemy ``Session``/``AsyncSession`` to guard.
|
|
63
|
+
phase: Name of the executing phase for error messages.
|
|
64
|
+
allow_flush: Whether ``flush`` should be permitted.
|
|
65
|
+
allow_commit: Whether ``commit`` should be permitted.
|
|
66
|
+
require_owned_tx_for_commit: Block commits if the executor did not
|
|
67
|
+
open the transaction.
|
|
68
|
+
owns_tx: Whether the runtime opened the transaction.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
A ``_GuardHandle`` for restoring original methods.
|
|
72
|
+
"""
|
|
73
|
+
if db is None:
|
|
74
|
+
return _GuardHandle(None, None, None)
|
|
75
|
+
orig_commit = getattr(db, "commit", None)
|
|
76
|
+
orig_flush = getattr(db, "flush", None)
|
|
77
|
+
|
|
78
|
+
def _raise(op: str) -> None:
|
|
79
|
+
raise RuntimeError(f"db.{op}() is not allowed during {phase} phase")
|
|
80
|
+
|
|
81
|
+
if not allow_commit or (require_owned_tx_for_commit and not owns_tx):
|
|
82
|
+
if _is_async_db(db):
|
|
83
|
+
|
|
84
|
+
async def _blocked_commit() -> None: # type: ignore[func-returns-value]
|
|
85
|
+
_raise("commit")
|
|
86
|
+
else:
|
|
87
|
+
|
|
88
|
+
def _blocked_commit() -> None: # type: ignore[func-returns-value]
|
|
89
|
+
_raise("commit")
|
|
90
|
+
|
|
91
|
+
setattr(db, "commit", _blocked_commit) # type: ignore[assignment]
|
|
92
|
+
|
|
93
|
+
if not allow_flush:
|
|
94
|
+
if _is_async_db(db):
|
|
95
|
+
|
|
96
|
+
async def _blocked_flush() -> None: # type: ignore[func-returns-value]
|
|
97
|
+
_raise("flush")
|
|
98
|
+
else:
|
|
99
|
+
|
|
100
|
+
def _blocked_flush() -> None: # type: ignore[func-returns-value]
|
|
101
|
+
_raise("flush")
|
|
102
|
+
|
|
103
|
+
setattr(db, "flush", _blocked_flush) # type: ignore[assignment]
|
|
104
|
+
|
|
105
|
+
return _GuardHandle(db, orig_commit, orig_flush)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
async def _rollback_if_owned(
|
|
109
|
+
db: Union[Session, AsyncSession, None],
|
|
110
|
+
owns_tx: bool,
|
|
111
|
+
*,
|
|
112
|
+
phases: Optional[PhaseChains],
|
|
113
|
+
ctx: _Ctx,
|
|
114
|
+
) -> None:
|
|
115
|
+
"""Rollback the session if this runtime owns the transaction."""
|
|
116
|
+
|
|
117
|
+
if not owns_tx or db is None:
|
|
118
|
+
return
|
|
119
|
+
try:
|
|
120
|
+
if _is_async_db(db):
|
|
121
|
+
await db.rollback() # type: ignore[func-returns-value]
|
|
122
|
+
else:
|
|
123
|
+
db.rollback()
|
|
124
|
+
except Exception as rb_exc: # pragma: no cover
|
|
125
|
+
logger.exception("Rollback failed: %s", rb_exc)
|
|
126
|
+
try:
|
|
127
|
+
await _run_chain(ctx, _g(phases, "ON_ROLLBACK"), phase="ON_ROLLBACK")
|
|
128
|
+
except Exception: # pragma: no cover
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
__all__ = ["_GuardHandle", "_install_db_guards", "_rollback_if_owned"]
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# tigrbl/v3/runtime/executor/helpers.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import inspect
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Iterable, Optional, Sequence
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from sqlalchemy.ext.asyncio import AsyncSession # type: ignore
|
|
10
|
+
except Exception: # pragma: no cover
|
|
11
|
+
AsyncSession = Any # type: ignore
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from .. import trace as _trace # type: ignore
|
|
15
|
+
except Exception: # pragma: no cover
|
|
16
|
+
_trace = None # type: ignore
|
|
17
|
+
|
|
18
|
+
from .types import _Ctx, HandlerStep, PhaseChains
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _is_async_db(db: Any) -> bool:
|
|
24
|
+
"""Detect DB interfaces that require `await` for transactional methods."""
|
|
25
|
+
if isinstance(db, AsyncSession) or hasattr(db, "run_sync"):
|
|
26
|
+
return True
|
|
27
|
+
for attr in ("commit", "begin", "rollback", "flush"):
|
|
28
|
+
if inspect.iscoroutinefunction(getattr(db, attr, None)):
|
|
29
|
+
return True
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _bool_call(meth: Any) -> bool:
|
|
34
|
+
try:
|
|
35
|
+
return bool(meth())
|
|
36
|
+
except Exception: # pragma: no cover
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _in_tx(db: Any) -> bool:
|
|
41
|
+
for name in ("in_transaction", "in_nested_transaction"):
|
|
42
|
+
attr = getattr(db, name, None)
|
|
43
|
+
if callable(attr):
|
|
44
|
+
if _bool_call(attr):
|
|
45
|
+
return True
|
|
46
|
+
elif attr:
|
|
47
|
+
return True
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def _maybe_await(v: Any) -> Any:
|
|
52
|
+
if inspect.isawaitable(v):
|
|
53
|
+
return await v # type: ignore[func-returns-value]
|
|
54
|
+
return v
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
async def _run_chain(
|
|
58
|
+
ctx: _Ctx, chain: Optional[Iterable[HandlerStep]], *, phase: str
|
|
59
|
+
) -> None:
|
|
60
|
+
if not chain:
|
|
61
|
+
return
|
|
62
|
+
if _trace is not None:
|
|
63
|
+
with _trace.span(ctx, f"phase:{phase}"):
|
|
64
|
+
for step in chain:
|
|
65
|
+
rv = step(ctx)
|
|
66
|
+
rv = await _maybe_await(rv)
|
|
67
|
+
if rv is not None:
|
|
68
|
+
ctx.result = rv
|
|
69
|
+
return
|
|
70
|
+
for step in chain:
|
|
71
|
+
rv = step(ctx)
|
|
72
|
+
rv = await _maybe_await(rv)
|
|
73
|
+
if rv is not None:
|
|
74
|
+
ctx.result = rv
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _g(phases: Optional[PhaseChains], key: str) -> Sequence[HandlerStep]:
|
|
78
|
+
return () if not phases else phases.get(key, ())
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
__all__ = [
|
|
82
|
+
"_is_async_db",
|
|
83
|
+
"_bool_call",
|
|
84
|
+
"_in_tx",
|
|
85
|
+
"_maybe_await",
|
|
86
|
+
"_run_chain",
|
|
87
|
+
"_g",
|
|
88
|
+
]
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# tigrbl/v3/runtime/executor/invoke.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Any, MutableMapping, Optional, Union
|
|
6
|
+
|
|
7
|
+
from .types import _Ctx, PhaseChains, Request, Session, AsyncSession
|
|
8
|
+
from .helpers import _in_tx, _run_chain, _g
|
|
9
|
+
from .guards import _install_db_guards, _rollback_if_owned
|
|
10
|
+
from ..errors import create_standardized_error
|
|
11
|
+
from ...config.constants import CTX_SKIP_PERSIST_FLAG
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def _invoke(
|
|
17
|
+
*,
|
|
18
|
+
request: Optional[Request],
|
|
19
|
+
db: Union[Session, AsyncSession, None],
|
|
20
|
+
phases: Optional[PhaseChains],
|
|
21
|
+
ctx: Optional[MutableMapping[str, Any]] = None,
|
|
22
|
+
) -> Any:
|
|
23
|
+
"""Execute an operation through explicit phases with strict write policies."""
|
|
24
|
+
|
|
25
|
+
ctx = _Ctx.ensure(request=request, db=db, seed=ctx)
|
|
26
|
+
if getattr(ctx, "app", None) is None and getattr(ctx, "api", None) is not None:
|
|
27
|
+
ctx.app = ctx.api
|
|
28
|
+
if getattr(ctx, "op", None) is None and getattr(ctx, "method", None) is not None:
|
|
29
|
+
ctx.op = ctx.method
|
|
30
|
+
if getattr(ctx, "model", None) is None:
|
|
31
|
+
obj = getattr(ctx, "obj", None)
|
|
32
|
+
if obj is not None:
|
|
33
|
+
ctx.model = type(obj)
|
|
34
|
+
skip_persist: bool = bool(ctx.get(CTX_SKIP_PERSIST_FLAG) or ctx.get("skip_persist"))
|
|
35
|
+
|
|
36
|
+
existed_tx_before = _in_tx(db) if db is not None else False
|
|
37
|
+
|
|
38
|
+
async def _run_phase(
|
|
39
|
+
name: str,
|
|
40
|
+
*,
|
|
41
|
+
allow_flush: bool,
|
|
42
|
+
allow_commit: bool,
|
|
43
|
+
in_tx: bool,
|
|
44
|
+
require_owned_for_commit: bool = True,
|
|
45
|
+
nonfatal: bool = False,
|
|
46
|
+
owns_tx_for_phase: Optional[bool] = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
chain = _g(phases, name)
|
|
49
|
+
if not chain:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
eff_allow_flush = allow_flush and (not skip_persist)
|
|
53
|
+
eff_allow_commit = allow_commit and (not skip_persist)
|
|
54
|
+
|
|
55
|
+
owns_tx_now = bool(owns_tx_for_phase)
|
|
56
|
+
if owns_tx_for_phase is None:
|
|
57
|
+
owns_tx_now = not existed_tx_before
|
|
58
|
+
|
|
59
|
+
guard = _install_db_guards(
|
|
60
|
+
db,
|
|
61
|
+
phase=name,
|
|
62
|
+
allow_flush=eff_allow_flush,
|
|
63
|
+
allow_commit=eff_allow_commit,
|
|
64
|
+
require_owned_tx_for_commit=require_owned_for_commit,
|
|
65
|
+
owns_tx=owns_tx_now,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
await _run_chain(ctx, chain, phase=name)
|
|
70
|
+
except Exception as exc:
|
|
71
|
+
ctx.error = exc
|
|
72
|
+
if in_tx:
|
|
73
|
+
await _rollback_if_owned(db, owns_tx_now, phases=phases, ctx=ctx)
|
|
74
|
+
err_name = f"ON_{name}_ERROR"
|
|
75
|
+
try:
|
|
76
|
+
await _run_chain(
|
|
77
|
+
ctx, _g(phases, err_name) or _g(phases, "ON_ERROR"), phase=err_name
|
|
78
|
+
)
|
|
79
|
+
except Exception: # pragma: no cover
|
|
80
|
+
pass
|
|
81
|
+
if nonfatal:
|
|
82
|
+
logger.exception("%s failed (nonfatal): %s", name, exc)
|
|
83
|
+
return
|
|
84
|
+
raise create_standardized_error(exc)
|
|
85
|
+
finally:
|
|
86
|
+
guard.restore()
|
|
87
|
+
|
|
88
|
+
await _run_phase("PRE_TX_BEGIN", allow_flush=False, allow_commit=False, in_tx=False)
|
|
89
|
+
|
|
90
|
+
if not skip_persist:
|
|
91
|
+
await _run_phase(
|
|
92
|
+
"START_TX",
|
|
93
|
+
allow_flush=False,
|
|
94
|
+
allow_commit=False,
|
|
95
|
+
in_tx=False,
|
|
96
|
+
require_owned_for_commit=True,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
await _run_phase(
|
|
100
|
+
"PRE_HANDLER", allow_flush=True, allow_commit=False, in_tx=not skip_persist
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
await _run_phase(
|
|
104
|
+
"HANDLER", allow_flush=True, allow_commit=False, in_tx=not skip_persist
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
await _run_phase(
|
|
108
|
+
"POST_HANDLER", allow_flush=True, allow_commit=False, in_tx=not skip_persist
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
await _run_phase(
|
|
112
|
+
"PRE_COMMIT", allow_flush=False, allow_commit=False, in_tx=not skip_persist
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if not skip_persist:
|
|
116
|
+
owns_tx_for_commit = (not existed_tx_before) and (db is not None and _in_tx(db))
|
|
117
|
+
await _run_phase(
|
|
118
|
+
"END_TX",
|
|
119
|
+
allow_flush=True,
|
|
120
|
+
allow_commit=True,
|
|
121
|
+
in_tx=True,
|
|
122
|
+
require_owned_for_commit=True,
|
|
123
|
+
owns_tx_for_phase=owns_tx_for_commit,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
from types import SimpleNamespace as _NS
|
|
127
|
+
|
|
128
|
+
serializer = ctx.get("response_serializer")
|
|
129
|
+
if callable(serializer):
|
|
130
|
+
try:
|
|
131
|
+
ctx["result"] = serializer(ctx.get("result"))
|
|
132
|
+
except Exception:
|
|
133
|
+
logger.exception("response serialization failed", exc_info=True)
|
|
134
|
+
ctx.response = _NS(result=ctx.get("result"))
|
|
135
|
+
|
|
136
|
+
await _run_phase("POST_COMMIT", allow_flush=True, allow_commit=False, in_tx=False)
|
|
137
|
+
|
|
138
|
+
await _run_phase(
|
|
139
|
+
"POST_RESPONSE",
|
|
140
|
+
allow_flush=False,
|
|
141
|
+
allow_commit=False,
|
|
142
|
+
in_tx=False,
|
|
143
|
+
nonfatal=True,
|
|
144
|
+
)
|
|
145
|
+
if ctx.get("result") is not None:
|
|
146
|
+
ctx.response.result = ctx.get("result")
|
|
147
|
+
return ctx.response.result
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
__all__ = ["_invoke"]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# tigrbl/v3/runtime/executor/types.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
Awaitable,
|
|
7
|
+
Callable,
|
|
8
|
+
Mapping,
|
|
9
|
+
MutableMapping,
|
|
10
|
+
Optional,
|
|
11
|
+
Sequence,
|
|
12
|
+
Union,
|
|
13
|
+
Protocol,
|
|
14
|
+
runtime_checkable,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from fastapi import Request # type: ignore
|
|
19
|
+
except Exception: # pragma: no cover
|
|
20
|
+
Request = Any # type: ignore
|
|
21
|
+
|
|
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
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@runtime_checkable
|
|
31
|
+
class _Step(Protocol):
|
|
32
|
+
def __call__(self, ctx: "_Ctx") -> Union[Any, Awaitable[Any]]: ...
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
HandlerStep = Union[
|
|
36
|
+
_Step,
|
|
37
|
+
Callable[["_Ctx"], Any],
|
|
38
|
+
Callable[["_Ctx"], Awaitable[Any]],
|
|
39
|
+
]
|
|
40
|
+
PhaseChains = Mapping[str, Sequence[HandlerStep]]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class _Ctx(dict):
|
|
44
|
+
"""Dict-like context with attribute access.
|
|
45
|
+
|
|
46
|
+
Common keys:
|
|
47
|
+
• request: FastAPI Request (optional)
|
|
48
|
+
• db: Session | AsyncSession
|
|
49
|
+
• api/model/op: optional metadata
|
|
50
|
+
• result: last non-None step result
|
|
51
|
+
• error: last exception caught (on failure paths)
|
|
52
|
+
• response: SimpleNamespace(result=...) for POST_RESPONSE shaping
|
|
53
|
+
• temp: scratch dict used by atoms/hook steps
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
__slots__ = ()
|
|
57
|
+
__getattr__ = dict.get # type: ignore[assignment]
|
|
58
|
+
__setattr__ = dict.__setitem__ # type: ignore[assignment]
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def ensure(
|
|
62
|
+
cls,
|
|
63
|
+
*,
|
|
64
|
+
request: Optional[Request],
|
|
65
|
+
db: Union[Session, AsyncSession, None],
|
|
66
|
+
seed: Optional[MutableMapping[str, Any]] = None,
|
|
67
|
+
) -> "_Ctx":
|
|
68
|
+
ctx = cls() if seed is None else (seed if isinstance(seed, _Ctx) else cls(seed))
|
|
69
|
+
if request is not None:
|
|
70
|
+
ctx.request = request
|
|
71
|
+
state = getattr(request, "state", None)
|
|
72
|
+
if state is not None and getattr(state, "ctx", None) is None:
|
|
73
|
+
try:
|
|
74
|
+
state.ctx = ctx # make ctx available to deps
|
|
75
|
+
except Exception: # pragma: no cover
|
|
76
|
+
pass
|
|
77
|
+
if db is not None:
|
|
78
|
+
ctx.db = db
|
|
79
|
+
if "temp" not in ctx or not isinstance(ctx.get("temp"), dict):
|
|
80
|
+
ctx.temp = {}
|
|
81
|
+
return ctx
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
__all__ = ["_Ctx", "HandlerStep", "PhaseChains", "Request", "Session", "AsyncSession"]
|