tigrbl-core 0.1.0.dev15__tar.gz → 0.1.11.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.
Files changed (54) hide show
  1. tigrbl_core-0.1.11.dev1/PKG-INFO +48 -0
  2. tigrbl_core-0.1.11.dev1/README.md +23 -0
  3. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/pyproject.toml +3 -3
  4. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/__init__.py +27 -0
  5. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/app_spec.py +22 -33
  6. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/binding_spec.py +56 -1
  7. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/column_spec.py +61 -3
  8. tigrbl_core-0.1.11.dev1/tigrbl_core/_spec/datatypes.py +536 -0
  9. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/hook_spec.py +7 -1
  10. tigrbl_core-0.1.11.dev1/tigrbl_core/_spec/monotone.py +155 -0
  11. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/op_spec.py +63 -6
  12. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/storage_spec.py +2 -0
  13. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/table_spec.py +22 -0
  14. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/config/constants.py +60 -0
  15. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/schema/__init__.py +14 -0
  16. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/schema/builder/build_schema.py +8 -5
  17. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/schema/builder/helpers.py +27 -1
  18. tigrbl_core-0.1.11.dev1/tigrbl_core/schema/spec_json.py +804 -0
  19. tigrbl_core-0.1.0.dev15/PKG-INFO +0 -54
  20. tigrbl_core-0.1.0.dev15/README.md +0 -29
  21. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/alias_spec.py +0 -0
  22. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/engine_spec.py +0 -0
  23. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/field_spec.py +0 -0
  24. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/hook_types.py +0 -0
  25. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/io_spec.py +0 -0
  26. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/middleware_spec.py +0 -0
  27. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/op_utils.py +0 -0
  28. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/plugins.py +0 -0
  29. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/registry.py +0 -0
  30. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/request_spec.py +0 -0
  31. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/response_resolver.py +0 -0
  32. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/response_spec.py +0 -0
  33. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/response_types.py +0 -0
  34. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/router_spec.py +0 -0
  35. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/schema_spec.py +0 -0
  36. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/serde.py +0 -0
  37. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/session_spec.py +0 -0
  38. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/_spec/table_registry_spec.py +0 -0
  39. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/config/__init__.py +0 -0
  40. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/config/defaults.py +0 -0
  41. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/config/engine_traversal.py +0 -0
  42. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/config/resolver.py +0 -0
  43. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/core/__init__.py +0 -0
  44. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/op/__init__.py +0 -0
  45. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/op/canonical.py +0 -0
  46. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/op/collect.py +0 -0
  47. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/op/types.py +0 -0
  48. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/schema/builder/__init__.py +0 -0
  49. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/schema/builder/cache.py +0 -0
  50. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/schema/builder/extras.py +0 -0
  51. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/schema/builder/list_params.py +0 -0
  52. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/schema/builder/strip_parent_fields.py +0 -0
  53. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/schema/get_schema.py +0 -0
  54. {tigrbl_core-0.1.0.dev15 → tigrbl_core-0.1.11.dev1}/tigrbl_core/schema/utils.py +0 -0
