tigrbl-core 0.4.5.dev4__tar.gz → 0.4.5.dev8__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 (72) hide show
  1. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/PKG-INFO +7 -7
  2. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/README.md +5 -5
  3. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/pyproject.toml +3 -2
  4. tigrbl_core-0.4.5.dev8/tigrbl_core/_compat.py +40 -0
  5. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/__init__.py +8 -5
  6. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/binding_spec.py +11 -0
  7. tigrbl_core-0.4.5.dev4/tigrbl_core/_spec/session_spec.py → tigrbl_core-0.4.5.dev8/tigrbl_core/_spec/engine_session_spec.py +37 -30
  8. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/engine_spec.py +13 -1
  9. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/plugins.py +11 -4
  10. tigrbl_core-0.4.5.dev8/tigrbl_core/_spec/session_spec.py +42 -0
  11. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/table_profile_bindings.py +19 -0
  12. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/table_profile_spec.py +15 -67
  13. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/__init__.py +2 -0
  14. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/builder/build_schema.py +4 -2
  15. tigrbl_core-0.4.5.dev8/tigrbl_core/schema/builder/helpers.py +235 -0
  16. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/builder/list_params.py +22 -32
  17. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/get_schema.py +10 -5
  18. tigrbl_core-0.4.5.dev8/tigrbl_core/schema/materialize.py +28 -0
  19. tigrbl_core-0.4.5.dev8/tigrbl_core/schema/result.py +78 -0
  20. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/surface_chains.json +2 -2
  21. tigrbl_core-0.4.5.dev4/tigrbl_core/schema/builder/helpers.py +0 -113
  22. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/LICENSE +0 -0
  23. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/NOTICE +0 -0
  24. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/alias_spec.py +0 -0
  25. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/app_spec.py +0 -0
  26. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/column_spec.py +0 -0
  27. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/datatypes.py +0 -0
  28. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/docs_spec.py +0 -0
  29. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/exceptions.py +0 -0
  30. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/exposure_policy.py +0 -0
  31. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/field_spec.py +0 -0
  32. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/headers_spec.py +0 -0
  33. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/hook_spec.py +0 -0
  34. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/hook_types.py +0 -0
  35. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/io_spec.py +0 -0
  36. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/middleware_spec.py +0 -0
  37. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/monotone.py +0 -0
  38. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/op_spec.py +0 -0
  39. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/op_utils.py +0 -0
  40. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/path_spec.py +0 -0
  41. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/registry.py +0 -0
  42. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/request_spec.py +0 -0
  43. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/response_resolver.py +0 -0
  44. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/response_spec.py +0 -0
  45. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/response_types.py +0 -0
  46. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/router_spec.py +0 -0
  47. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/schema_spec.py +0 -0
  48. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/serde.py +0 -0
  49. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/storage_spec.py +0 -0
  50. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/table_registry_spec.py +0 -0
  51. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/table_spec.py +0 -0
  52. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/transport_stack.py +0 -0
  53. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/well_known_spec.py +0 -0
  54. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/canonical_json.py +0 -0
  55. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/config/__init__.py +0 -0
  56. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/config/constants.py +0 -0
  57. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/config/defaults.py +0 -0
  58. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/config/engine_traversal.py +0 -0
  59. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/config/resolver.py +0 -0
  60. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/core/__init__.py +0 -0
  61. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/op/__init__.py +0 -0
  62. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/op/canonical.py +0 -0
  63. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/op/collect.py +0 -0
  64. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/op/types.py +0 -0
  65. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/builder/__init__.py +0 -0
  66. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/builder/cache.py +0 -0
  67. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/builder/extras.py +0 -0
  68. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/builder/strip_parent_fields.py +0 -0
  69. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/provenance.py +0 -0
  70. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/provenance_inventory.py +0 -0
  71. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/spec_json.py +0 -0
  72. {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tigrbl-core
3
- Version: 0.4.5.dev4
3
+ Version: 0.4.5.dev8
4
4
  Summary: Core Tigrbl framework specifications, decorators, schemas, hooks, operations, and primitives for schema-first APIs.
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -235,7 +235,7 @@ Project-URL: Discord, https://discord.gg/K4YTAPapjR
235
235
  Project-URL: Homepage, https://github.com/tigrbl/tigrbl
236
236
  Project-URL: Issues, https://github.com/tigrbl/tigrbl/issues
237
237
  Project-URL: Organization, https://github.com/tigrbl
238
- Project-URL: Repository, https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl_core
238
+ Project-URL: Repository, https://github.com/tigrbl/tigrbl/tree/master/pkgs/10_core/tigrbl_core
239
239
  Description-Content-Type: text/markdown
240
240
 
241
241
  <div align="center">
@@ -245,7 +245,7 @@ Description-Content-Type: text/markdown
245
245
  <a href="https://pypi.org/project/tigrbl-core/"><img src="https://img.shields.io/pypi/v/tigrbl-core?label=PyPI" alt="PyPI version for tigrbl-core"/></a>
246
246
  <a href="https://pypi.org/project/tigrbl-core/"><img src="https://static.pepy.tech/badge/tigrbl-core" alt="Downloads for tigrbl-core"/></a>
247
247
  <a href="https://discord.gg/K4YTAPapjR"><img src="https://img.shields.io/badge/Discord-Join%20chat-5865F2?logo=discord&logoColor=white" alt="Discord community for tigrbl-core"/></a>
248
- <a href="https://github.com/tigrbl/tigrbl/blob/master/pkgs/core/tigrbl_core/README.md"><img src="https://hits.sh/github.com/tigrbl/tigrbl/blob/master/pkgs/core/tigrbl_core/README.md.svg?label=hits" alt="Repository hits for tigrbl-core README"/></a>
248
+ <a href="https://github.com/tigrbl/tigrbl/blob/master/pkgs/10_core/tigrbl_core/README.md"><img src="https://hits.sh/github.com/tigrbl/tigrbl/blob/master/pkgs/10_core/tigrbl_core/README.md.svg?label=hits" alt="Repository hits for tigrbl-core README"/></a>
249
249
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-525252" alt="Apache 2.0 license"/></a>
250
250
  <a href="pyproject.toml"><img src="https://img.shields.io/badge/python-3.10%2C%203.11%2C%203.12%2C%203.13%2C%203.14-3776ab" alt="Python versions 3.10 | 3.11 | 3.12 | 3.13 | 3.14 for tigrbl-core"/></a>
251
251
  <a href="https://github.com/tigrbl/tigrbl/blob/master/docs/README.md"><img src="https://img.shields.io/badge/workspace-core-1f6feb" alt="Workspace group for tigrbl-core"/></a>
@@ -269,7 +269,7 @@ Framework maintainers, extension authors, and advanced users composing Tigrbl fr
269
269
 
270
270
  ## Where does tigrbl-core fit?
271
271
 
272
- `tigrbl-core` lives at `pkgs/core/tigrbl_core` and serves a focused layer in the split Tigrbl framework.
272
+ `tigrbl-core` lives at `pkgs/10_core/tigrbl_core` and serves a focused layer in the split Tigrbl framework.
273
273
 
274
274
  ## How does tigrbl-core work?
275
275
 
@@ -291,7 +291,7 @@ pip install tigrbl-core
291
291
  | Surface | Value |
292
292
  |---|---|
293
293
  | PyPI package | [`tigrbl-core`](https://pypi.org/project/tigrbl-core/) |
294
- | Repository path | [`pkgs/core/tigrbl_core`](https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl_core) |
294
+ | Repository path | [`pkgs/10_core/tigrbl_core`](https://github.com/tigrbl/tigrbl/tree/master/pkgs/10_core/tigrbl_core) |
295
295
  | Python import root | `tigrbl_core` |
296
296
  | Console scripts | none declared |
297
297
  | Entry points | none declared |
@@ -460,7 +460,7 @@ Choose `tigrbl-core` when the quick-answer table matches your use case. Choose [
460
460
 
461
461
  - Community: [Discord](https://discord.gg/K4YTAPapjR).
462
462
  - Issues: [GitHub Issues](https://github.com/tigrbl/tigrbl/issues).
463
- - Repository: [pkgs/core/tigrbl_core](https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl_core).
463
+ - Repository: [pkgs/10_core/tigrbl_core](https://github.com/tigrbl/tigrbl/tree/master/pkgs/10_core/tigrbl_core).
464
464
 
465
465
  ## Package-local Boundary
466
466
 
@@ -471,7 +471,7 @@ This file is a package-local distribution entry point. This README is the packag
471
471
  - Package status: governed package in the `tigrbl/tigrbl` workspace.
472
472
  - Governance source: [SSOT registry](https://github.com/tigrbl/tigrbl/blob/master/.ssot/registry.json).
473
473
  - Release evidence: [publish workflow](https://github.com/tigrbl/tigrbl/actions/workflows/publish.yml) validates package builds, tests, GitHub release assets, and PyPI publication for managed packages.
474
- - Local certification guard: `pkgs/core/tigrbl_tests/tests/unit/test_package_badges_and_notices.py` verifies every package README keeps the Discord badge, Apache 2.0 badge, explicit Python-version badge, `LICENSE`, and `NOTICE`.
474
+ - Local certification guard: `pkgs/97_tests/tigrbl_tests/tests/unit/test_package_badges_and_notices.py` verifies every package README keeps the Discord badge, Apache 2.0 badge, explicit Python-version badge, `LICENSE`, and `NOTICE`.
475
475
  - Scope note: this README documents the package boundary. Runtime feature support remains governed by `.ssot/` entities and the conformance docs linked below.
476
476
 
477
477
  ## License
@@ -5,7 +5,7 @@
5
5
  <a href="https://pypi.org/project/tigrbl-core/"><img src="https://img.shields.io/pypi/v/tigrbl-core?label=PyPI" alt="PyPI version for tigrbl-core"/></a>
6
6
  <a href="https://pypi.org/project/tigrbl-core/"><img src="https://static.pepy.tech/badge/tigrbl-core" alt="Downloads for tigrbl-core"/></a>
7
7
  <a href="https://discord.gg/K4YTAPapjR"><img src="https://img.shields.io/badge/Discord-Join%20chat-5865F2?logo=discord&logoColor=white" alt="Discord community for tigrbl-core"/></a>
8
- <a href="https://github.com/tigrbl/tigrbl/blob/master/pkgs/core/tigrbl_core/README.md"><img src="https://hits.sh/github.com/tigrbl/tigrbl/blob/master/pkgs/core/tigrbl_core/README.md.svg?label=hits" alt="Repository hits for tigrbl-core README"/></a>
8
+ <a href="https://github.com/tigrbl/tigrbl/blob/master/pkgs/10_core/tigrbl_core/README.md"><img src="https://hits.sh/github.com/tigrbl/tigrbl/blob/master/pkgs/10_core/tigrbl_core/README.md.svg?label=hits" alt="Repository hits for tigrbl-core README"/></a>
9
9
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-525252" alt="Apache 2.0 license"/></a>
10
10
  <a href="pyproject.toml"><img src="https://img.shields.io/badge/python-3.10%2C%203.11%2C%203.12%2C%203.13%2C%203.14-3776ab" alt="Python versions 3.10 | 3.11 | 3.12 | 3.13 | 3.14 for tigrbl-core"/></a>
11
11
  <a href="https://github.com/tigrbl/tigrbl/blob/master/docs/README.md"><img src="https://img.shields.io/badge/workspace-core-1f6feb" alt="Workspace group for tigrbl-core"/></a>
@@ -29,7 +29,7 @@ Framework maintainers, extension authors, and advanced users composing Tigrbl fr
29
29
 
30
30
  ## Where does tigrbl-core fit?
31
31
 
32
- `tigrbl-core` lives at `pkgs/core/tigrbl_core` and serves a focused layer in the split Tigrbl framework.
32
+ `tigrbl-core` lives at `pkgs/10_core/tigrbl_core` and serves a focused layer in the split Tigrbl framework.
33
33
 
34
34
  ## How does tigrbl-core work?
35
35
 
@@ -51,7 +51,7 @@ pip install tigrbl-core
51
51
  | Surface | Value |
52
52
  |---|---|
53
53
  | PyPI package | [`tigrbl-core`](https://pypi.org/project/tigrbl-core/) |
54
- | Repository path | [`pkgs/core/tigrbl_core`](https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl_core) |
54
+ | Repository path | [`pkgs/10_core/tigrbl_core`](https://github.com/tigrbl/tigrbl/tree/master/pkgs/10_core/tigrbl_core) |
55
55
  | Python import root | `tigrbl_core` |
56
56
  | Console scripts | none declared |
57
57
  | Entry points | none declared |
@@ -220,7 +220,7 @@ Choose `tigrbl-core` when the quick-answer table matches your use case. Choose [
220
220
 
221
221
  - Community: [Discord](https://discord.gg/K4YTAPapjR).
222
222
  - Issues: [GitHub Issues](https://github.com/tigrbl/tigrbl/issues).
223
- - Repository: [pkgs/core/tigrbl_core](https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl_core).
223
+ - Repository: [pkgs/10_core/tigrbl_core](https://github.com/tigrbl/tigrbl/tree/master/pkgs/10_core/tigrbl_core).
224
224
 
225
225
  ## Package-local Boundary
226
226
 
@@ -231,7 +231,7 @@ This file is a package-local distribution entry point. This README is the packag
231
231
  - Package status: governed package in the `tigrbl/tigrbl` workspace.
232
232
  - Governance source: [SSOT registry](https://github.com/tigrbl/tigrbl/blob/master/.ssot/registry.json).
233
233
  - Release evidence: [publish workflow](https://github.com/tigrbl/tigrbl/actions/workflows/publish.yml) validates package builds, tests, GitHub release assets, and PyPI publication for managed packages.
234
- - Local certification guard: `pkgs/core/tigrbl_tests/tests/unit/test_package_badges_and_notices.py` verifies every package README keeps the Discord badge, Apache 2.0 badge, explicit Python-version badge, `LICENSE`, and `NOTICE`.
234
+ - Local certification guard: `pkgs/97_tests/tigrbl_tests/tests/unit/test_package_badges_and_notices.py` verifies every package README keeps the Discord badge, Apache 2.0 badge, explicit Python-version badge, `LICENSE`, and `NOTICE`.
235
235
  - Scope note: this README documents the package boundary. Runtime feature support remains governed by `.ssot/` entities and the conformance docs linked below.
236
236
 
237
237
  ## License
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tigrbl-core"
3
- version = "0.4.5.dev4"
3
+ version = "0.4.5.dev8"
4
4
  description = "Core Tigrbl framework specifications, decorators, schemas, hooks, operations, and primitives for schema-first APIs."
5
5
  readme = "README.md"
6
6
  license = { file = "LICENSE" }
@@ -55,7 +55,7 @@ dependencies = [
55
55
  Organization = "https://github.com/tigrbl"
56
56
  Discord = "https://discord.gg/K4YTAPapjR"
57
57
  Homepage = "https://github.com/tigrbl/tigrbl"
58
- Repository = "https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl_core"
58
+ Repository = "https://github.com/tigrbl/tigrbl/tree/master/pkgs/10_core/tigrbl_core"
59
59
  Issues = "https://github.com/tigrbl/tigrbl/issues"
60
60
 
61
61
  [tool.uv.sources]
@@ -75,5 +75,6 @@ packages = [
75
75
  [dependency-groups]
76
76
  dev = [
77
77
  "pytest>=8.0",
78
+ "sqlalchemy>=2.0",
78
79
  "ruff>=0.9",
79
80
  ]
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+ import re
5
+ import warnings
6
+
7
+ _REMOVAL_VERSION = (0, 13, 0)
8
+
9
+
10
+ def _dist_version_tuple() -> tuple[int, int, int] | None:
11
+ try:
12
+ raw = version("tigrbl")
13
+ except PackageNotFoundError:
14
+ return None
15
+ parts = re.split(r"[.+-]", raw, maxsplit=1)[0].split(".")
16
+ numbers = []
17
+ for item in parts[:3]:
18
+ try:
19
+ numbers.append(int(item))
20
+ except ValueError:
21
+ numbers.append(0)
22
+ while len(numbers) < 3:
23
+ numbers.append(0)
24
+ return tuple(numbers) # type: ignore[return-value]
25
+
26
+
27
+ def warn_legacy_engine_session_name(old: str, new: str) -> None:
28
+ installed = _dist_version_tuple()
29
+ if installed is not None and installed > _REMOVAL_VERSION:
30
+ raise RuntimeError(
31
+ f"{old} was removed after tigrbl 0.13.0; use {new} instead."
32
+ )
33
+ warnings.warn(
34
+ f"{old} is deprecated and will fail after tigrbl 0.13.0; use {new} instead.",
35
+ DeprecationWarning,
36
+ stacklevel=3,
37
+ )
38
+
39
+
40
+ __all__ = ["warn_legacy_engine_session_name"]
@@ -122,12 +122,16 @@ _EXPORTS = {
122
122
  "SchemaSpec": "schema_spec",
123
123
  "SchemaRef": "schema_spec",
124
124
  "SchemaArg": "schema_spec",
125
+ "EngineSessionCfg": "engine_session_spec",
126
+ "EngineSessionSpec": "engine_session_spec",
127
+ "engine_session_spec": "engine_session_spec",
128
+ "SessionCfg": "session_spec",
125
129
  "SessionSpec": "session_spec",
126
130
  "session_spec": "session_spec",
127
- "tx_read_committed": "session_spec",
128
- "tx_repeatable_read": "session_spec",
129
- "tx_serializable": "session_spec",
130
- "readonly": "session_spec",
131
+ "tx_read_committed": "engine_session_spec",
132
+ "tx_repeatable_read": "engine_session_spec",
133
+ "tx_serializable": "engine_session_spec",
134
+ "readonly": "engine_session_spec",
131
135
  "StorageTypeRef": "datatypes",
132
136
  "StorageSpec": "storage_spec",
133
137
  "S": "storage_spec",
@@ -156,7 +160,6 @@ _EXPORTS = {
156
160
  "get_builtin_table_profile_definition": "table_profile_spec",
157
161
  "get_table_profile": "table_profile_spec",
158
162
  "iter_builtin_table_profile_definitions": "table_profile_spec",
159
- "make_builtin_table_profile": "table_profile_spec",
160
163
  "register_table_profile": "table_profile_spec",
161
164
  "TableRegistrySpec": "table_registry_spec",
162
165
  "BINDING_STACK_PROJECTIONS": "transport_stack",
@@ -775,6 +775,7 @@ class WebTransportBindingSpec(SerdeMixin):
775
775
  exchange: Exchange = "bidirectional_stream"
776
776
  framing: None = None
777
777
  inner_framing: FramingSpec | None = None
778
+ rpc_method: str | None = None
778
779
 
779
780
  def __post_init__(self) -> None:
780
781
  if self.framing is not None:
@@ -801,10 +802,20 @@ class WebTransportBindingSpec(SerdeMixin):
801
802
  lane=lane,
802
803
  inner_framing=self.inner_framing,
803
804
  )
805
+ rpc_method = self.rpc_method
806
+ if rpc_method is not None:
807
+ rpc_method = str(rpc_method).strip()
808
+ if not rpc_method:
809
+ raise ValueError("WebTransport rpc_method must not be empty")
810
+ if lane != "bidi_stream" or framing_kind(inner_framing) != "jsonrpc":
811
+ raise ValueError(
812
+ "WebTransport rpc_method requires a bidi_stream lane with JSON-RPC framing"
813
+ )
804
814
  object.__setattr__(self, "framing", None)
805
815
  object.__setattr__(self, "lane", lane)
806
816
  object.__setattr__(self, "exchange", exchange)
807
817
  object.__setattr__(self, "inner_framing", inner_framing)
818
+ object.__setattr__(self, "rpc_method", rpc_method)
808
819
 
809
820
 
810
821
  @dataclass(frozen=True, slots=True)
@@ -5,13 +5,13 @@ from typing import Any, Mapping, MutableMapping, Optional, Union
5
5
 
6
6
  from .serde import SerdeMixin
7
7
 
8
- SessionCfg = Union["SessionSpec", Mapping[str, Any], None]
8
+ EngineSessionCfg = Union["EngineSessionSpec", Mapping[str, Any], None]
9
9
 
10
10
 
11
11
  @dataclass(frozen=True)
12
- class SessionSpec(SerdeMixin):
12
+ class EngineSessionSpec(SerdeMixin):
13
13
  """
14
- Per-session policy for Tigrbl sessions.
14
+ Per-session policy for Tigrbl engine database sessions.
15
15
 
16
16
  These fields are backend-agnostic hints and constraints. Adapters should
17
17
  validate and apply them where supported; critical ones (like isolation and
@@ -69,21 +69,27 @@ class SessionSpec(SerdeMixin):
69
69
  idempotency_key: Optional[str] = None
70
70
  page_snapshot: Optional[str] = None
71
71
 
72
- def merge(self, higher: "SessionSpec | Mapping[str, Any] | None") -> "SessionSpec":
72
+ def merge(
73
+ self, higher: "EngineSessionSpec | Mapping[str, Any] | None"
74
+ ) -> "EngineSessionSpec":
73
75
  """
74
76
  Overlay another spec on top of this one (explicit fields take precedence).
75
77
  Use to implement op > model > router > app precedence.
76
78
  """
77
79
  if higher is None:
78
80
  return self
79
- h = higher if isinstance(higher, SessionSpec) else SessionSpec.from_any(higher)
81
+ h = (
82
+ higher
83
+ if isinstance(higher, EngineSessionSpec)
84
+ else EngineSessionSpec.from_any(higher)
85
+ )
80
86
  if h is None:
81
87
  return self
82
- defaults = SessionSpec()
88
+ defaults = EngineSessionSpec()
83
89
  vals: MutableMapping[str, Any] = {
84
- f.name: getattr(self, f.name) for f in fields(SessionSpec)
90
+ f.name: getattr(self, f.name) for f in fields(EngineSessionSpec)
85
91
  }
86
- for f in fields(SessionSpec):
92
+ for f in fields(EngineSessionSpec):
87
93
  hv = getattr(h, f.name)
88
94
  if hv is None:
89
95
  continue
@@ -91,61 +97,62 @@ class SessionSpec(SerdeMixin):
91
97
  if hv == default and vals[f.name] != default:
92
98
  continue
93
99
  vals[f.name] = hv
94
- return SessionSpec(**vals) # type: ignore[arg-type]
100
+ return EngineSessionSpec(**vals) # type: ignore[arg-type]
95
101
 
96
102
  def to_kwargs(self) -> dict[str, Any]:
97
103
  """Return only non-None items as a plain dict (adapters can **kwargs this)."""
98
104
  return {
99
105
  f.name: getattr(self, f.name)
100
- for f in fields(SessionSpec)
106
+ for f in fields(EngineSessionSpec)
101
107
  if getattr(self, f.name) is not None
102
108
  }
103
109
 
104
110
  @staticmethod
105
- def from_any(x: SessionCfg) -> Optional["SessionSpec"]:
111
+ def from_any(x: EngineSessionCfg) -> Optional["EngineSessionSpec"]:
106
112
  if x is None:
107
113
  return None
108
- if isinstance(x, SessionSpec):
114
+ if isinstance(x, EngineSessionSpec):
109
115
  return x
110
116
  if isinstance(x, Mapping):
111
117
  m = dict(x)
112
- # aliases
113
118
  if "readonly" in m and "read_only" not in m:
114
119
  m["read_only"] = bool(m.pop("readonly"))
115
120
  if "iso" in m and "isolation" not in m:
116
121
  m["isolation"] = str(m.pop("iso"))
117
- allowed = {f.name for f in fields(SessionSpec)}
118
- return SessionSpec(**{k: v for k, v in m.items() if k in allowed})
119
- raise TypeError(f"Unsupported SessionSpec input: {type(x)}")
122
+ allowed = {f.name for f in fields(EngineSessionSpec)}
123
+ return EngineSessionSpec(**{k: v for k, v in m.items() if k in allowed})
124
+ raise TypeError(f"Unsupported EngineSessionSpec input: {type(x)}")
120
125
 
121
126
 
122
- def session_spec(cfg: SessionCfg = None, /, **kw: Any) -> SessionSpec:
123
- """Build a SessionSpec from either a mapping/spec or kwargs."""
127
+ def engine_session_spec(
128
+ cfg: EngineSessionCfg = None, /, **kw: Any
129
+ ) -> EngineSessionSpec:
130
+ """Build an EngineSessionSpec from either a mapping/spec or kwargs."""
124
131
  if cfg is not None and kw:
125
132
  raise ValueError("Provide either a mapping/spec or kwargs, not both")
126
- return SessionSpec.from_any(cfg or kw) or SessionSpec()
133
+ return EngineSessionSpec.from_any(cfg or kw) or EngineSessionSpec()
127
134
 
128
135
 
129
- def tx_read_committed(*, read_only: Optional[bool] = None) -> SessionSpec:
130
- return SessionSpec(isolation="read_committed", read_only=read_only)
136
+ def tx_read_committed(*, read_only: Optional[bool] = None) -> EngineSessionSpec:
137
+ return EngineSessionSpec(isolation="read_committed", read_only=read_only)
131
138
 
132
139
 
133
- def tx_repeatable_read(*, read_only: Optional[bool] = None) -> SessionSpec:
134
- return SessionSpec(isolation="repeatable_read", read_only=read_only)
140
+ def tx_repeatable_read(*, read_only: Optional[bool] = None) -> EngineSessionSpec:
141
+ return EngineSessionSpec(isolation="repeatable_read", read_only=read_only)
135
142
 
136
143
 
137
- def tx_serializable(*, read_only: Optional[bool] = None) -> SessionSpec:
138
- return SessionSpec(isolation="serializable", read_only=read_only)
144
+ def tx_serializable(*, read_only: Optional[bool] = None) -> EngineSessionSpec:
145
+ return EngineSessionSpec(isolation="serializable", read_only=read_only)
139
146
 
140
147
 
141
- def readonly() -> SessionSpec:
142
- return SessionSpec(read_only=True)
148
+ def readonly() -> EngineSessionSpec:
149
+ return EngineSessionSpec(read_only=True)
143
150
 
144
151
 
145
152
  __all__ = [
146
- "SessionCfg",
147
- "SessionSpec",
148
- "session_spec",
153
+ "EngineSessionCfg",
154
+ "EngineSessionSpec",
155
+ "engine_session_spec",
149
156
  "tx_read_committed",
150
157
  "tx_repeatable_read",
151
158
  "tx_serializable",
@@ -53,6 +53,8 @@ class EngineSpec(SerdeMixin):
53
53
  "sqlite+aiosqlite:///./file.db" ,
54
54
  "postgresql://user:pwd@host:5432/db" ,
55
55
  "postgresql+asyncpg://user:pwd@host:5432/db"
56
+ "analytics.duckdb" ,
57
+ "quack://analytics.internal:9494"
56
58
  • Mapping (recommended for clarity/portability):
57
59
  {"kind":"sqlite","async":True,"path":"./file.db"}
58
60
  {"kind":"postgres","async":True,"host":"db","db":"app_db",...}
@@ -163,6 +165,10 @@ class EngineSpec(SerdeMixin):
163
165
  if s.startswith("postgresql://") or s.startswith("postgres://"):
164
166
  return EngineSpec(kind="postgres", async_=False, dsn=s)
165
167
 
168
+ # DuckDB accepts its quack URI as well as a direct database file.
169
+ if s.lower().startswith("quack:") or s.lower().endswith(".duckdb"):
170
+ return EngineSpec(kind="duckdb", async_=False, dsn=s)
171
+
166
172
  raise ValueError(f"Unsupported DSN: {s}")
167
173
 
168
174
  # Mapping
@@ -360,7 +366,13 @@ class EngineSpec(SerdeMixin):
360
366
  return None
361
367
  redacted: dict[str, object] = {}
362
368
  for key, value in mapping.items():
363
- if str(key).lower() in {"pwd", "password", "pass", "secret"}:
369
+ if str(key).lower() in {
370
+ "pwd",
371
+ "password",
372
+ "pass",
373
+ "secret",
374
+ "token",
375
+ }:
364
376
  redacted[key] = "***"
365
377
  else:
366
378
  redacted[key] = value
@@ -29,14 +29,21 @@ def load_engine_plugins() -> None:
29
29
  if _LOADED:
30
30
  return
31
31
 
32
- try:
33
- discovered = entry_points(group="tigrbl.engine_plugins")
34
- except TypeError:
35
- discovered = entry_points().get("tigrbl.engine_plugins", []) # pragma: no cover
32
+ discovered = []
33
+ for group in ("tigrbl.engine_plugins", "tigrbl.engine"):
34
+ try:
35
+ discovered.extend(entry_points(group=group))
36
+ except TypeError: # pragma: no cover
37
+ discovered.extend(entry_points().get(group, []))
36
38
 
39
+ loaded_names: set[str] = set()
37
40
  for ep in discovered:
41
+ name = str(getattr(ep, "name", ""))
42
+ if name in loaded_names:
43
+ continue
38
44
  try:
39
45
  _invoke_plugin(ep.load())
46
+ loaded_names.add(name)
40
47
  except Exception:
41
48
  # EngineSpec handles missing registrations with explicit error text.
42
49
  continue
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from tigrbl_core._compat import warn_legacy_engine_session_name
6
+
7
+ from .engine_session_spec import (
8
+ EngineSessionCfg,
9
+ EngineSessionSpec,
10
+ engine_session_spec,
11
+ readonly,
12
+ tx_read_committed,
13
+ tx_repeatable_read,
14
+ tx_serializable,
15
+ )
16
+
17
+
18
+ def __getattr__(name: str) -> Any:
19
+ if name == "SessionSpec":
20
+ warn_legacy_engine_session_name("SessionSpec", "EngineSessionSpec")
21
+ return EngineSessionSpec
22
+ if name == "SessionCfg":
23
+ warn_legacy_engine_session_name("SessionCfg", "EngineSessionCfg")
24
+ return EngineSessionCfg
25
+ if name == "session_spec":
26
+ warn_legacy_engine_session_name("session_spec", "engine_session_spec")
27
+ return engine_session_spec
28
+ raise AttributeError(name)
29
+
30
+
31
+ __all__ = [
32
+ "EngineSessionCfg",
33
+ "EngineSessionSpec",
34
+ "engine_session_spec",
35
+ "readonly",
36
+ "tx_read_committed",
37
+ "tx_repeatable_read",
38
+ "tx_serializable",
39
+ "SessionCfg",
40
+ "SessionSpec",
41
+ "session_spec",
42
+ ]
@@ -4,6 +4,7 @@ from dataclasses import dataclass, replace
4
4
  from typing import Any, Iterable, Literal, Sequence
5
5
 
6
6
  from .binding_spec import (
7
+ BindingSpec,
7
8
  BytesFramingSpec,
8
9
  HttpJsonRpcBindingSpec,
9
10
  HttpRestBindingSpec,
@@ -234,6 +235,24 @@ def lower_default_bindings_for_op(
234
235
  profile: TableProfileSpec,
235
236
  op: OpSpec,
236
237
  ) -> tuple[LoweredBinding, ...]:
238
+ profile_bindings = tuple(
239
+ binding.spec if isinstance(binding, BindingSpec) else binding
240
+ for binding in profile.default_bindings
241
+ )
242
+ if profile_bindings:
243
+ return tuple(
244
+ LoweredBinding(
245
+ token=_token_from_binding(
246
+ source="table_profile",
247
+ profile=profile.kind,
248
+ op=op,
249
+ binding=binding,
250
+ precedence=1,
251
+ ),
252
+ binding=binding,
253
+ )
254
+ for binding in profile_bindings
255
+ )
237
256
  if _is_custom_or_unknown(op):
238
257
  return ()
239
258
 
@@ -414,69 +414,6 @@ class TableProfileSpec(SerdeMixin):
414
414
  )
415
415
 
416
416
 
417
- def make_profile_op(
418
- target: TargetOp,
419
- *,
420
- alias: str | None = None,
421
- bindings: Sequence[Any] = (),
422
- exchange: Any = "request_response",
423
- ) -> OpSpec:
424
- selected_alias = alias or str(target)
425
- return OpSpec(
426
- alias=selected_alias,
427
- target=target,
428
- arity="collection" if target in _COLLECTION_TARGETS else "member",
429
- bindings=tuple(bindings or ()),
430
- exchange=exchange,
431
- persist="default",
432
- handler=None,
433
- request_model=None,
434
- response_model=None,
435
- hooks=(),
436
- status_code=None,
437
- extra={},
438
- )
439
-
440
-
441
- def make_table_profile(
442
- kind: str,
443
- targets: Sequence[TargetOp],
444
- *,
445
- role: TableProfileRole = "concrete",
446
- default_bindings: Sequence[BindingSpec | TransportBindingSpec] = (),
447
- docs_exposure: DocsExposure = "none",
448
- runtime_exposure: RuntimeExposure = "none",
449
- custom: bool = False,
450
- namespace: str | None = None,
451
- ) -> TableProfileSpec:
452
- return TableProfileSpec(
453
- kind=kind,
454
- role=role,
455
- ops=tuple(make_profile_op(target) for target in targets),
456
- default_bindings=tuple(default_bindings or ()),
457
- docs_exposure=docs_exposure,
458
- runtime_exposure=runtime_exposure,
459
- custom=custom,
460
- namespace=namespace,
461
- )
462
-
463
-
464
- def make_builtin_table_profile(kind: str) -> TableProfileSpec:
465
- try:
466
- row = BUILTIN_TABLE_PROFILE_DEFINITIONS[kind]
467
- except KeyError as exc:
468
- raise TableProfileError(
469
- f"unknown built-in table profile kind {kind!r}"
470
- ) from exc
471
- return make_table_profile(
472
- row.kind,
473
- row.targets,
474
- role=row.role,
475
- docs_exposure=row.docs_exposure,
476
- runtime_exposure=row.runtime_exposure,
477
- )
478
-
479
-
480
417
  def get_builtin_table_profile_definition(kind: str) -> BuiltinTableProfile:
481
418
  try:
482
419
  return BUILTIN_TABLE_PROFILE_DEFINITIONS[kind]
@@ -491,7 +428,21 @@ def iter_builtin_table_profile_definitions() -> tuple[BuiltinTableProfile, ...]:
491
428
 
492
429
 
493
430
  _BUILTIN_TABLE_PROFILES: dict[str, TableProfileSpec] = {
494
- kind: make_builtin_table_profile(kind) for kind in BUILTIN_TABLE_PROFILE_DEFINITIONS
431
+ kind: TableProfileSpec(
432
+ kind=row.kind,
433
+ role=row.role,
434
+ ops=tuple(
435
+ OpSpec(
436
+ alias=str(target),
437
+ target=target,
438
+ arity=("collection" if target in _COLLECTION_TARGETS else "member"),
439
+ )
440
+ for target in row.targets
441
+ ),
442
+ docs_exposure=row.docs_exposure,
443
+ runtime_exposure=row.runtime_exposure,
444
+ )
445
+ for kind, row in BUILTIN_TABLE_PROFILE_DEFINITIONS.items()
495
446
  }
496
447
 
497
448
  PLAIN_TABLE_PROFILE = _BUILTIN_TABLE_PROFILES["plain"]
@@ -553,8 +504,5 @@ __all__ = [
553
504
  "get_builtin_table_profile_definition",
554
505
  "get_table_profile",
555
506
  "iter_builtin_table_profile_definitions",
556
- "make_profile_op",
557
- "make_builtin_table_profile",
558
- "make_table_profile",
559
507
  "register_table_profile",
560
508
  ]
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from tigrbl_core._spec.schema_spec import SchemaArg, SchemaRef
4
4
  from .get_schema import get_schema
5
+ from .result import build_operation_result_json_schema
5
6
  from .builder.build_schema import _build_schema
6
7
  from .builder.list_params import _build_list_params
7
8
  from .spec_json import (
@@ -39,6 +40,7 @@ from .utils import (
39
40
 
40
41
  __all__ = [
41
42
  "get_schema",
43
+ "build_operation_result_json_schema",
42
44
  "SchemaRef",
43
45
  "SchemaArg",
44
46
  "_build_schema",
@@ -215,12 +215,14 @@ def _build_schema(
215
215
  logger.debug("schema: processing column %s (verb=%s)", attr_name, verb)
216
216
 
217
217
  # Determine type and requiredness
218
- py_t = _python_type(col)
218
+ fs = getattr(spec, "field", None)
219
+ py_t = getattr(fs, "py_type", Any) if fs is not None else Any
219
220
  py_t = _normalize_py_type(py_t)
221
+ if py_t is Any:
222
+ py_t = _normalize_py_type(_python_type(col))
220
223
  required = _is_required(col, verb)
221
224
 
222
225
  # Field construction (collect kwargs then create Field once)
223
- fs = getattr(spec, "field", None)
224
226
  field_kwargs: Dict[str, Any] = dict(getattr(fs, "constraints", {}) or {})
225
227
  description = getattr(fs, "description", None)
226
228
  if description and "description" not in field_kwargs: