tigrbl-core 0.4.5.dev1__tar.gz → 0.4.5.dev6__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 (71) hide show
  1. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/PKG-INFO +7 -7
  2. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/README.md +5 -5
  3. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/pyproject.toml +3 -2
  4. tigrbl_core-0.4.5.dev6/tigrbl_core/_compat.py +40 -0
  5. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/__init__.py +8 -5
  6. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/binding_spec.py +11 -0
  7. tigrbl_core-0.4.5.dev1/tigrbl_core/_spec/session_spec.py → tigrbl_core-0.4.5.dev6/tigrbl_core/_spec/engine_session_spec.py +37 -30
  8. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/engine_spec.py +13 -1
  9. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/op_spec.py +48 -3
  10. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/plugins.py +11 -4
  11. tigrbl_core-0.4.5.dev6/tigrbl_core/_spec/session_spec.py +42 -0
  12. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/table_profile_bindings.py +19 -0
  13. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/table_profile_spec.py +15 -67
  14. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/schema/__init__.py +2 -0
  15. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/schema/builder/build_schema.py +4 -2
  16. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/schema/builder/helpers.py +113 -1
  17. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/schema/builder/list_params.py +22 -32
  18. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/schema/get_schema.py +10 -5
  19. tigrbl_core-0.4.5.dev6/tigrbl_core/schema/materialize.py +28 -0
  20. tigrbl_core-0.4.5.dev6/tigrbl_core/schema/result.py +78 -0
  21. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/schema/surface_chains.json +2 -2
  22. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/LICENSE +0 -0
  23. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/NOTICE +0 -0
  24. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/alias_spec.py +0 -0
  25. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/app_spec.py +0 -0
  26. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/column_spec.py +0 -0
  27. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/datatypes.py +0 -0
  28. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/docs_spec.py +0 -0
  29. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/exceptions.py +0 -0
  30. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/exposure_policy.py +0 -0
  31. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/field_spec.py +0 -0
  32. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/headers_spec.py +0 -0
  33. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/hook_spec.py +0 -0
  34. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/hook_types.py +0 -0
  35. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/io_spec.py +0 -0
  36. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/middleware_spec.py +0 -0
  37. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/monotone.py +0 -0
  38. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/op_utils.py +0 -0
  39. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/path_spec.py +0 -0
  40. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/registry.py +0 -0
  41. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/request_spec.py +0 -0
  42. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/response_resolver.py +0 -0
  43. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/response_spec.py +0 -0
  44. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/response_types.py +0 -0
  45. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/router_spec.py +0 -0
  46. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/schema_spec.py +0 -0
  47. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/serde.py +0 -0
  48. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/storage_spec.py +0 -0
  49. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/table_registry_spec.py +0 -0
  50. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/table_spec.py +0 -0
  51. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/transport_stack.py +0 -0
  52. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/_spec/well_known_spec.py +0 -0
  53. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/canonical_json.py +0 -0
  54. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/config/__init__.py +0 -0
  55. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/config/constants.py +0 -0
  56. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/config/defaults.py +0 -0
  57. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/config/engine_traversal.py +0 -0
  58. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/config/resolver.py +0 -0
  59. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/core/__init__.py +0 -0
  60. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/op/__init__.py +0 -0
  61. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/op/canonical.py +0 -0
  62. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/op/collect.py +0 -0
  63. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/op/types.py +0 -0
  64. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/schema/builder/__init__.py +0 -0
  65. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/schema/builder/cache.py +0 -0
  66. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/schema/builder/extras.py +0 -0
  67. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/schema/builder/strip_parent_fields.py +0 -0
  68. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/schema/provenance.py +0 -0
  69. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/schema/provenance_inventory.py +0 -0
  70. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/tigrbl_core/schema/spec_json.py +0 -0
  71. {tigrbl_core-0.4.5.dev1 → tigrbl_core-0.4.5.dev6}/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.dev1
3
+ Version: 0.4.5.dev6
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.dev1"
3
+ version = "0.4.5.dev6"
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
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
+ from importlib import metadata as importlib_metadata
4
5
  import inspect
6
+ import warnings
5
7
  from typing import (
6
8
  TYPE_CHECKING,
7
9
  Any,
@@ -59,6 +61,11 @@ TargetOp = Literal[
59
61
  "custom",
60
62
  ]
61
63
 
64
+ _CANONICAL_OP_SPEC_ATTR = "__tigrbl_op_spec__"
65
+ _LEGACY_OP_DECL_ATTR = "__tigrbl_op_decl__"
66
+ _LEGACY_OP_DECL_FALLBACK_VERSION = "0.4.5.dev1"
67
+ _LEGACY_OP_DECL_FAILS_IN = (0, 7)
68
+
62
69
 
63
70
  if TYPE_CHECKING: # pragma: no cover
64
71
  from .._spec.engine_spec import EngineCfg
@@ -151,6 +158,46 @@ class OpSpec(SerdeMixin):
151
158
 
152
159
 
153
160
  _ALIAS_RE = __import__("re").compile(r"^[a-z][a-z0-9_]*$")
161
+ _VERSION_RE = __import__("re").compile(r"^\s*(\d+)\.(\d+)")
162
+
163
+
164
+ def _parse_major_minor(version: str) -> tuple[int, int]:
165
+ match = _VERSION_RE.match(version)
166
+ if match is None:
167
+ return (0, 0)
168
+ return (int(match.group(1)), int(match.group(2)))
169
+
170
+
171
+ def _current_major_minor() -> tuple[int, int]:
172
+ try:
173
+ version = importlib_metadata.version("tigrbl-core")
174
+ except importlib_metadata.PackageNotFoundError:
175
+ version = _LEGACY_OP_DECL_FALLBACK_VERSION
176
+ return _parse_major_minor(version)
177
+
178
+
179
+ def _warn_or_fail_legacy_op_decl() -> None:
180
+ if _current_major_minor() >= _LEGACY_OP_DECL_FAILS_IN:
181
+ raise RuntimeError(
182
+ "__tigrbl_op_decl__ is no longer supported; use "
183
+ "__tigrbl_op_spec__ for operation metadata."
184
+ )
185
+ warnings.warn(
186
+ "__tigrbl_op_decl__ is deprecated and will be removed in "
187
+ "tigrbl-core 0.7.0; use __tigrbl_op_spec__ instead.",
188
+ DeprecationWarning,
189
+ stacklevel=3,
190
+ )
191
+
192
+
193
+ def _get_decorated_op_spec(func: Any) -> Optional["OpSpec"]:
194
+ op_spec: OpSpec | None = getattr(func, _CANONICAL_OP_SPEC_ATTR, None)
195
+ if op_spec is not None:
196
+ return op_spec
197
+ op_spec = getattr(func, _LEGACY_OP_DECL_ATTR, None)
198
+ if op_spec is not None:
199
+ _warn_or_fail_legacy_op_decl()
200
+ return op_spec
154
201
 
155
202
 
156
203
  def _ensure_spec_table(spec: Any, table: type) -> OpSpec:
@@ -484,9 +531,7 @@ def _mro_collect_decorated_ops(table: type) -> List["OpSpec"]:
484
531
  if name in seen:
485
532
  continue
486
533
  func = _unwrap(attr)
487
- op_spec: OpSpec | None = getattr(func, "__tigrbl_op_spec__", None)
488
- if op_spec is None:
489
- op_spec = getattr(func, "__tigrbl_op_decl__", None)
534
+ op_spec = _get_decorated_op_spec(func)
490
535
  if op_spec is None:
491
536
  continue
492
537
 
@@ -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