tigrbl-atoms 0.1.0.dev5__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.
Files changed (89) hide show
  1. tigrbl_atoms-0.1.0.dev5/PKG-INFO +55 -0
  2. tigrbl_atoms-0.1.0.dev5/README.md +29 -0
  3. tigrbl_atoms-0.1.0.dev5/pyproject.toml +51 -0
  4. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/__init__.py +25 -0
  5. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/_ctx.py +23 -0
  6. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/_opview_helpers.py +161 -0
  7. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/_request.py +159 -0
  8. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/algebra.py +239 -0
  9. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/__init__.py +111 -0
  10. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/_temp.py +91 -0
  11. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/dep/__init__.py +15 -0
  12. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/dep/_param_resolver.py +96 -0
  13. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/dep/extra.py +121 -0
  14. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/dep/security.py +131 -0
  15. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/dispatch/__init__.py +19 -0
  16. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/dispatch/binding_match.py +166 -0
  17. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/dispatch/binding_parse.py +243 -0
  18. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/dispatch/input_normalize.py +63 -0
  19. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/dispatch/op_resolve.py +102 -0
  20. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/egress/__init__.py +48 -0
  21. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/egress/asgi_send.py +347 -0
  22. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/egress/envelope_apply.py +108 -0
  23. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/egress/headers_apply.py +65 -0
  24. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/egress/http_finalize.py +50 -0
  25. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/egress/out_dump.py +40 -0
  26. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/egress/result_normalize.py +44 -0
  27. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/egress/to_transport_response.py +183 -0
  28. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/emit/__init__.py +42 -0
  29. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/emit/paired_post.py +165 -0
  30. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/emit/paired_pre.py +120 -0
  31. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/emit/readtime_alias.py +133 -0
  32. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/err/__init__.py +3 -0
  33. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/err/rollback.py +29 -0
  34. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/ingress/__init__.py +35 -0
  35. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/ingress/ctx_init.py +34 -0
  36. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/ingress/input_prepare.py +50 -0
  37. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/ingress/transport_extract.py +190 -0
  38. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/out/__init__.py +38 -0
  39. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/out/masking.py +149 -0
  40. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/refresh/__init__.py +38 -0
  41. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/refresh/demand.py +144 -0
  42. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/resolve/__init__.py +40 -0
  43. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/resolve/assemble.py +164 -0
  44. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/resolve/paired_gen.py +163 -0
  45. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/response/__init__.py +23 -0
  46. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/response/error_to_transport.py +48 -0
  47. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/response/headers_from_payload.py +73 -0
  48. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/response/negotiate.py +44 -0
  49. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/response/negotiation.py +43 -0
  50. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/response/render.py +100 -0
  51. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/response/renderer.py +199 -0
  52. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/response/template.py +58 -0
  53. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/response/templates.py +90 -0
  54. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/schema/__init__.py +40 -0
  55. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/schema/collect_in.py +43 -0
  56. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/schema/collect_out.py +43 -0
  57. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/storage/__init__.py +38 -0
  58. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/storage/to_stored.py +181 -0
  59. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/__init__.py +79 -0
  60. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/_db.py +45 -0
  61. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/_oltp_context.py +214 -0
  62. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/commit_tx.py +49 -0
  63. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/handler_bulk_create.py +36 -0
  64. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/handler_bulk_delete.py +48 -0
  65. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/handler_bulk_merge.py +36 -0
  66. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/handler_bulk_replace.py +36 -0
  67. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/handler_bulk_update.py +36 -0
  68. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/handler_clear.py +33 -0
  69. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/handler_create.py +35 -0
  70. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/handler_custom.py +74 -0
  71. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/handler_delete.py +34 -0
  72. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/handler_list.py +104 -0
  73. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/handler_merge.py +35 -0
  74. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/handler_noop.py +38 -0
  75. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/handler_persistence.py +111 -0
  76. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/handler_read.py +34 -0
  77. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/handler_replace.py +35 -0
  78. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/handler_update.py +35 -0
  79. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/phase_db.py +157 -0
  80. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/sys/start_tx.py +49 -0
  81. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/wire/__init__.py +45 -0
  82. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/wire/build_in.py +350 -0
  83. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/wire/build_out.py +125 -0
  84. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/wire/dump.py +284 -0
  85. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/atoms/wire/validate_in.py +272 -0
  86. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/events.py +395 -0
  87. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/phases.py +234 -0
  88. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/stages.py +109 -0
  89. tigrbl_atoms-0.1.0.dev5/tigrbl_atoms/types.py +403 -0
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.4
2
+ Name: tigrbl-atoms
3
+ Version: 0.1.0.dev5
4
+ Summary: Runtime atom utilities and execution helpers 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: jinja2 (>=3.1)
19
+ Requires-Dist: sqlalchemy (>=2.0)
20
+ Requires-Dist: tigrbl-core
21
+ Requires-Dist: tigrbl-ops-oltp
22
+ Requires-Dist: tigrbl-typing
23
+ Requires-Dist: typing-extensions (>=4.0)
24
+ Description-Content-Type: text/markdown
25
+
26
+ ![Tigrbl branding](https://github.com/swarmauri/swarmauri-sdk/blob/a170683ecda8ca1c4f912c966d4499649ffb8224/assets/tigrbl.brand.theme.svg)
27
+
28
+ # tigrbl-atoms
29
+
30
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/tigrbl-atoms.svg) ![Hits](https://hits.sh/github.com/swarmauri/swarmauri-sdk.svg) ![Python Versions](https://img.shields.io/pypi/pyversions/tigrbl-atoms.svg) ![License](https://img.shields.io/pypi/l/tigrbl-atoms.svg) ![Version](https://img.shields.io/pypi/v/tigrbl-atoms.svg)
31
+
32
+ ## Features
33
+
34
+ - Modular package in the Tigrbl namespace.
35
+ - Supports Python 3.10 through 3.12.
36
+ - Distributed as part of the swarmauri-sdk workspace.
37
+
38
+ ## Installation
39
+
40
+ ### uv
41
+
42
+ ```bash
43
+ uv add tigrbl-atoms
44
+ ```
45
+
46
+ ### pip
47
+
48
+ ```bash
49
+ pip install tigrbl-atoms
50
+ ```
51
+
52
+ ## Usage
53
+
54
+ Import from the shared package-specific module namespaces after installation in your environment.
55
+
@@ -0,0 +1,29 @@
1
+ ![Tigrbl branding](https://github.com/swarmauri/swarmauri-sdk/blob/a170683ecda8ca1c4f912c966d4499649ffb8224/assets/tigrbl.brand.theme.svg)
2
+
3
+ # tigrbl-atoms
4
+
5
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/tigrbl-atoms.svg) ![Hits](https://hits.sh/github.com/swarmauri/swarmauri-sdk.svg) ![Python Versions](https://img.shields.io/pypi/pyversions/tigrbl-atoms.svg) ![License](https://img.shields.io/pypi/l/tigrbl-atoms.svg) ![Version](https://img.shields.io/pypi/v/tigrbl-atoms.svg)
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-atoms
19
+ ```
20
+
21
+ ### pip
22
+
23
+ ```bash
24
+ pip install tigrbl-atoms
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ Import from the shared package-specific module namespaces after installation in your environment.
@@ -0,0 +1,51 @@
1
+ [project]
2
+ name = "tigrbl-atoms"
3
+ version = "0.1.0.dev5"
4
+ description = "Runtime atom utilities and execution helpers 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-ops-oltp",
22
+ "tigrbl-core",
23
+ "tigrbl-typing",
24
+ "jinja2>=3.1",
25
+ "sqlalchemy>=2.0",
26
+ "typing-extensions>=4.0",
27
+ ]
28
+ keywords = ["tigrbl", "sdk", "standards", "framework"]
29
+
30
+ [tool.uv.sources]
31
+ "tigrbl-ops-oltp" = { workspace = true }
32
+ "tigrbl-core" = { workspace = true }
33
+ "tigrbl-typing" = { workspace = true }
34
+
35
+ [build-system]
36
+ requires = ["poetry-core>=1.0.0"]
37
+ build-backend = "poetry.core.masonry.api"
38
+
39
+
40
+ [tool.poetry]
41
+ packages = [
42
+ { include = "tigrbl_atoms" },
43
+ ]
44
+
45
+ [dependency-groups]
46
+ dev = [
47
+ "pytest>=8.0",
48
+ "pytest-asyncio>=0.24.0",
49
+ "pytest-timeout>=2.3.1",
50
+ "ruff>=0.9",
51
+ ]
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from .types import (
4
+ EGRESS_PHASES,
5
+ HANDLER_PHASES,
6
+ INGRESS_PHASES,
7
+ PHASE_SEQUENCE,
8
+ HookPhase,
9
+ HookPhases,
10
+ HookPredicate,
11
+ StepFn,
12
+ VALID_HOOK_PHASES,
13
+ )
14
+
15
+ __all__ = [
16
+ "PHASE_SEQUENCE",
17
+ "INGRESS_PHASES",
18
+ "HANDLER_PHASES",
19
+ "EGRESS_PHASES",
20
+ "HookPhase",
21
+ "HookPhases",
22
+ "VALID_HOOK_PHASES",
23
+ "StepFn",
24
+ "HookPredicate",
25
+ ]
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict
4
+
5
+
6
+ def _ctx_view(ctx: Any) -> Dict[str, Any]:
7
+ """Small read-only view for user callables."""
8
+ safe_view = getattr(ctx, "safe_view", None)
9
+ if callable(safe_view):
10
+ view = safe_view(include_temp=True)
11
+ return dict(view)
12
+
13
+ return {
14
+ "op": getattr(ctx, "op", None),
15
+ "persist": getattr(ctx, "persist", True),
16
+ "temp": getattr(ctx, "temp", None),
17
+ "tenant": getattr(ctx, "tenant", None),
18
+ "user": getattr(ctx, "user", None),
19
+ "now": getattr(ctx, "now", None),
20
+ }
21
+
22
+
23
+ __all__ = ["_ctx_view"]
@@ -0,0 +1,161 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from typing import Any
5
+
6
+
7
+ def _ensure_temp(ctx: Any) -> dict[str, Any]:
8
+ tmp = getattr(ctx, "temp", None)
9
+ if not isinstance(tmp, dict):
10
+ tmp = {}
11
+ setattr(ctx, "temp", tmp)
12
+ return tmp
13
+
14
+
15
+ def _ensure_ov(ctx: Any):
16
+ ov = getattr(ctx, "opview", None)
17
+ if ov is None:
18
+ raise RuntimeError("ctx_missing:opview")
19
+ return ov
20
+
21
+
22
+ def _normalize_schema_from_specs(ctx: Any) -> None:
23
+ specs = getattr(ctx, "specs", None)
24
+ op = getattr(ctx, "op", None)
25
+ if not isinstance(specs, Mapping) or not isinstance(op, str) or not op:
26
+ raise RuntimeError("ctx_missing:opview")
27
+
28
+ in_fields: list[str] = []
29
+ out_fields: list[str] = []
30
+ by_field_in: dict[str, dict[str, Any]] = {}
31
+ by_field_out: dict[str, dict[str, Any]] = {}
32
+
33
+ for field_name, spec in specs.items():
34
+ if not isinstance(field_name, str):
35
+ continue
36
+ io = getattr(spec, "io", None)
37
+ fs = getattr(spec, "field", None)
38
+ storage = getattr(spec, "storage", None)
39
+
40
+ in_verbs = set(getattr(io, "in_verbs", ()) or ())
41
+ out_verbs = set(getattr(io, "out_verbs", ()) or ())
42
+
43
+ if op in in_verbs:
44
+ in_fields.append(field_name)
45
+ in_meta: dict[str, Any] = {"in_enabled": True}
46
+ if storage is None:
47
+ in_meta["virtual"] = True
48
+
49
+ default_factory = getattr(spec, "default_factory", None)
50
+ if callable(default_factory):
51
+ in_meta["default_factory"] = default_factory
52
+
53
+ alias_in = getattr(io, "alias_in", None)
54
+ if alias_in:
55
+ in_meta["alias_in"] = alias_in
56
+
57
+ header_in = getattr(io, "header_in", None)
58
+ if header_in:
59
+ in_meta["header_in"] = header_in
60
+ in_meta["header_required_in"] = bool(
61
+ getattr(io, "header_required_in", False)
62
+ )
63
+
64
+ in_meta["required"] = bool(fs and op in getattr(fs, "required_in", ()))
65
+ in_meta["nullable"] = (
66
+ True if storage is None else bool(getattr(storage, "nullable", True))
67
+ )
68
+ by_field_in[field_name] = in_meta
69
+
70
+ if op in out_verbs:
71
+ out_fields.append(field_name)
72
+ out_meta: dict[str, Any] = {}
73
+
74
+ alias_out = getattr(io, "alias_out", None)
75
+ if alias_out:
76
+ out_meta["alias_out"] = alias_out
77
+ if storage is None:
78
+ out_meta["virtual"] = True
79
+
80
+ py_type = getattr(getattr(fs, "py_type", None), "__name__", None)
81
+ if py_type:
82
+ out_meta["py_type"] = py_type
83
+ by_field_out[field_name] = out_meta
84
+
85
+ in_fields_sorted = tuple(sorted(in_fields))
86
+ out_fields_sorted = tuple(sorted(out_fields))
87
+
88
+ setattr(
89
+ ctx,
90
+ "schema_in",
91
+ {
92
+ "fields": in_fields_sorted,
93
+ "by_field": {f: by_field_in.get(f, {}) for f in in_fields_sorted},
94
+ "required": tuple(
95
+ f for f in in_fields_sorted if by_field_in.get(f, {}).get("required")
96
+ ),
97
+ },
98
+ )
99
+ setattr(
100
+ ctx,
101
+ "schema_out",
102
+ {
103
+ "fields": out_fields_sorted,
104
+ "by_field": {f: by_field_out.get(f, {}) for f in out_fields_sorted},
105
+ "expose": out_fields_sorted,
106
+ },
107
+ )
108
+
109
+
110
+ def _ensure_schema_in(ctx: Any) -> Mapping[str, Any]:
111
+ temp = _ensure_temp(ctx)
112
+ cached = temp.get("schema_in")
113
+ if isinstance(cached, Mapping):
114
+ return cached
115
+
116
+ schema_in = getattr(ctx, "schema_in", None)
117
+ if isinstance(schema_in, Mapping):
118
+ temp["schema_in"] = schema_in
119
+ return schema_in
120
+
121
+ try:
122
+ ov = _ensure_ov(ctx)
123
+ bf = ov.schema_in.by_field
124
+ req = tuple(n for n, e in bf.items() if e.get("required"))
125
+ temp["schema_in"] = {
126
+ "fields": ov.schema_in.fields,
127
+ "by_field": bf,
128
+ "required": req,
129
+ }
130
+ except RuntimeError as exc:
131
+ if str(exc) != "ctx_missing:opview":
132
+ raise
133
+ _normalize_schema_from_specs(ctx)
134
+ temp["schema_in"] = getattr(ctx, "schema_in")
135
+ return temp["schema_in"]
136
+
137
+
138
+ def _ensure_schema_out(ctx: Any) -> Mapping[str, Any]:
139
+ temp = _ensure_temp(ctx)
140
+ cached = temp.get("schema_out")
141
+ if isinstance(cached, Mapping):
142
+ return cached
143
+
144
+ schema_out = getattr(ctx, "schema_out", None)
145
+ if isinstance(schema_out, Mapping):
146
+ temp["schema_out"] = schema_out
147
+ return schema_out
148
+
149
+ try:
150
+ ov = _ensure_ov(ctx)
151
+ temp["schema_out"] = {
152
+ "fields": ov.schema_out.fields,
153
+ "by_field": ov.schema_out.by_field,
154
+ "expose": ov.schema_out.expose,
155
+ }
156
+ except RuntimeError as exc:
157
+ if str(exc) != "ctx_missing:opview":
158
+ raise
159
+ _normalize_schema_from_specs(ctx)
160
+ temp["schema_out"] = getattr(ctx, "schema_out")
161
+ return temp["schema_out"]
@@ -0,0 +1,159 @@
1
+ from __future__ import annotations
2
+
3
+ import json as json_module
4
+ from dataclasses import dataclass, field
5
+ from http.cookies import SimpleCookie
6
+ from types import SimpleNamespace
7
+ from typing import Any
8
+ from collections.abc import Iterable, Mapping
9
+ from urllib.parse import parse_qs
10
+
11
+ from tigrbl_core._spec.request_spec import RequestSpec
12
+
13
+
14
+ from tigrbl_typing.request import AwaitableValue, URL
15
+
16
+
17
+ @dataclass(init=False)
18
+ class Request(RequestSpec):
19
+ method: str
20
+ path: str
21
+ headers: Mapping[str, str] | Iterable[tuple[str, str]]
22
+ query: dict[str, list[str]]
23
+ path_params: dict[str, str]
24
+ body: bytes
25
+ script_name: str = ""
26
+ app: Any | None = None
27
+ state: SimpleNamespace = field(default_factory=SimpleNamespace)
28
+ scope: dict[str, Any] = field(default_factory=dict)
29
+ _json_cache: Any = field(default=None, init=False, repr=False)
30
+ _json_loaded: bool = field(default=False, init=False, repr=False)
31
+
32
+ def __init__(
33
+ self,
34
+ method: str | dict[str, Any],
35
+ path: str | None = None,
36
+ headers: Mapping[str, str] | Iterable[tuple[str, str]] | None = None,
37
+ query: dict[str, list[str]] | None = None,
38
+ path_params: dict[str, str] | None = None,
39
+ body: bytes = b"",
40
+ script_name: str = "",
41
+ app: Any | None = None,
42
+ state: SimpleNamespace | None = None,
43
+ scope: dict[str, Any] | None = None,
44
+ receive: Any | None = None,
45
+ ) -> None:
46
+ del receive
47
+
48
+ self._json_cache = None
49
+ self._json_loaded = False
50
+
51
+ if isinstance(method, dict):
52
+ if scope is not None:
53
+ raise TypeError("scope cannot be provided when first argument is scope")
54
+ self._init_from_scope(method, app=app, state=state)
55
+ return
56
+
57
+ if path is None:
58
+ raise TypeError("path is required when constructing Request from fields")
59
+
60
+ self.method = method
61
+ self.path = path
62
+ self.headers = headers or {}
63
+ self.query = query or {}
64
+ self.path_params = path_params or {}
65
+ self.body = body
66
+ self.script_name = script_name
67
+ self.app = app
68
+ self.state = state or SimpleNamespace()
69
+ self.scope = scope or {}
70
+
71
+ def _init_from_scope(
72
+ self,
73
+ scope: dict[str, Any],
74
+ *,
75
+ app: Any | None,
76
+ state: SimpleNamespace | None,
77
+ ) -> None:
78
+ self.method = (scope.get("method") or "GET").upper()
79
+ self.path = scope.get("path") or "/"
80
+ self.headers = {
81
+ key.decode("latin-1").lower(): value.decode("latin-1")
82
+ for key, value in scope.get("headers", [])
83
+ }
84
+ self.query = parse_qs(
85
+ scope.get("query_string", b"").decode("latin-1"), keep_blank_values=True
86
+ )
87
+ self.path_params = scope.get("path_params") or {}
88
+ self.body = b""
89
+ self.script_name = scope.get("root_path") or ""
90
+ self.app = app
91
+ self.state = state or SimpleNamespace()
92
+ self.scope = scope
93
+
94
+ @classmethod
95
+ def from_scope(
96
+ cls,
97
+ scope: dict[str, Any],
98
+ receive: Any | None = None,
99
+ *,
100
+ app: Any | None = None,
101
+ state: SimpleNamespace | None = None,
102
+ ) -> "Request":
103
+ return cls(scope, app=app, state=state, receive=receive)
104
+
105
+ def json(self) -> AwaitableValue:
106
+ return AwaitableValue(self.json_sync())
107
+
108
+ def json_sync(self) -> Any:
109
+ if self._json_loaded:
110
+ return self._json_cache
111
+ if not self.body:
112
+ self._json_loaded = True
113
+ self._json_cache = None
114
+ return None
115
+ self._json_cache = json_module.loads(self.body.decode("utf-8"))
116
+ self._json_loaded = True
117
+ return self._json_cache
118
+
119
+ @property
120
+ def url(self) -> URL:
121
+ return URL(path=self.path, query=self.query, script_name=self.script_name)
122
+
123
+ @property
124
+ def query_params(self) -> dict[str, str]:
125
+ return {name: vals[0] for name, vals in self.query.items() if vals}
126
+
127
+ @property
128
+ def cookies(self) -> dict[str, str]:
129
+ raw = self.headers.get("cookie", "") or ""
130
+ parsed = SimpleCookie()
131
+ parsed.load(raw)
132
+ return {name: morsel.value for name, morsel in parsed.items()}
133
+
134
+ @property
135
+ def bearer_token(self) -> str | None:
136
+ authorization = self.headers.get("authorization", "") or ""
137
+ scheme, _, token = authorization.partition(" ")
138
+ cleaned = token.strip()
139
+ return cleaned if scheme.lower() == "bearer" and cleaned else None
140
+
141
+ @property
142
+ def admin_key(self) -> str | None:
143
+ key = (self.headers.get("x-admin-key") or "").strip()
144
+ return key or None
145
+
146
+ @property
147
+ def session_token(self) -> str | None:
148
+ return self.bearer_token or (self.cookies.get("sid") or "").strip() or None
149
+
150
+ @property
151
+ def client(self) -> SimpleNamespace:
152
+ host = ""
153
+ client = self.scope.get("client") if isinstance(self.scope, dict) else None
154
+ if isinstance(client, tuple) and client:
155
+ host = str(client[0])
156
+ return SimpleNamespace(ip=host, host=host)
157
+
158
+
159
+ __all__ = ["Request", "URL", "AwaitableValue"]