@@ -0,0 +1,48 @@
1
+ Metadata-Version: 2.4
2
+ Name: tigrbl-core
3
+ Version: 0.1.11.dev1
4
+ Summary: Core specifications and primitives for the Tigrbl framework.
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.14
10
+ Classifier: Development Status :: 1 - Planning
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Requires-Dist: pydantic (>=2.10,<3)
19
+ Requires-Dist: pyyaml
20
+ Requires-Dist: tigrbl-typing
21
+ Requires-Dist: tomli (>=2.0.1) ; python_version < "3.11"
22
+ Requires-Dist: tomli-w
23
+ Description-Content-Type: text/markdown
24
+
25
+ # tigrbl_core
26
+
27
+ This file is a package-local distribution entry point.
28
+ It is not the authoritative location for repository governance, current target status, current state reporting, certification claims, or release evidence.
29
+
30
+ ## Canonical repository docs
31
+
32
+ - `README.md`
33
+ - `docs/README.md`
34
+ - `docs/conformance/CURRENT_TARGET.md`
35
+ - `docs/conformance/CURRENT_STATE.md`
36
+ - `docs/conformance/NEXT_STEPS.md`
37
+ - `docs/governance/DOC_POINTERS.md`
38
+ - `docs/developer/PACKAGE_CATALOG.md`
39
+ - `docs/developer/PACKAGE_LAYOUT.md`
40
+
41
+ ## Package identity
42
+
43
+ - workspace path: `pkgs/core/tigrbl_core`
44
+ - workspace class: core Python package
45
+ - implementation layout: `tigrbl_core/`
46
+
47
+ Long-form repository documentation is governed from `docs/`.
48
+
@@ -0,0 +1,23 @@
1
+ # tigrbl_core
2
+
3
+ This file is a package-local distribution entry point.
4
+ It is not the authoritative location for repository governance, current target status, current state reporting, certification claims, or release evidence.
5
+
6
+ ## Canonical repository docs
7
+
8
+ - `README.md`
9
+ - `docs/README.md`
10
+ - `docs/conformance/CURRENT_TARGET.md`
11
+ - `docs/conformance/CURRENT_STATE.md`
12
+ - `docs/conformance/NEXT_STEPS.md`
13
+ - `docs/governance/DOC_POINTERS.md`
14
+ - `docs/developer/PACKAGE_CATALOG.md`
15
+ - `docs/developer/PACKAGE_LAYOUT.md`
16
+
17
+ ## Package identity
18
+
19
+ - workspace path: `pkgs/core/tigrbl_core`
20
+ - workspace class: core Python package
21
+ - implementation layout: `tigrbl_core/`
22
+
23
+ Long-form repository documentation is governed from `docs/`.
@@ -1,17 +1,17 @@
1
1
  [project]
2
2
  name = "tigrbl-core"
3
- version = "0.1.0.dev15"
3
+ version = "0.1.11.dev1"
4
4
  description = "Core specifications and primitives for the Tigrbl framework."
5
5
  license = "Apache-2.0"
6
6
  readme = "README.md"
7
7
  repository = "http://github.com/swarmauri/swarmauri-sdk"
