tigrbl-runtime 0.1.0.dev1__tar.gz
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_runtime-0.1.0.dev1/PKG-INFO +50 -0
- tigrbl_runtime-0.1.0.dev1/README.md +29 -0
- tigrbl_runtime-0.1.0.dev1/pyproject.toml +42 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/README.md +180 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/__init__.py +20 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/context.py +206 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/events.py +435 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/exceptions.py +18 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/executor/__init__.py +6 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/executor/guards.py +132 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/executor/helpers.py +194 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/executor/invoke.py +242 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/executor/types.py +128 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/gw/__init__.py +4 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/gw/invoke.py +103 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/gw/raw.py +26 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/hook_types.py +60 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/kernel.py +667 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/labels.py +369 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/opview.py +89 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/ordering.py +331 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/status/__init__.py +63 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/status/converters.py +222 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/status/exceptions.py +149 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/status/mappings.py +94 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/status/utils.py +114 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/system.py +338 -0
- tigrbl_runtime-0.1.0.dev1/tigrbl_runtime/runtime/trace.py +330 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tigrbl-runtime
|
|
3
|
+
Version: 0.1.0.dev1
|
|
4
|
+
Summary: Runtime pipeline and executor components for Tigrbl.
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Keywords: tigrbl,sdk,standards,framework
|
|
7
|
+
Author: Jacob Stewart
|
|
8
|
+
Author-email: jacob@swarmauri.com
|
|
9
|
+
Requires-Python: >=3.10,<3.13
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Development Status :: 1 - Planning
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Requires-Dist: tigrbl-kernel
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
# tigrbl-runtime
|
|
24
|
+
|
|
25
|
+
    
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- Modular package in the Tigrbl namespace.
|
|
30
|
+
- Supports Python 3.10 through 3.12.
|
|
31
|
+
- Distributed as part of the swarmauri-sdk workspace.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
### uv
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv add tigrbl-runtime
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### pip
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install tigrbl-runtime
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
Import from the shared package-specific module namespaces after installation in your environment.
|
|
50
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# tigrbl-runtime
|
|
4
|
+
|
|
5
|
+
    
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Modular package in the Tigrbl namespace.
|
|
10
|
+
- Supports Python 3.10 through 3.12.
|
|
11
|
+
- Distributed as part of the swarmauri-sdk workspace.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### uv
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uv add tigrbl-runtime
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### pip
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install tigrbl-runtime
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
Import from the shared package-specific module namespaces after installation in your environment.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tigrbl-runtime"
|
|
3
|
+
version = "0.1.0.dev1"
|
|
4
|
+
description = "Runtime pipeline and executor components for Tigrbl."
|
|
5
|
+
license = "Apache-2.0"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
repository = "http://github.com/swarmauri/swarmauri-sdk"
|
|
8
|
+
requires-python = ">=3.10,<3.13"
|
|
9
|
+
classifiers = [
|
|
10
|
+
"License :: OSI Approved :: Apache Software License",
|
|
11
|
+
"Development Status :: 1 - Planning",
|
|
12
|
+
"Programming Language :: Python :: 3.10",
|
|
13
|
+
"Programming Language :: Python :: 3.11",
|
|
14
|
+
"Programming Language :: Python :: 3.12",
|
|
15
|
+
"Programming Language :: Python",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
18
|
+
]
|
|
19
|
+
authors = [{ name = "Jacob Stewart", email = "jacob@swarmauri.com" }]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"tigrbl-kernel",
|
|
22
|
+
]
|
|
23
|
+
keywords = ["tigrbl", "sdk", "standards", "framework"]
|
|
24
|
+
|
|
25
|
+
[tool.uv.sources]
|
|
26
|
+
"tigrbl-kernel" = { workspace = true }
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["poetry-core>=1.0.0"]
|
|
30
|
+
build-backend = "poetry.core.masonry.api"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
[tool.poetry]
|
|
34
|
+
packages = [
|
|
35
|
+
{ include = "tigrbl_runtime" },
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[dependency-groups]
|
|
39
|
+
dev = [
|
|
40
|
+
"pytest>=8.0",
|
|
41
|
+
"ruff>=0.9",
|
|
42
|
+
]
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Runtime Execution Module (v3)
|
|
2
|
+
|
|
3
|
+
> **Maintainer-only:** This module is internal to the SDK. Downstream users **must not** modify or rely on it directly.
|
|
4
|
+
|
|
5
|
+
The runtime executor coordinates Tigrbl operations through a fixed set of **phase chains**. Each phase has a list of steps built by the kernel and is executed under strict database guards.
|
|
6
|
+
|
|
7
|
+
## Kernel Architecture Form
|
|
8
|
+
|
|
9
|
+
Tigrbl's runtime kernel follows a **compiled spec form with predicate-gated
|
|
10
|
+
phase-chain execution**. It is not imperative request handling and not purely
|
|
11
|
+
declarative configuration.
|
|
12
|
+
|
|
13
|
+
The kernel form is:
|
|
14
|
+
|
|
15
|
+
`Spec → Normalization → Compilation → KernelPlan → PhaseChain execution over GwRawEnvelope`
|
|
16
|
+
|
|
17
|
+
1. `AppSpec` is normalized into `NormalizedSpec`.
|
|
18
|
+
2. `NormalizedSpec` is compiled once into a deterministic `KernelPlan`.
|
|
19
|
+
3. `Executor(plan).invoke(env)` executes ordered phase chains over a raw
|
|
20
|
+
transport envelope.
|
|
21
|
+
|
|
22
|
+
At runtime, the executor behaves like a compiled plan engine:
|
|
23
|
+
|
|
24
|
+
- compile once and cache the plan,
|
|
25
|
+
- run ingress chain,
|
|
26
|
+
- resolve an operation key by indexed protocol lookup,
|
|
27
|
+
- run operation chain,
|
|
28
|
+
- run egress chain.
|
|
29
|
+
|
|
30
|
+
Dispatch is index-based (`proto_indices[proto][route_key] -> opkey`), not
|
|
31
|
+
router traversal. Operation metadata (`opmeta[opkey]`) drives deterministic
|
|
32
|
+
execution.
|
|
33
|
+
|
|
34
|
+
### Canonical KernelPlan Shape
|
|
35
|
+
|
|
36
|
+
Conceptually, the compiled plan contains:
|
|
37
|
+
|
|
38
|
+
- `proto_indices` – protocol-to-route hash maps for O(1) dispatch,
|
|
39
|
+
- `opmeta` – operation metadata keyed by operation key,
|
|
40
|
+
- `ingress_chain` – ordered atoms before operation execution,
|
|
41
|
+
- `op_chains` – per-operation ordered atoms,
|
|
42
|
+
- `egress_chain` – ordered atoms after operation execution.
|
|
43
|
+
|
|
44
|
+
Atoms are small execution units with optional predicates. Predicate checks gate
|
|
45
|
+
execution without introducing heuristic branching in the executor.
|
|
46
|
+
|
|
47
|
+
### Classification
|
|
48
|
+
|
|
49
|
+
In systems terms, the runtime kernel is a deterministic, compiled,
|
|
50
|
+
phase-oriented dispatch engine derived from normalized application
|
|
51
|
+
specification. This gives:
|
|
52
|
+
|
|
53
|
+
- compile-once / execute-many behavior,
|
|
54
|
+
- deterministic and inspectable plans,
|
|
55
|
+
- protocol abstraction over a raw envelope,
|
|
56
|
+
- phase-level security and diagnostics injection,
|
|
57
|
+
- consistent phase ordering.
|
|
58
|
+
|
|
59
|
+
## Phase Chains
|
|
60
|
+
|
|
61
|
+
Phase chains map phase names to ordered handler lists. The executor runs the phases in the sequence below:
|
|
62
|
+
|
|
63
|
+
1. `PRE_TX_BEGIN` – pre-transaction checks.
|
|
64
|
+
2. `START_TX` – open a new transaction (system-only).
|
|
65
|
+
3. `PRE_HANDLER` – request validation and setup.
|
|
66
|
+
4. `HANDLER` – core operation logic.
|
|
67
|
+
5. `POST_HANDLER` – post-processing while still in the transaction.
|
|
68
|
+
6. `PRE_COMMIT` – final checks before committing.
|
|
69
|
+
7. `END_TX` – commit and close the transaction.
|
|
70
|
+
8. `POST_COMMIT` – steps after commit.
|
|
71
|
+
9. `POST_RESPONSE` – fire-and-forget side effects.
|
|
72
|
+
|
|
73
|
+
## Step Kinds
|
|
74
|
+
|
|
75
|
+
The kernel labels every piece of work so it can be ordered predictably:
|
|
76
|
+
|
|
77
|
+
- **secdeps** – security dependencies that run before any other steps. Downstream
|
|
78
|
+
applications configure these to enforce authentication or authorization.
|
|
79
|
+
- **deps** – general dependencies resolved ahead of handlers. These are also
|
|
80
|
+
provided downstream.
|
|
81
|
+
- **sys** – system steps shipped with Tigrbl to coordinate core behavior. They
|
|
82
|
+
are maintained by project maintainers.
|
|
83
|
+
- **atoms** – built-in runtime units such as schema collectors or wire
|
|
84
|
+
serializers. Maintainers own these components.
|
|
85
|
+
- **hooks** – user-supplied handlers that attach to anchors within phases.
|
|
86
|
+
|
|
87
|
+
Downstream consumers configure `secdeps`, `deps`, and `hooks`, while `sys` and
|
|
88
|
+
`atom` steps are maintained by the Tigrbl maintainers.
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
## Step Precedence
|
|
92
|
+
|
|
93
|
+
When the kernel assembles an operation it flattens several step kinds into a
|
|
94
|
+
single execution plan. They run in the following precedence:
|
|
95
|
+
|
|
96
|
+
1. Security dependencies (`secdeps`)
|
|
97
|
+
2. General dependencies (`deps`)
|
|
98
|
+
3. System steps (`sys`) such as transaction begin, handler dispatch, and commit
|
|
99
|
+
4. Runtime atoms (`atoms`)
|
|
100
|
+
5. Hooks (`hooks`)
|
|
101
|
+
|
|
102
|
+
System steps appear only on the `START_TX`, `HANDLER`, and `END_TX` anchors. Within
|
|
103
|
+
each anchor, atoms execute before hooks and any remaining ties are resolved by
|
|
104
|
+
anchor-specific preferences.
|
|
105
|
+
|
|
106
|
+
## Atom Domains
|
|
107
|
+
|
|
108
|
+
Atoms are grouped into domain-specific registries so the kernel can inject them
|
|
109
|
+
at the correct stage of the lifecycle. Each domain focuses on a different slice
|
|
110
|
+
of request or response processing:
|
|
111
|
+
|
|
112
|
+
- **wire** – builds inbound data, validates it, and prepares outbound payloads at
|
|
113
|
+
the field level.
|
|
114
|
+
- **schema** – collects request and response schema definitions for models.
|
|
115
|
+
- **resolve** – assembles derived values or generates paired inputs before they
|
|
116
|
+
are flushed.
|
|
117
|
+
- **storage** – converts field specifications into storage-layer instructions.
|
|
118
|
+
- **emit** – surfaces runtime metadata such as aliases or extras for downstream
|
|
119
|
+
consumers.
|
|
120
|
+
- **out** – mutates data after handlers run, for example masking fields before
|
|
121
|
+
serialization.
|
|
122
|
+
- **response** – negotiates content types and renders the final HTTP response or
|
|
123
|
+
template.
|
|
124
|
+
- **refresh** – triggers post-commit refreshes like demand-driven reloads.
|
|
125
|
+
|
|
126
|
+
Domains differ by the moment they run and the guarantees they provide. A `wire`
|
|
127
|
+
atom transforms raw request values before validation, whereas a `response` atom
|
|
128
|
+
operates after the transaction is committed to shape the returned payload. The
|
|
129
|
+
kernel uses the pair `(domain, subject)` to register and inject atoms into phase
|
|
130
|
+
chains.
|
|
131
|
+
|
|
132
|
+
## DB Guards
|
|
133
|
+
|
|
134
|
+
For every phase the executor installs database guards that monkey‑patch
|
|
135
|
+
`commit` and `flush` on the session. Guards enforce which operations are
|
|
136
|
+
allowed and ensure only the owning transaction may commit.
|
|
137
|
+
|
|
138
|
+
The guard installer swaps these methods with stubs that raise
|
|
139
|
+
`RuntimeError` when a disallowed operation is attempted. Each phase passes
|
|
140
|
+
flags describing its policy:
|
|
141
|
+
|
|
142
|
+
- `allow_flush` – permit calls to `session.flush`.
|
|
143
|
+
- `allow_commit` – permit calls to `session.commit`.
|
|
144
|
+
- `require_owned_tx_for_commit` – when `True`, block commits if the
|
|
145
|
+
executor did not open the transaction.
|
|
146
|
+
|
|
147
|
+
The installer returns a handle that restores the original methods once the
|
|
148
|
+
phase finishes so restrictions do not leak across phases. A companion
|
|
149
|
+
helper triggers a rollback if the runtime owns the transaction and a phase
|
|
150
|
+
raises an error.
|
|
151
|
+
|
|
152
|
+
| Phase | Flush | Commit | Notes |
|
|
153
|
+
|-------|-------|--------|-------|
|
|
154
|
+
| PRE_TX_BEGIN | ❌ | ❌ | no database writes |
|
|
155
|
+
| START_TX | ❌ | ❌ | transaction opening |
|
|
156
|
+
| PRE_HANDLER | ✅ | ❌ | writes allowed, commit blocked |
|
|
157
|
+
| HANDLER | ✅ | ❌ | writes allowed, commit blocked |
|
|
158
|
+
| POST_HANDLER | ✅ | ❌ | writes allowed, commit blocked |
|
|
159
|
+
| PRE_COMMIT | ❌ | ❌ | freeze writes before commit |
|
|
160
|
+
| END_TX | ✅ | ✅ | commit allowed only if runtime owns the transaction |
|
|
161
|
+
| POST_COMMIT | ✅ | ❌ | post-commit writes without commit |
|
|
162
|
+
| POST_RESPONSE | ❌ | ❌ | background work, no writes |
|
|
163
|
+
|
|
164
|
+
### Transaction Boundaries
|
|
165
|
+
|
|
166
|
+
`start_tx` is a system step that opens a new database transaction when
|
|
167
|
+
no transaction is active and marks the runtime as its owner. While this
|
|
168
|
+
phase runs, both `session.flush` and `session.commit` are blocked. After a
|
|
169
|
+
transaction is started, phases such as `PRE_HANDLER`, `HANDLER`, and
|
|
170
|
+
`POST_HANDLER` allow flushes so SQL statements can be issued while the
|
|
171
|
+
commit remains deferred. The `end_tx` step executes during the `END_TX`
|
|
172
|
+
phase, performing a final flush and committing the transaction if the
|
|
173
|
+
runtime owns it. Once this phase completes, guards restore the original
|
|
174
|
+
session methods.
|
|
175
|
+
|
|
176
|
+
If a phase fails, the guard restores the original methods and the executor rolls back when it owns the transaction. Optional `ON_<PHASE>_ERROR` chains can handle cleanup.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
This runtime layer is maintained by the core team. Downstream packages should treat it as read‑only and interact only through the public Tigrbl interfaces.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# tigrbl/runtime/__init__.py
|
|
2
|
+
from .executor import _invoke, _Ctx
|
|
3
|
+
from .kernel import Kernel, build_phase_chains, run, get_cached_specs, _default_kernel
|
|
4
|
+
from . import events, status, context
|
|
5
|
+
from .labels import STEP_KINDS, DOMAINS
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"_invoke",
|
|
9
|
+
"_Ctx",
|
|
10
|
+
"Kernel",
|
|
11
|
+
"build_phase_chains",
|
|
12
|
+
"run",
|
|
13
|
+
"get_cached_specs",
|
|
14
|
+
"_default_kernel",
|
|
15
|
+
"events",
|
|
16
|
+
"status",
|
|
17
|
+
"context",
|
|
18
|
+
"STEP_KINDS",
|
|
19
|
+
"DOMAINS",
|
|
20
|
+
]
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# tigrbl/runtime/context.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import datetime as _dt
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, Dict, Mapping, Optional, Sequence
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _canon_op(op: Optional[str]) -> str:
|
|
10
|
+
return (op or "").strip().lower() or "unknown"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class Context:
|
|
15
|
+
"""
|
|
16
|
+
Canonical runtime context shared by the kernel and atoms.
|
|
17
|
+
|
|
18
|
+
Minimal contract (consumed by atoms we’ve written so far):
|
|
19
|
+
- op: operation name (e.g., 'create' | 'update' | 'read' | 'list' | custom)
|
|
20
|
+
- persist: write vs. read (affects pruning of persist-tied anchors)
|
|
21
|
+
- specs: mapping of field -> ColumnSpec (frozen at bind time)
|
|
22
|
+
- cfg: read-only config view (see config.resolver.CfgView)
|
|
23
|
+
- temp: dict scratchpad used by atoms to exchange data
|
|
24
|
+
|
|
25
|
+
Optional adapter slots:
|
|
26
|
+
- model: owning model type / class
|
|
27
|
+
- obj: hydrated ORM instance (if any)
|
|
28
|
+
- session: DB session / unit-of-work handle
|
|
29
|
+
- user, tenant, now: identity/time hints
|
|
30
|
+
- row/values/current_values: mapping fallbacks (for read paths)
|
|
31
|
+
- in_data / payload / data / body: inbound payload staging (for build_in)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# core
|
|
35
|
+
op: str
|
|
36
|
+
persist: bool
|
|
37
|
+
specs: Mapping[str, Any]
|
|
38
|
+
cfg: Any
|
|
39
|
+
|
|
40
|
+
# shared scratchpad
|
|
41
|
+
temp: Dict[str, Any] = field(default_factory=dict)
|
|
42
|
+
|
|
43
|
+
# optional context
|
|
44
|
+
model: Any | None = None
|
|
45
|
+
obj: Any | None = None
|
|
46
|
+
session: Any | None = None
|
|
47
|
+
|
|
48
|
+
# identity/time
|
|
49
|
+
user: Any | None = None
|
|
50
|
+
tenant: Any | None = None
|
|
51
|
+
now: _dt.datetime | None = None
|
|
52
|
+
|
|
53
|
+
# read-path fallbacks
|
|
54
|
+
row: Mapping[str, Any] | None = None
|
|
55
|
+
values: Mapping[str, Any] | None = None
|
|
56
|
+
current_values: Mapping[str, Any] | None = None
|
|
57
|
+
|
|
58
|
+
# inbound staging (router/adapters may set any one of these)
|
|
59
|
+
in_data: Any | None = None
|
|
60
|
+
payload: Any | None = None
|
|
61
|
+
data: Any | None = None
|
|
62
|
+
body: Any | None = None
|
|
63
|
+
|
|
64
|
+
def __post_init__(self) -> None:
|
|
65
|
+
self.op = _canon_op(self.op)
|
|
66
|
+
# Normalize now to a timezone-aware UTC timestamp when not provided
|
|
67
|
+
if self.now is None:
|
|
68
|
+
try:
|
|
69
|
+
self.now = _dt.datetime.now(_dt.timezone.utc)
|
|
70
|
+
except Exception: # pragma: no cover
|
|
71
|
+
self.now = _dt.datetime.utcnow().replace(tzinfo=None)
|
|
72
|
+
|
|
73
|
+
# Ensure temp is a dict (atoms rely on it)
|
|
74
|
+
if not isinstance(self.temp, dict):
|
|
75
|
+
self.temp = dict(self.temp)
|
|
76
|
+
|
|
77
|
+
# ── convenience flags ─────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def is_write(self) -> bool:
|
|
81
|
+
"""Alias for persist; reads better in some call sites."""
|
|
82
|
+
return bool(self.persist)
|
|
83
|
+
|
|
84
|
+
# ── safe read-only view for user callables (generators, default_factory) ──
|
|
85
|
+
|
|
86
|
+
def safe_view(
|
|
87
|
+
self,
|
|
88
|
+
*,
|
|
89
|
+
include_temp: bool = False,
|
|
90
|
+
temp_keys: Optional[Sequence[str]] = None,
|
|
91
|
+
) -> Mapping[str, Any]:
|
|
92
|
+
"""
|
|
93
|
+
Return a small, read-only mapping exposing only safe, frequently useful keys.
|
|
94
|
+
|
|
95
|
+
By default, temp is NOT included (to avoid leaking internals like paired raw values).
|
|
96
|
+
If include_temp=True, only exposes the keys listed in 'temp_keys' (if provided),
|
|
97
|
+
otherwise exposes a conservative subset.
|
|
98
|
+
|
|
99
|
+
This method is intended to be passed into author callables such as
|
|
100
|
+
default_factory(ctx_view) or paired token generators.
|
|
101
|
+
"""
|
|
102
|
+
base = {
|
|
103
|
+
"op": self.op,
|
|
104
|
+
"persist": self.persist,
|
|
105
|
+
"model": self.model,
|
|
106
|
+
"specs": self.specs,
|
|
107
|
+
"user": self.user,
|
|
108
|
+
"tenant": self.tenant,
|
|
109
|
+
"now": self.now,
|
|
110
|
+
}
|
|
111
|
+
if include_temp:
|
|
112
|
+
allowed = set(temp_keys or ("assembled_values", "virtual_in"))
|
|
113
|
+
exposed: Dict[str, Any] = {}
|
|
114
|
+
for k in allowed:
|
|
115
|
+
if k in self.temp:
|
|
116
|
+
exposed[k] = self.temp[k]
|
|
117
|
+
base = {**base, "temp": MappingProxy(exposed)}
|
|
118
|
+
return MappingProxy(base)
|
|
119
|
+
|
|
120
|
+
# ── tiny helpers used by atoms / kernel ───────────────────────────────────
|
|
121
|
+
|
|
122
|
+
def mark_used_returning(self, value: bool = True) -> None:
|
|
123
|
+
"""Flag that DB RETURNING already hydrated values."""
|
|
124
|
+
self.temp["used_returning"] = bool(value)
|
|
125
|
+
|
|
126
|
+
def merge_hydrated_values(
|
|
127
|
+
self, mapping: Mapping[str, Any], *, replace: bool = False
|
|
128
|
+
) -> None:
|
|
129
|
+
"""
|
|
130
|
+
Save values hydrated from DB (RETURNING/refresh). If replace=False (default),
|
|
131
|
+
performs a shallow merge into any existing 'hydrated_values'.
|
|
132
|
+
"""
|
|
133
|
+
if not isinstance(mapping, Mapping):
|
|
134
|
+
return
|
|
135
|
+
hv = self.temp.get("hydrated_values")
|
|
136
|
+
if replace or not isinstance(hv, dict):
|
|
137
|
+
self.temp["hydrated_values"] = dict(mapping)
|
|
138
|
+
else:
|
|
139
|
+
hv.update(mapping)
|
|
140
|
+
|
|
141
|
+
def add_response_extras(
|
|
142
|
+
self, extras: Mapping[str, Any], *, overwrite: Optional[bool] = None
|
|
143
|
+
) -> Sequence[str]:
|
|
144
|
+
"""
|
|
145
|
+
Merge alias extras into temp['response_extras'].
|
|
146
|
+
Returns a tuple of conflicting keys that were skipped when overwrite=False.
|
|
147
|
+
"""
|
|
148
|
+
if not isinstance(extras, Mapping) or not extras:
|
|
149
|
+
return ()
|
|
150
|
+
buf = self.temp.get("response_extras")
|
|
151
|
+
if not isinstance(buf, dict):
|
|
152
|
+
buf = {}
|
|
153
|
+
self.temp["response_extras"] = buf
|
|
154
|
+
if overwrite is None:
|
|
155
|
+
# fall back to cfg; atoms call wire:dump to honor final overwrite policy
|
|
156
|
+
overwrite = bool(getattr(self.cfg, "response_extras_overwrite", False))
|
|
157
|
+
conflicts: list[str] = []
|
|
158
|
+
for k, v in extras.items():
|
|
159
|
+
if (k in buf) and not overwrite:
|
|
160
|
+
conflicts.append(k)
|
|
161
|
+
continue
|
|
162
|
+
buf[k] = v
|
|
163
|
+
return tuple(conflicts)
|
|
164
|
+
|
|
165
|
+
def get_response_payload(self) -> Any:
|
|
166
|
+
"""Return the payload assembled by wire:dump (or None if not yet available)."""
|
|
167
|
+
return self.temp.get("response_payload")
|
|
168
|
+
|
|
169
|
+
# ── representation (avoid leaking large/sensitive temp contents) ──────────
|
|
170
|
+
|
|
171
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
172
|
+
model_name = getattr(self.model, "__name__", None) or str(self.model)
|
|
173
|
+
return (
|
|
174
|
+
f"Context(op={self.op!r}, persist={self.persist}, model={model_name!r}, "
|
|
175
|
+
f"user={(getattr(self.user, 'id', None) or None)!r}, temp_keys={sorted(self.temp.keys())})"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# ── tiny immutable mapping proxy (local; no external deps) ────────────────────
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class MappingProxy(Mapping[str, Any]):
|
|
183
|
+
"""A lightweight, read-only mapping wrapper."""
|
|
184
|
+
|
|
185
|
+
__slots__ = ("_d",)
|
|
186
|
+
|
|
187
|
+
def __init__(self, data: Mapping[str, Any]):
|
|
188
|
+
self._d = dict(data)
|
|
189
|
+
|
|
190
|
+
def __getitem__(self, k: str) -> Any:
|
|
191
|
+
return self._d[k]
|
|
192
|
+
|
|
193
|
+
def __iter__(self):
|
|
194
|
+
return iter(self._d)
|
|
195
|
+
|
|
196
|
+
def __len__(self) -> int:
|
|
197
|
+
return len(self._d)
|
|
198
|
+
|
|
199
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
200
|
+
return self._d.get(key, default)
|
|
201
|
+
|
|
202
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
203
|
+
return f"MappingProxy({self._d!r})"
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
__all__ = ["Context", "MappingProxy"]
|