tigrbl 0.0.1.dev1__py3-none-any.whl → 0.3.0__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 +97 -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 +291 -0
- tigrbl/app/__init__.py +0 -0
- tigrbl/app/_app.py +86 -0
- tigrbl/app/_model_registry.py +41 -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 +319 -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 +286 -0
- tigrbl/bindings/rest/common.py +120 -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/io_headers.py +49 -0
- tigrbl/bindings/rest/member.py +386 -0
- tigrbl/bindings/rest/router.py +296 -0
- tigrbl/bindings/rest/routing.py +153 -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 +136 -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 +45 -0
- tigrbl/engine/_engine.py +144 -0
- tigrbl/engine/bind.py +33 -0
- tigrbl/engine/builders.py +236 -0
- tigrbl/engine/capabilities.py +29 -0
- tigrbl/engine/collect.py +111 -0
- tigrbl/engine/decorators.py +110 -0
- tigrbl/engine/docs/PLUGINS.md +49 -0
- tigrbl/engine/engine_spec.py +355 -0
- tigrbl/engine/plugins.py +52 -0
- tigrbl/engine/registry.py +36 -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 +118 -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 +171 -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 +19 -0
- tigrbl/runtime/atoms/response/headers_from_payload.py +57 -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 +644 -0
- tigrbl/runtime/labels.py +353 -0
- tigrbl/runtime/opview.py +89 -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 +79 -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/session/README.md +14 -0
- tigrbl/session/__init__.py +28 -0
- tigrbl/session/abc.py +76 -0
- tigrbl/session/base.py +151 -0
- tigrbl/session/decorators.py +43 -0
- tigrbl/session/default.py +118 -0
- tigrbl/session/shortcuts.py +50 -0
- tigrbl/session/spec.py +112 -0
- tigrbl/shortcuts.py +22 -0
- tigrbl/specs.py +44 -0
- tigrbl/system/__init__.py +13 -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/system/uvicorn.py +60 -0
- tigrbl/table/__init__.py +9 -0
- tigrbl/table/_base.py +260 -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 +170 -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/types/uuid.py +55 -0
- tigrbl-0.3.0.dist-info/METADATA +516 -0
- tigrbl-0.3.0.dist-info/RECORD +266 -0
- {tigrbl-0.0.1.dev1.dist-info → tigrbl-0.3.0.dist-info}/WHEEL +1 -1
- tigrbl-0.3.0.dist-info/licenses/LICENSE +201 -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
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# tigrbl/v3/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"]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .utils import HTTPException, status
|
|
4
|
+
from .mappings import (
|
|
5
|
+
HTTP_ERROR_MESSAGES,
|
|
6
|
+
ERROR_MESSAGES,
|
|
7
|
+
_HTTP_TO_RPC,
|
|
8
|
+
_RPC_TO_HTTP,
|
|
9
|
+
)
|
|
10
|
+
from .converters import (
|
|
11
|
+
http_exc_to_rpc,
|
|
12
|
+
rpc_error_to_http,
|
|
13
|
+
_http_exc_to_rpc,
|
|
14
|
+
_rpc_error_to_http,
|
|
15
|
+
create_standardized_error,
|
|
16
|
+
create_standardized_error_from_status,
|
|
17
|
+
to_rpc_error_payload,
|
|
18
|
+
)
|
|
19
|
+
from .exceptions import (
|
|
20
|
+
TigrblError,
|
|
21
|
+
PlanningError,
|
|
22
|
+
LabelError,
|
|
23
|
+
ConfigError,
|
|
24
|
+
SystemStepError,
|
|
25
|
+
ValidationError,
|
|
26
|
+
TransformError,
|
|
27
|
+
DeriveError,
|
|
28
|
+
KernelAbort,
|
|
29
|
+
coerce_runtime_error,
|
|
30
|
+
raise_for_in_errors,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"HTTPException",
|
|
35
|
+
"status",
|
|
36
|
+
# maps & messages
|
|
37
|
+
"HTTP_ERROR_MESSAGES",
|
|
38
|
+
"ERROR_MESSAGES",
|
|
39
|
+
"_HTTP_TO_RPC",
|
|
40
|
+
"_RPC_TO_HTTP",
|
|
41
|
+
# conversions
|
|
42
|
+
"http_exc_to_rpc",
|
|
43
|
+
"rpc_error_to_http",
|
|
44
|
+
"_http_exc_to_rpc",
|
|
45
|
+
"_rpc_error_to_http",
|
|
46
|
+
"create_standardized_error",
|
|
47
|
+
"create_standardized_error_from_status",
|
|
48
|
+
"to_rpc_error_payload",
|
|
49
|
+
# typed errors + helpers
|
|
50
|
+
"TigrblError",
|
|
51
|
+
"PlanningError",
|
|
52
|
+
"LabelError",
|
|
53
|
+
"ConfigError",
|
|
54
|
+
"SystemStepError",
|
|
55
|
+
"ValidationError",
|
|
56
|
+
"TransformError",
|
|
57
|
+
"DeriveError",
|
|
58
|
+
"KernelAbort",
|
|
59
|
+
"coerce_runtime_error",
|
|
60
|
+
"raise_for_in_errors",
|
|
61
|
+
]
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Tuple
|
|
4
|
+
|
|
5
|
+
from .utils import (
|
|
6
|
+
HTTPException,
|
|
7
|
+
status,
|
|
8
|
+
PydanticValidationError,
|
|
9
|
+
RequestValidationError,
|
|
10
|
+
IntegrityError,
|
|
11
|
+
DBAPIError,
|
|
12
|
+
OperationalError,
|
|
13
|
+
NoResultFound,
|
|
14
|
+
_is_asyncpg_constraint_error,
|
|
15
|
+
_stringify_exc,
|
|
16
|
+
_format_validation,
|
|
17
|
+
)
|
|
18
|
+
from .exceptions import TigrblError
|
|
19
|
+
from .mappings import (
|
|
20
|
+
_HTTP_TO_RPC,
|
|
21
|
+
_RPC_TO_HTTP,
|
|
22
|
+
ERROR_MESSAGES,
|
|
23
|
+
HTTP_ERROR_MESSAGES,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def http_exc_to_rpc(exc: HTTPException) -> tuple[int, str, Any | None]:
|
|
28
|
+
"""Convert HTTPException → (rpc_code, message, data)."""
|
|
29
|
+
code = _HTTP_TO_RPC.get(exc.status_code, -32603)
|
|
30
|
+
detail = exc.detail
|
|
31
|
+
if isinstance(detail, (dict, list)):
|
|
32
|
+
return code, ERROR_MESSAGES.get(code, "Unknown error"), detail
|
|
33
|
+
msg = getattr(exc, "rpc_message", None) or (
|
|
34
|
+
detail if isinstance(detail, str) else None
|
|
35
|
+
)
|
|
36
|
+
if not msg:
|
|
37
|
+
msg = ERROR_MESSAGES.get(
|
|
38
|
+
code, HTTP_ERROR_MESSAGES.get(exc.status_code, "Unknown error")
|
|
39
|
+
)
|
|
40
|
+
data = getattr(exc, "rpc_data", None)
|
|
41
|
+
return code, msg, data
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def rpc_error_to_http(
|
|
45
|
+
rpc_code: int, message: str | None = None, data: Any | None = None
|
|
46
|
+
) -> HTTPException:
|
|
47
|
+
"""Convert JSON-RPC error code (and optional message/data) → HTTPException."""
|
|
48
|
+
http_status = _RPC_TO_HTTP.get(rpc_code, 500)
|
|
49
|
+
msg = (
|
|
50
|
+
message
|
|
51
|
+
or HTTP_ERROR_MESSAGES.get(http_status)
|
|
52
|
+
or ERROR_MESSAGES.get(rpc_code, "Unknown error")
|
|
53
|
+
)
|
|
54
|
+
http_exc = HTTPException(status_code=http_status, detail=msg)
|
|
55
|
+
setattr(http_exc, "rpc_code", rpc_code)
|
|
56
|
+
setattr(http_exc, "rpc_message", msg)
|
|
57
|
+
setattr(http_exc, "rpc_data", data)
|
|
58
|
+
return http_exc
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _http_exc_to_rpc(exc: HTTPException) -> tuple[int, str, Any | None]:
|
|
62
|
+
"""Alias for :func:`http_exc_to_rpc` to preserve older import paths."""
|
|
63
|
+
return http_exc_to_rpc(exc)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _rpc_error_to_http(
|
|
67
|
+
rpc_code: int, message: str | None = None, data: Any | None = None
|
|
68
|
+
) -> HTTPException:
|
|
69
|
+
"""Alias for :func:`rpc_error_to_http` to preserve older import paths."""
|
|
70
|
+
return rpc_error_to_http(rpc_code, message, data)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _classify_exception(
|
|
74
|
+
exc: BaseException,
|
|
75
|
+
) -> Tuple[int, str | dict | list, Any | None]:
|
|
76
|
+
"""
|
|
77
|
+
Return (http_status, detail_or_message, data) suitable for HTTPException and JSON-RPC mapping.
|
|
78
|
+
`detail_or_message` may be a string OR a structured dict/list (validation).
|
|
79
|
+
"""
|
|
80
|
+
# 0) Typed Tigrbl errors
|
|
81
|
+
if isinstance(exc, TigrblError):
|
|
82
|
+
status_code = getattr(exc, "status", 400) or 400
|
|
83
|
+
details = getattr(exc, "details", None)
|
|
84
|
+
if isinstance(details, (dict, list)):
|
|
85
|
+
return status_code, details, details
|
|
86
|
+
return status_code, str(exc) or exc.code, None
|
|
87
|
+
|
|
88
|
+
# 1) Pass-through HTTPException preserving detail
|
|
89
|
+
if isinstance(exc, HTTPException):
|
|
90
|
+
return exc.status_code, exc.detail, getattr(exc, "rpc_data", None)
|
|
91
|
+
|
|
92
|
+
# 2) Validation errors → 422 with structured data
|
|
93
|
+
if (PydanticValidationError is not None) and isinstance(
|
|
94
|
+
exc, PydanticValidationError
|
|
95
|
+
):
|
|
96
|
+
return (
|
|
97
|
+
status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
98
|
+
HTTP_ERROR_MESSAGES.get(422, "Validation failed"),
|
|
99
|
+
_format_validation(exc),
|
|
100
|
+
)
|
|
101
|
+
if (RequestValidationError is not None) and isinstance(exc, RequestValidationError):
|
|
102
|
+
return (
|
|
103
|
+
status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
104
|
+
HTTP_ERROR_MESSAGES.get(422, "Validation failed"),
|
|
105
|
+
_format_validation(exc),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# 3) Common client errors
|
|
109
|
+
if isinstance(exc, (ValueError, TypeError, KeyError)):
|
|
110
|
+
return status.HTTP_400_BAD_REQUEST, _stringify_exc(exc), None
|
|
111
|
+
if isinstance(exc, PermissionError):
|
|
112
|
+
return status.HTTP_403_FORBIDDEN, _stringify_exc(exc), None
|
|
113
|
+
if isinstance(exc, NotImplementedError):
|
|
114
|
+
return status.HTTP_501_NOT_IMPLEMENTED, _stringify_exc(exc), None
|
|
115
|
+
if isinstance(exc, TimeoutError):
|
|
116
|
+
return status.HTTP_504_GATEWAY_TIMEOUT, _stringify_exc(exc), None
|
|
117
|
+
|
|
118
|
+
# 4) ORM/DB mapping
|
|
119
|
+
if (NoResultFound is not None) and isinstance(exc, NoResultFound):
|
|
120
|
+
return status.HTTP_404_NOT_FOUND, "Resource not found", None
|
|
121
|
+
|
|
122
|
+
if _is_asyncpg_constraint_error(exc):
|
|
123
|
+
return status.HTTP_409_CONFLICT, _stringify_exc(exc), None
|
|
124
|
+
|
|
125
|
+
if (IntegrityError is not None) and isinstance(exc, IntegrityError):
|
|
126
|
+
msg = _stringify_exc(exc)
|
|
127
|
+
lower_msg = msg.lower()
|
|
128
|
+
if "not null constraint" in lower_msg or "check constraint" in lower_msg:
|
|
129
|
+
return status.HTTP_422_UNPROCESSABLE_ENTITY, msg, None
|
|
130
|
+
return status.HTTP_409_CONFLICT, msg, None
|
|
131
|
+
|
|
132
|
+
if (OperationalError is not None) and isinstance(exc, OperationalError):
|
|
133
|
+
return status.HTTP_503_SERVICE_UNAVAILABLE, _stringify_exc(exc), None
|
|
134
|
+
|
|
135
|
+
if (DBAPIError is not None) and isinstance(exc, DBAPIError):
|
|
136
|
+
return status.HTTP_500_INTERNAL_SERVER_ERROR, _stringify_exc(exc), None
|
|
137
|
+
|
|
138
|
+
# 5) Fallback
|
|
139
|
+
return status.HTTP_500_INTERNAL_SERVER_ERROR, _stringify_exc(exc), None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def create_standardized_error(exc: BaseException) -> HTTPException:
|
|
143
|
+
"""
|
|
144
|
+
Normalize any exception → HTTPException with attached RPC context:
|
|
145
|
+
• .rpc_code
|
|
146
|
+
• .rpc_message
|
|
147
|
+
• .rpc_data
|
|
148
|
+
"""
|
|
149
|
+
http_status, detail_or_message, data = _classify_exception(exc)
|
|
150
|
+
rpc_code = _HTTP_TO_RPC.get(http_status, -32603)
|
|
151
|
+
if isinstance(detail_or_message, (dict, list)):
|
|
152
|
+
http_detail = detail_or_message
|
|
153
|
+
rpc_message = ERROR_MESSAGES.get(
|
|
154
|
+
rpc_code, HTTP_ERROR_MESSAGES.get(http_status, "Unknown error")
|
|
155
|
+
)
|
|
156
|
+
else:
|
|
157
|
+
http_detail = detail_or_message
|
|
158
|
+
rpc_message = detail_or_message or ERROR_MESSAGES.get(
|
|
159
|
+
rpc_code, HTTP_ERROR_MESSAGES.get(http_status, "Unknown error")
|
|
160
|
+
)
|
|
161
|
+
http_exc = HTTPException(status_code=http_status, detail=http_detail)
|
|
162
|
+
setattr(http_exc, "rpc_code", rpc_code)
|
|
163
|
+
setattr(http_exc, "rpc_message", rpc_message)
|
|
164
|
+
setattr(http_exc, "rpc_data", data)
|
|
165
|
+
return http_exc
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def create_standardized_error_from_status(
|
|
169
|
+
http_status: int,
|
|
170
|
+
message: str | None = None,
|
|
171
|
+
*,
|
|
172
|
+
rpc_code: int | None = None,
|
|
173
|
+
data: Any | None = None,
|
|
174
|
+
) -> tuple[HTTPException, int, str]:
|
|
175
|
+
"""Explicit constructor used by code paths that already decided on an HTTP status."""
|
|
176
|
+
if rpc_code is None:
|
|
177
|
+
rpc_code = _HTTP_TO_RPC.get(http_status, -32603)
|
|
178
|
+
if message is None:
|
|
179
|
+
http_message = HTTP_ERROR_MESSAGES.get(http_status) or ERROR_MESSAGES.get(
|
|
180
|
+
rpc_code, "Unknown error"
|
|
181
|
+
)
|
|
182
|
+
rpc_message = ERROR_MESSAGES.get(rpc_code) or HTTP_ERROR_MESSAGES.get(
|
|
183
|
+
http_status, "Unknown error"
|
|
184
|
+
)
|
|
185
|
+
else:
|
|
186
|
+
http_message = rpc_message = message
|
|
187
|
+
http_exc = HTTPException(status_code=http_status, detail=http_message)
|
|
188
|
+
setattr(http_exc, "rpc_code", rpc_code)
|
|
189
|
+
setattr(http_exc, "rpc_message", rpc_message)
|
|
190
|
+
setattr(http_exc, "rpc_data", data)
|
|
191
|
+
return http_exc, rpc_code, rpc_message
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def to_rpc_error_payload(exc: HTTPException) -> dict:
|
|
195
|
+
"""Produce a JSON-RPC error object from an HTTPException (with or without rpc_* attrs)."""
|
|
196
|
+
code, msg, data = http_exc_to_rpc(exc)
|
|
197
|
+
payload = {"code": code, "message": msg}
|
|
198
|
+
if data is not None:
|
|
199
|
+
payload["data"] = data
|
|
200
|
+
else:
|
|
201
|
+
if isinstance(exc.detail, (dict, list)):
|
|
202
|
+
payload["data"] = exc.detail
|
|
203
|
+
return payload
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
__all__ = [
|
|
207
|
+
"http_exc_to_rpc",
|
|
208
|
+
"rpc_error_to_http",
|
|
209
|
+
"_http_exc_to_rpc",
|
|
210
|
+
"_rpc_error_to_http",
|
|
211
|
+
"create_standardized_error",
|
|
212
|
+
"create_standardized_error_from_status",
|
|
213
|
+
"to_rpc_error_payload",
|
|
214
|
+
]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from .utils import _read_in_errors, _has_in_errors
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TigrblError(Exception):
|
|
9
|
+
"""Base class for runtime errors in Tigrbl v3."""
|
|
10
|
+
|
|
11
|
+
code: str = "tigrbl_error"
|
|
12
|
+
status: int = 400
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
message: str = "",
|
|
17
|
+
*,
|
|
18
|
+
code: Optional[str] = None,
|
|
19
|
+
status: Optional[int] = None,
|
|
20
|
+
details: Any = None,
|
|
21
|
+
cause: Optional[BaseException] = None,
|
|
22
|
+
):
|
|
23
|
+
super().__init__(message)
|
|
24
|
+
if cause is not None:
|
|
25
|
+
self.__cause__ = cause
|
|
26
|
+
if code is not None:
|
|
27
|
+
self.code = code
|
|
28
|
+
if status is not None:
|
|
29
|
+
self.status = status
|
|
30
|
+
self.details = details
|
|
31
|
+
|
|
32
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
33
|
+
d = {
|
|
34
|
+
"type": self.__class__.__name__,
|
|
35
|
+
"code": self.code,
|
|
36
|
+
"status": self.status,
|
|
37
|
+
"message": str(self),
|
|
38
|
+
}
|
|
39
|
+
if self.details is not None:
|
|
40
|
+
d["details"] = self.details
|
|
41
|
+
return d
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class PlanningError(TigrblError):
|
|
45
|
+
code = "planning_error"
|
|
46
|
+
status = 500
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class LabelError(TigrblError):
|
|
50
|
+
code = "label_error"
|
|
51
|
+
status = 400
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ConfigError(TigrblError):
|
|
55
|
+
code = "config_error"
|
|
56
|
+
status = 400
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class SystemStepError(TigrblError):
|
|
60
|
+
code = "system_step_error"
|
|
61
|
+
status = 500
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ValidationError(TigrblError):
|
|
65
|
+
code = "validation_error"
|
|
66
|
+
status = 422
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def from_ctx(
|
|
70
|
+
ctx: Any, message: str = "Input validation failed."
|
|
71
|
+
) -> "ValidationError":
|
|
72
|
+
return ValidationError(message, status=422, details=_read_in_errors(ctx))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class TransformError(TigrblError):
|
|
76
|
+
code = "transform_error"
|
|
77
|
+
status = 400
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class DeriveError(TigrblError):
|
|
81
|
+
code = "derive_error"
|
|
82
|
+
status = 400
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class KernelAbort(TigrblError):
|
|
86
|
+
code = "kernel_abort"
|
|
87
|
+
status = 403
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def coerce_runtime_error(exc: BaseException, ctx: Any | None = None) -> TigrblError:
|
|
91
|
+
"""
|
|
92
|
+
Map arbitrary exceptions to a typed TigrblError for consistent kernel handling.
|
|
93
|
+
- Already TigrblError → return as-is
|
|
94
|
+
- ValueError + ctx.temp['in_errors'] → ValidationError
|
|
95
|
+
- Otherwise → generic TigrblError
|
|
96
|
+
"""
|
|
97
|
+
if isinstance(exc, TigrblError):
|
|
98
|
+
return exc
|
|
99
|
+
if isinstance(exc, ValueError) and ctx is not None and _has_in_errors(ctx):
|
|
100
|
+
return ValidationError.from_ctx(
|
|
101
|
+
ctx, message=str(exc) or "Input validation failed."
|
|
102
|
+
)
|
|
103
|
+
return TigrblError(str(exc) or exc.__class__.__name__)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def raise_for_in_errors(ctx: Any) -> None:
|
|
107
|
+
"""Raise a typed ValidationError if ctx.temp['in_errors'] indicates invalid input."""
|
|
108
|
+
if _has_in_errors(ctx):
|
|
109
|
+
raise ValidationError.from_ctx(ctx)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
__all__ = [
|
|
113
|
+
"TigrblError",
|
|
114
|
+
"PlanningError",
|
|
115
|
+
"LabelError",
|
|
116
|
+
"ConfigError",
|
|
117
|
+
"SystemStepError",
|
|
118
|
+
"ValidationError",
|
|
119
|
+
"TransformError",
|
|
120
|
+
"DeriveError",
|
|
121
|
+
"KernelAbort",
|
|
122
|
+
"coerce_runtime_error",
|
|
123
|
+
"raise_for_in_errors",
|
|
124
|
+
]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
# HTTP → JSON-RPC code map
|
|
4
|
+
_HTTP_TO_RPC: dict[int, int] = {
|
|
5
|
+
400: -32602,
|
|
6
|
+
401: -32001,
|
|
7
|
+
403: -32002,
|
|
8
|
+
404: -32003,
|
|
9
|
+
409: -32004,
|
|
10
|
+
422: -32602,
|
|
11
|
+
500: -32603,
|
|
12
|
+
501: -32603,
|
|
13
|
+
503: -32603,
|
|
14
|
+
504: -32603,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# JSON-RPC → HTTP status map
|
|
18
|
+
_RPC_TO_HTTP: dict[int, int] = {
|
|
19
|
+
-32700: 400,
|
|
20
|
+
-32600: 400,
|
|
21
|
+
-32601: 404,
|
|
22
|
+
-32602: 400,
|
|
23
|
+
-32603: 500,
|
|
24
|
+
-32001: 401,
|
|
25
|
+
-32002: 403,
|
|
26
|
+
-32003: 404,
|
|
27
|
+
-32004: 409,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Standardized error messages
|
|
31
|
+
ERROR_MESSAGES: dict[int, str] = {
|
|
32
|
+
-32700: "Parse error",
|
|
33
|
+
-32600: "Invalid Request",
|
|
34
|
+
-32601: "Method not found",
|
|
35
|
+
-32602: "Invalid params",
|
|
36
|
+
-32603: "Internal error",
|
|
37
|
+
-32001: "Authentication required",
|
|
38
|
+
-32002: "Insufficient permissions",
|
|
39
|
+
-32003: "Resource not found",
|
|
40
|
+
-32004: "Resource conflict",
|
|
41
|
+
-32000: "Server error",
|
|
42
|
+
-32099: "Duplicate key constraint violation",
|
|
43
|
+
-32098: "Data constraint violation",
|
|
44
|
+
-32097: "Foreign key constraint violation",
|
|
45
|
+
-32096: "Authentication required",
|
|
46
|
+
-32095: "Authorization failed",
|
|
47
|
+
-32094: "Resource not found",
|
|
48
|
+
-32093: "Validation error",
|
|
49
|
+
-32092: "Transaction failed",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# HTTP status code → standardized message
|
|
53
|
+
HTTP_ERROR_MESSAGES: dict[int, str] = {
|
|
54
|
+
400: "Bad Request: malformed input",
|
|
55
|
+
401: "Unauthorized: authentication required",
|
|
56
|
+
403: "Forbidden: insufficient permissions",
|
|
57
|
+
404: "Not Found: resource does not exist",
|
|
58
|
+
409: "Conflict: duplicate key or constraint violation",
|
|
59
|
+
422: "Unprocessable Entity: validation failed",
|
|
60
|
+
500: "Internal Server Error: unexpected server error",
|
|
61
|
+
501: "Not Implemented",
|
|
62
|
+
503: "Service Unavailable",
|
|
63
|
+
504: "Gateway Timeout",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
__all__ = [
|
|
67
|
+
"_HTTP_TO_RPC",
|
|
68
|
+
"_RPC_TO_HTTP",
|
|
69
|
+
"ERROR_MESSAGES",
|
|
70
|
+
"HTTP_ERROR_MESSAGES",
|
|
71
|
+
]
|