8
- requires-python = ">=3.10,<3.13"
8
+ requires-python = ">=3.10,<3.14"
9
9
  classifiers = [
10
- "License :: OSI Approved :: Apache Software License",
11
10
  "Development Status :: 1 - Planning",
12
11
  "Programming Language :: Python :: 3.10",
13
12
  "Programming Language :: Python :: 3.11",
14
13
  "Programming Language :: Python :: 3.12",
14
+ "Programming Language :: Python :: 3.13",
15
15
  "Programming Language :: Python",
16
16
  "Programming Language :: Python :: 3",
17
17
  "Programming Language :: Python :: 3 :: Only",
@@ -13,24 +13,48 @@ _EXPORTS = {
13
13
  "AppSpec": "app_spec",
14
14
  "BindingSpec": "binding_spec",
15
15
  "BindingRegistrySpec": "binding_spec",
16
+ "Exchange": "binding_spec",
17
+ "Framing": "binding_spec",
16
18
  "TransportBindingSpec": "binding_spec",
17
19
  "HttpRestBindingSpec": "binding_spec",
18
20
  "HttpJsonRpcBindingSpec": "binding_spec",
21
+ "HttpStreamBindingSpec": "binding_spec",
22
+ "SseBindingSpec": "binding_spec",
23
+ "WebTransportBindingSpec": "binding_spec",
19
24
  "WsBindingSpec": "binding_spec",
20
25
  "resolve_rest_nested_prefix": "binding_spec",
21
26
  "ColumnSpec": "column_spec",
27
+ "CANONICAL_DATATYPES": "datatypes",
28
+ "DataTypeSpec": "datatypes",
22
29
  "EngineSpec": "engine_spec",
23
30
  "EngineProviderSpec": "engine_spec",
31
+ "EngineDatatypeBridge": "datatypes",
32
+ "EngineRegistry": "datatypes",
33
+ "EngineTypeLowerer": "datatypes",
24
34
  "FieldSpec": "field_spec",
25
35
  "F": "field_spec",
36
+ "BaseTypeAdapter": "datatypes",
26
37
  "HookSpec": "hook_spec",
27
38
  "IOSpec": "io_spec",
28
39
  "IO": "io_spec",
40
+ "ReflectedDatatype": "datatypes",
41
+ "ReflectedTypeMapper": "datatypes",
42
+ "StaticEngineLowerer": "datatypes",
43
+ "as_tuple": "monotone",
29
44
  "MiddlewareSpec": "middleware_spec",
45
+ "highest_precedence": "monotone",
46
+ "keyed_overlay": "monotone",
47
+ "mapping_overlay": "monotone",
48
+ "merge_mro_mapping_attr": "monotone",
49
+ "merge_mro_sequence_attr": "monotone",
30
50
  "OpSpec": "op_spec",
51
+ "sequence_union": "monotone",
52
+ "stable_keyed_overlay": "monotone",
53
+ "stable_unique": "monotone",
31
54
  "Arity": "op_spec",
32
55
  "TargetOp": "op_spec",
33
56
  "PersistPolicy": "op_spec",
57
+ "TxScope": "op_spec",
34
58
  "PHASE": "op_spec",
35
59
  "PHASES": "op_spec",
36
60
  "HookPhase": "hook_spec",
@@ -48,11 +72,14 @@ _EXPORTS = {
48
72
  "tx_repeatable_read": "session_spec",
49
73
  "tx_serializable": "session_spec",
50
74
  "readonly": "session_spec",
75
+ "StorageTypeRef": "datatypes",
51
76
  "StorageSpec": "storage_spec",
52
77
  "S": "storage_spec",
53
78
  "StorageTransformSpec": "storage_spec",
54
79
  "StorageTransform": "storage_spec",
55
80
  "ForeignKeySpec": "storage_spec",
81
+ "TypeAdapter": "datatypes",
82
+ "TypeRegistry": "datatypes",
56
83
  "TableSpec": "table_spec",
57
84
  "TableRegistrySpec": "table_registry_spec",
58
85
  }
@@ -1,9 +1,10 @@
1
1
  # pkgs/standards/tigrbl_core/tigrbl/_spec/app_spec.py
2
2
  from __future__ import annotations
3
3
  from dataclasses import dataclass, field
4
- from typing import Any, Callable, Iterable, Mapping, Optional, Sequence
4
+ from typing import Any, Callable, Optional, Sequence
5
5
 
6
6
  from .._spec.engine_spec import EngineCfg
7
+ from .._spec.monotone import as_tuple, merge_mro_sequence_attr
7
8
  from .._spec.response_spec import ResponseSpec
8
9
  from .serde import SerdeMixin
9
10
 
@@ -11,17 +12,7 @@ from .serde import SerdeMixin
11
12
  def _seqify(value: Any) -> tuple[Any, ...]:
12
13
  """Normalize sequence-like inputs while treating scalars as a single item."""
13
14
 
14
- if value is None:
15
- return ()
16
- if isinstance(value, tuple):
17
- return value
18
- if isinstance(value, (str, bytes, bytearray)):
19
- return (value,)
20
- if isinstance(value, Mapping):
21
- return (value,)
22
- if isinstance(value, Iterable):
23
- return tuple(value)
24
- return (value,)
15
+ return as_tuple(value)
25
16
 
26
17
 
27
18
  def merge_seq_attr(
@@ -34,27 +25,13 @@ def merge_seq_attr(
34
25
  ) -> tuple[Any, ...]:
35
26
  """Merge sequence-like class attributes over the MRO."""
36
27
 
37
- values: list[Any] = []
38
- seen_hashable: set[Any] = set()
39
- mro = reversed(owner.__mro__) if reverse else owner.__mro__
40
- for base in mro:
41
- if include_inherited:
42
- if not hasattr(base, attr):
43
- continue
44
- seq = getattr(base, attr) or ()
45
- else:
46
- seq = base.__dict__.get(attr, ()) or ()
47
- for item in _seqify(seq):
48
- if dedupe:
49
- try:
50
- if item in seen_hashable:
51
- continue
52
- seen_hashable.add(item)
53
- except TypeError:
54
- if any(item == existing for existing in values):
55
- continue
56
- values.append(item)
57
- return tuple(values)
28
+ return merge_mro_sequence_attr(
29
+ owner,
30
+ attr,
31
+ include_inherited=include_inherited,
32
+ reverse=reverse,
33
+ dedupe=dedupe,
34
+ )
58
35
 
59
36
 
60
37
  def normalize_app_spec(spec: "AppSpec") -> "AppSpec":
@@ -68,6 +45,7 @@ def normalize_app_spec(spec: "AppSpec") -> "AppSpec":
68
45
  title=str(spec.title or "Tigrbl"),
69
46
  description=spec.description,
70
47
  version=str(spec.version or "0.1.0"),
48
+ execution_backend=str(getattr(spec, "execution_backend", None) or "auto"),
71
49
  engine=spec.engine,
72
50
  routers=routers,
73
51
  ops=ops,
@@ -93,6 +71,7 @@ class AppSpec(SerdeMixin):
93
71
  title: str = "Tigrbl"
94
72
  description: str | None = None
95
73
  version: str = "0.1.0"
74
+ execution_backend: str = "auto"
96
75
  engine: Optional[EngineCfg] = None
97
76
 
98
77
  # NEW: multi-Router composition (store Router classes or instances)
@@ -128,6 +107,7 @@ class AppSpec(SerdeMixin):
128
107
  sentinel = object()
129
108
  title: Any = sentinel
130
109
  version: Any = sentinel
110
+ execution_backend: Any = sentinel
131
111
  engine: Any | None = sentinel # type: ignore[assignment]
132
112
  response: Any = sentinel
133
113
  jsonrpc_prefix: Any = sentinel
@@ -139,6 +119,11 @@ class AppSpec(SerdeMixin):
139
119
  title = base.__dict__["TITLE"]
140
120
  if "VERSION" in base.__dict__ and version is sentinel:
141
121
  version = base.__dict__["VERSION"]
122
+ if (
123
+ "EXECUTION_BACKEND" in base.__dict__
124
+ and execution_backend is sentinel
125
+ ):
126
+ execution_backend = base.__dict__["EXECUTION_BACKEND"]
142
127
  if "ENGINE" in base.__dict__ and engine is sentinel:
143
128
  engine = base.__dict__["ENGINE"]
144
129
  if "RESPONSE" in base.__dict__ and response is sentinel:
@@ -154,6 +139,8 @@ class AppSpec(SerdeMixin):
154
139
  title = "Tigrbl"
155
140
  if version is sentinel:
156
141
  version = "0.1.0"
142
+ if execution_backend is sentinel:
143
+ execution_backend = "auto"
157
144
  if engine is sentinel:
158
145
  engine = None
159
146
  if response is sentinel:
@@ -171,6 +158,7 @@ class AppSpec(SerdeMixin):
171
158
  title=title,
172
159
  description=description,
173
160
  version=version,
161
+ execution_backend=execution_backend,
174
162
  engine=engine,
175
163
  routers=tuple(
176
164
  merge_seq_attr(
@@ -199,6 +187,7 @@ class AppSpec(SerdeMixin):
199
187
  title=spec.title,
200
188
  description=spec.description,
201
189
  version=spec.version,
190
+ execution_backend=spec.execution_backend,
202
191
  engine=spec.engine,
203
192
  routers=tuple(spec.routers or ()),
204
193
  ops=tuple(spec.ops or ()),
@@ -3,21 +3,58 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass, field
4
4
  from typing import Literal, Optional, Type, Union
5
5
 
6
- from ..config.constants import TIGRBL_NESTED_PATHS_ATTR
6
+ from ..config.constants import (
7
+ TIGRBL_NESTED_PATHS_ATTR,
8
+ __JSONRPC_DEFAULT_ENDPOINT__,
9
+ )
7
10
  from .serde import SerdeMixin
8
11
 
12
+ Exchange = Literal[
13
+ "request_response",
14
+ "server_stream",
15
+ "event_stream",
16
+ "client_stream",
17
+ "bidirectional",
18
+ "bidirectional_stream",
19
+ "fire_and_forget",
20
+ ]
21
+ Framing = Literal["json", "jsonrpc", "sse", "stream", "text", "bytes", "webtransport"]
22
+
9
23
 
10
24
  @dataclass(frozen=True, slots=True)
11
25
  class HttpRestBindingSpec(SerdeMixin):
12
26
  proto: Literal["http.rest", "https.rest"]
13
27
  methods: tuple[str, ...]
14
28
  path: str
29
+ exchange: Exchange = "request_response"
30
+ framing: Framing = "json"
15
31
 
16
32
 
17
33
  @dataclass(frozen=True, slots=True)
18
34
  class HttpJsonRpcBindingSpec(SerdeMixin):
19
35
  proto: Literal["http.jsonrpc", "https.jsonrpc"]
20
36
  rpc_method: str
37
+ endpoint: str = __JSONRPC_DEFAULT_ENDPOINT__
38
+ exchange: Exchange = "request_response"
39
+ framing: Framing = "jsonrpc"
40
+
41
+
42
+ @dataclass(frozen=True, slots=True)
43
+ class HttpStreamBindingSpec(SerdeMixin):
44
+ proto: Literal["http.stream", "https.stream"]
45
+ path: str
46
+ methods: tuple[str, ...] = ("GET",)
47
+ exchange: Exchange = "server_stream"
48
+ framing: Framing = "stream"
49
+
50
+
51
+ @dataclass(frozen=True, slots=True)
52
+ class SseBindingSpec(SerdeMixin):
53
+ proto: Literal["http.sse", "https.sse"] = "http.sse"
54
+ path: str = "/"
55
+ methods: tuple[str, ...] = ("GET",)
56
+ exchange: Exchange = "server_stream"
57
+ framing: Framing = "sse"
21
58
 
22
59
 
23
60
  @dataclass(frozen=True, slots=True)
@@ -25,12 +62,25 @@ class WsBindingSpec(SerdeMixin):
25
62
  proto: Literal["ws", "wss"]
26
63
  path: str
27
64
  subprotocols: tuple[str, ...] = ()
65
+ exchange: Exchange = "bidirectional_stream"
66
+ framing: Framing = "text"
67
+
68
+
69
+ @dataclass(frozen=True, slots=True)
70
+ class WebTransportBindingSpec(SerdeMixin):
71
+ proto: Literal["webtransport"] = "webtransport"
72
+ path: str = "/"
73
+ exchange: Exchange = "bidirectional_stream"
74
+ framing: Framing = "webtransport"
28
75
 
29
76
 
30
77
  TransportBindingSpec = Union[
31
78
  HttpRestBindingSpec,
32
79
  HttpJsonRpcBindingSpec,
80
+ HttpStreamBindingSpec,
81
+ SseBindingSpec,
33
82
  WsBindingSpec,
83
+ WebTransportBindingSpec,
34
84
  ]
35
85
 
36
86
 
@@ -70,9 +120,14 @@ def resolve_rest_nested_prefix(model: Type) -> Optional[str]:
70
120
  __all__ = [
71
121
  "BindingSpec",
72
122
  "BindingRegistrySpec",
123
+ "Exchange",
124
+ "Framing",
73
125
  "HttpJsonRpcBindingSpec",
74
126
  "HttpRestBindingSpec",
127
+ "HttpStreamBindingSpec",
128
+ "SseBindingSpec",
75
129
  "TransportBindingSpec",
130
+ "WebTransportBindingSpec",
76
131
  "WsBindingSpec",
77
132
  "resolve_rest_nested_prefix",
78
133
  ]
@@ -5,9 +5,10 @@ from functools import lru_cache
5
5
  from types import SimpleNamespace
6
6
  from typing import Any, Callable, Dict, Optional
7
7
 
8
+ from .datatypes import DataTypeSpec, StorageTypeRef, infer_datatype
8
9
  from .._spec.field_spec import FieldSpec as F
9
10
  from .._spec.io_spec import IOSpec as IO
10
- from .._spec.storage_spec import StorageSpec as S
11
+ from .._spec.storage_spec import ForeignKeySpec, StorageSpec as S
11
12
  from .serde import SerdeMixin
12
13
 
13
14
  logger = logging.getLogger("uvicorn")
@@ -34,6 +35,7 @@ class ColumnSpec(SerdeMixin):
34
35
  self,
35
36
  *,
36
37
  storage: S | None,
38
+ datatype: DataTypeSpec | None = None,
37
39
  field: F | None = None,
38
40
  io: IO | None = None,
39
41
  default_factory: Optional[Callable[[dict], Any]] = None,
@@ -42,6 +44,12 @@ class ColumnSpec(SerdeMixin):
42
44
  self.storage = storage
43
45
  self.field = field if field is not None else F()
44
46
  self.io = io if io is not None else IO()
47
+ self.datatype = datatype or infer_datatype(
48
+ field_py_type=self.field.py_type,
49
+ storage_type=getattr(storage, "type_", None),
50
+ nullable=getattr(storage, "nullable", None),
51
+ constraints=getattr(self.field, "constraints", {}),
52
+ )
45
53
  self.default_factory = default_factory
46
54
  self.read_producer = read_producer
47
55
 
@@ -85,6 +93,49 @@ class ColumnSpec(SerdeMixin):
85
93
  except TypeError:
86
94
  return ()
87
95
 
96
+ @staticmethod
97
+ def _storage_spec_from_column(column: object) -> S:
98
+ fk_spec = None
99
+ foreign_keys = tuple(getattr(column, "foreign_keys", ()) or ())
100
+ if foreign_keys:
101
+ fk = foreign_keys[0]
102
+ target = str(getattr(fk, "target_fullname", "") or "")
103
+ if target:
104
+ fk_spec = ForeignKeySpec(
105
+ target=target,
106
+ on_delete=str(getattr(fk, "ondelete", None) or "RESTRICT"),
107
+ on_update=str(getattr(fk, "onupdate", None) or "RESTRICT"),
108
+ deferrable=bool(getattr(fk, "deferrable", False)),
109
+ initially_deferred=(
110
+ str(getattr(fk, "initially", "") or "").upper() == "DEFERRED"
111
+ ),
112
+ match=str(getattr(fk, "match", None) or "SIMPLE"),
113
+ )
114
+
115
+ type_obj = getattr(column, "type", None)
116
+ physical_name = None
117
+ if type_obj is not None:
118
+ physical_name = getattr(type_obj, "__visit_name__", None) or type(type_obj).__name__
119
+
120
+ return S(
121
+ type_=type_obj,
122
+ physical_type=(
123
+ StorageTypeRef(physical_name=str(physical_name))
124
+ if physical_name is not None
125
+ else None
126
+ ),
127
+ nullable=getattr(column, "nullable", None),
128
+ unique=bool(getattr(column, "unique", False)),
129
+ index=bool(getattr(column, "index", False)),
130
+ primary_key=bool(getattr(column, "primary_key", False)),
131
+ autoincrement=getattr(column, "autoincrement", None),
132
+ default=getattr(getattr(column, "default", None), "arg", None),
133
+ onupdate=getattr(getattr(column, "onupdate", None), "arg", None),
134
+ server_default=getattr(getattr(column, "server_default", None), "arg", None),
135
+ fk=fk_spec,
136
+ comment=getattr(column, "comment", None),
137
+ )
138
+
88
139
  @staticmethod
89
140
  @lru_cache(maxsize=None)
90
141
  def _mro_collect_columns_cached(
@@ -121,7 +172,13 @@ class ColumnSpec(SerdeMixin):
121
172
  name = getattr(col, "key", None) or getattr(col, "name", None)
122
173
  if not isinstance(name, str):
123
174
  continue
124
- out.setdefault(name, ColumnSpec(storage=S(), io=ColumnSpec._DEFAULT_IO))
175
+ out.setdefault(
176
+ name,
177
+ ColumnSpec(
178
+ storage=ColumnSpec._storage_spec_from_column(col),
179
+ io=ColumnSpec._DEFAULT_IO,
180
+ ),
181
+ )
125
182
  else:
126
183
  # Declarative models can be inspected before SQLAlchemy finishes
127
184
  # materializing ``__table__``. In that transient state plain
@@ -133,7 +190,8 @@ class ColumnSpec(SerdeMixin):
133
190
  continue
134
191
  if hasattr(value, "type") and hasattr(value, "nullable"):
135
192
  out[attr_name] = ColumnSpec(
136
- storage=S(), io=ColumnSpec._DEFAULT_IO
193
+ storage=ColumnSpec._storage_spec_from_column(value),
194
+ io=ColumnSpec._DEFAULT_IO,
137
195
  )
138
196
 
139
197
  logger.debug(