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.
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/PKG-INFO +7 -7
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/README.md +5 -5
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/pyproject.toml +3 -2
- tigrbl_core-0.4.5.dev8/tigrbl_core/_compat.py +40 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/__init__.py +8 -5
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/binding_spec.py +11 -0
- 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
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/engine_spec.py +13 -1
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/plugins.py +11 -4
- tigrbl_core-0.4.5.dev8/tigrbl_core/_spec/session_spec.py +42 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/table_profile_bindings.py +19 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/table_profile_spec.py +15 -67
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/__init__.py +2 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/builder/build_schema.py +4 -2
- tigrbl_core-0.4.5.dev8/tigrbl_core/schema/builder/helpers.py +235 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/builder/list_params.py +22 -32
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/get_schema.py +10 -5
- tigrbl_core-0.4.5.dev8/tigrbl_core/schema/materialize.py +28 -0
- tigrbl_core-0.4.5.dev8/tigrbl_core/schema/result.py +78 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/surface_chains.json +2 -2
- tigrbl_core-0.4.5.dev4/tigrbl_core/schema/builder/helpers.py +0 -113
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/LICENSE +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/NOTICE +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/alias_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/app_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/column_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/datatypes.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/docs_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/exceptions.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/exposure_policy.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/field_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/headers_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/hook_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/hook_types.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/io_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/middleware_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/monotone.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/op_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/op_utils.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/path_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/registry.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/request_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/response_resolver.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/response_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/response_types.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/router_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/schema_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/serde.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/storage_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/table_registry_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/table_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/transport_stack.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/well_known_spec.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/canonical_json.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/config/__init__.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/config/constants.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/config/defaults.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/config/engine_traversal.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/config/resolver.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/core/__init__.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/op/__init__.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/op/canonical.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/op/collect.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/op/types.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/builder/__init__.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/builder/cache.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/builder/extras.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/builder/strip_parent_fields.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/provenance.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/provenance_inventory.py +0 -0
- {tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/spec_json.py +0 -0
- {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.
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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.
|
|
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/
|
|
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": "
|
|
128
|
-
"tx_repeatable_read": "
|
|
129
|
-
"tx_serializable": "
|
|
130
|
-
"readonly": "
|
|
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
|
-
|
|
8
|
+
EngineSessionCfg = Union["EngineSessionSpec", Mapping[str, Any], None]
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@dataclass(frozen=True)
|
|
12
|
-
class
|
|
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(
|
|
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 =
|
|
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 =
|
|
88
|
+
defaults = EngineSessionSpec()
|
|
83
89
|
vals: MutableMapping[str, Any] = {
|
|
84
|
-
f.name: getattr(self, f.name) for f in fields(
|
|
90
|
+
f.name: getattr(self, f.name) for f in fields(EngineSessionSpec)
|
|
85
91
|
}
|
|
86
|
-
for f in fields(
|
|
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
|
|
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(
|
|
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:
|
|
111
|
+
def from_any(x: EngineSessionCfg) -> Optional["EngineSessionSpec"]:
|
|
106
112
|
if x is None:
|
|
107
113
|
return None
|
|
108
|
-
if isinstance(x,
|
|
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(
|
|
118
|
-
return
|
|
119
|
-
raise TypeError(f"Unsupported
|
|
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
|
|
123
|
-
|
|
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
|
|
133
|
+
return EngineSessionSpec.from_any(cfg or kw) or EngineSessionSpec()
|
|
127
134
|
|
|
128
135
|
|
|
129
|
-
def tx_read_committed(*, read_only: Optional[bool] = None) ->
|
|
130
|
-
return
|
|
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) ->
|
|
134
|
-
return
|
|
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) ->
|
|
138
|
-
return
|
|
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() ->
|
|
142
|
-
return
|
|
148
|
+
def readonly() -> EngineSessionSpec:
|
|
149
|
+
return EngineSessionSpec(read_only=True)
|
|
143
150
|
|
|
144
151
|
|
|
145
152
|
__all__ = [
|
|
146
|
-
"
|
|
147
|
-
"
|
|
148
|
-
"
|
|
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 {
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
+
]
|
{tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/_spec/table_profile_bindings.py
RENAMED
|
@@ -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:
|
|
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",
|
{tigrbl_core-0.4.5.dev4 → tigrbl_core-0.4.5.dev8}/tigrbl_core/schema/builder/build_schema.py
RENAMED
|
@@ -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
|
-
|
|
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:
|