tigrbl-canon 0.1.0__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 (65) hide show
  1. tigrbl_canon-0.1.0/PKG-INFO +55 -0
  2. tigrbl_canon-0.1.0/README.md +29 -0
  3. tigrbl_canon-0.1.0/pyproject.toml +52 -0
  4. tigrbl_canon-0.1.0/tigrbl_canon/__init__.py +14 -0
  5. tigrbl_canon-0.1.0/tigrbl_canon/column/infer/__init__.py +25 -0
  6. tigrbl_canon-0.1.0/tigrbl_canon/column/infer/core.py +92 -0
  7. tigrbl_canon-0.1.0/tigrbl_canon/column/infer/jsonhints.py +44 -0
  8. tigrbl_canon-0.1.0/tigrbl_canon/column/infer/planning.py +133 -0
  9. tigrbl_canon-0.1.0/tigrbl_canon/column/infer/types.py +102 -0
  10. tigrbl_canon-0.1.0/tigrbl_canon/column/infer/utils.py +59 -0
  11. tigrbl_canon-0.1.0/tigrbl_canon/mapping/__init__.py +65 -0
  12. tigrbl_canon-0.1.0/tigrbl_canon/mapping/app_mro_collect.py +14 -0
  13. tigrbl_canon-0.1.0/tigrbl_canon/mapping/apply.py +98 -0
  14. tigrbl_canon-0.1.0/tigrbl_canon/mapping/collect.py +60 -0
  15. tigrbl_canon-0.1.0/tigrbl_canon/mapping/collect_decorated_schemas.py +78 -0
  16. tigrbl_canon-0.1.0/tigrbl_canon/mapping/column_mro_collect.py +3 -0
  17. tigrbl_canon-0.1.0/tigrbl_canon/mapping/columns.py +53 -0
  18. tigrbl_canon-0.1.0/tigrbl_canon/mapping/config_resolver.py +276 -0
  19. tigrbl_canon-0.1.0/tigrbl_canon/mapping/context.py +48 -0
  20. tigrbl_canon-0.1.0/tigrbl_canon/mapping/core_resolver.py +95 -0
  21. tigrbl_canon-0.1.0/tigrbl_canon/mapping/defaults.py +24 -0
  22. tigrbl_canon-0.1.0/tigrbl_canon/mapping/diagnostics.py +20 -0
  23. tigrbl_canon-0.1.0/tigrbl_canon/mapping/engine_resolver.py +385 -0
  24. tigrbl_canon-0.1.0/tigrbl_canon/mapping/handlers/__init__.py +10 -0
  25. tigrbl_canon-0.1.0/tigrbl_canon/mapping/handlers/builder.py +118 -0
  26. tigrbl_canon-0.1.0/tigrbl_canon/mapping/handlers/ctx.py +85 -0
  27. tigrbl_canon-0.1.0/tigrbl_canon/mapping/handlers/identifiers.py +227 -0
  28. tigrbl_canon-0.1.0/tigrbl_canon/mapping/handlers/namespaces.py +50 -0
  29. tigrbl_canon-0.1.0/tigrbl_canon/mapping/handlers/steps.py +121 -0
  30. tigrbl_canon-0.1.0/tigrbl_canon/mapping/hook_mro_collect.py +122 -0
  31. tigrbl_canon-0.1.0/tigrbl_canon/mapping/hooks.py +327 -0
  32. tigrbl_canon-0.1.0/tigrbl_canon/mapping/mapping_resolver.py +5 -0
  33. tigrbl_canon-0.1.0/tigrbl_canon/mapping/model.py +33 -0
  34. tigrbl_canon-0.1.0/tigrbl_canon/mapping/model_helpers.py +151 -0
  35. tigrbl_canon-0.1.0/tigrbl_canon/mapping/model_registry.py +85 -0
  36. tigrbl_canon-0.1.0/tigrbl_canon/mapping/op_mro_collect.py +135 -0
  37. tigrbl_canon-0.1.0/tigrbl_canon/mapping/op_resolver.py +3 -0
  38. tigrbl_canon-0.1.0/tigrbl_canon/mapping/passes.py +74 -0
  39. tigrbl_canon-0.1.0/tigrbl_canon/mapping/plan.py +54 -0
  40. tigrbl_canon-0.1.0/tigrbl_canon/mapping/precedence.py +45 -0
  41. tigrbl_canon-0.1.0/tigrbl_canon/mapping/responses_resolver.py +14 -0
  42. tigrbl_canon-0.1.0/tigrbl_canon/mapping/rest/__init__.py +7 -0
  43. tigrbl_canon-0.1.0/tigrbl_canon/mapping/rest/attach.py +25 -0
  44. tigrbl_canon-0.1.0/tigrbl_canon/mapping/rest/collection.py +290 -0
  45. tigrbl_canon-0.1.0/tigrbl_canon/mapping/rest/common.py +121 -0
  46. tigrbl_canon-0.1.0/tigrbl_canon/mapping/rest/helpers.py +29 -0
  47. tigrbl_canon-0.1.0/tigrbl_canon/mapping/rest/io.py +335 -0
  48. tigrbl_canon-0.1.0/tigrbl_canon/mapping/rest/io_headers.py +49 -0
  49. tigrbl_canon-0.1.0/tigrbl_canon/mapping/rest/member.py +200 -0
  50. tigrbl_canon-0.1.0/tigrbl_canon/mapping/rest/router.py +332 -0
  51. tigrbl_canon-0.1.0/tigrbl_canon/mapping/rest/routing.py +137 -0
  52. tigrbl_canon-0.1.0/tigrbl_canon/mapping/router/common.py +100 -0
  53. tigrbl_canon-0.1.0/tigrbl_canon/mapping/router/include.py +464 -0
  54. tigrbl_canon-0.1.0/tigrbl_canon/mapping/router/resource_proxy.py +13 -0
  55. tigrbl_canon-0.1.0/tigrbl_canon/mapping/router/rpc.py +13 -0
  56. tigrbl_canon-0.1.0/tigrbl_canon/mapping/router_mro_collect.py +45 -0
  57. tigrbl_canon-0.1.0/tigrbl_canon/mapping/rpc.py +13 -0
  58. tigrbl_canon-0.1.0/tigrbl_canon/mapping/runtime_routes.py +20 -0
  59. tigrbl_canon-0.1.0/tigrbl_canon/mapping/schemas/__init__.py +10 -0
  60. tigrbl_canon-0.1.0/tigrbl_canon/mapping/schemas/builder.py +354 -0
  61. tigrbl_canon-0.1.0/tigrbl_canon/mapping/schemas/defaults.py +288 -0
  62. tigrbl_canon-0.1.0/tigrbl_canon/mapping/schemas/utils.py +192 -0
  63. tigrbl_canon-0.1.0/tigrbl_canon/mapping/table.py +5 -0
  64. tigrbl_canon-0.1.0/tigrbl_canon/mapping/table_mro_collect.py +14 -0
  65. tigrbl_canon-0.1.0/tigrbl_canon/mapping/traversal.py +196 -0
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.4
2
+ Name: tigrbl-canon
3
+ Version: 0.1.0
4
+ Summary: Canonical mapping and routing resolution utilities for Tigrbl.
5
+ License-Expression: Apache-2.0
6
+ Keywords: tigrbl,sdk,standards,framework
7
+ Author: Jacob Stewart
8
+ Author-email: jacob@swarmauri.com
9
+ Requires-Python: >=3.10,<3.13
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Development Status :: 1 - Planning
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Requires-Dist: tigrbl
19
+ Requires-Dist: tigrbl-atoms
20
+ Requires-Dist: tigrbl-base
21
+ Requires-Dist: tigrbl-core
22
+ Requires-Dist: tigrbl-ops-oltp
23
+ Requires-Dist: tigrbl-runtime
24
+ Description-Content-Type: text/markdown
25
+
26
+ ![Tigrbl branding](https://github.com/swarmauri/swarmauri-sdk/blob/a170683ecda8ca1c4f912c966d4499649ffb8224/assets/tigrbl.brand.theme.svg)
27
+
28
+ # tigrbl-canon
29
+
30
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/tigrbl-canon.svg) ![Hits](https://hits.sh/github.com/swarmauri/swarmauri-sdk.svg) ![Python Versions](https://img.shields.io/pypi/pyversions/tigrbl-canon.svg) ![License](https://img.shields.io/pypi/l/tigrbl-canon.svg) ![Version](https://img.shields.io/pypi/v/tigrbl-canon.svg)
31
+
32
+ ## Features
33
+
34
+ - Modular package in the Tigrbl namespace.
35
+ - Supports Python 3.10 through 3.12.
36
+ - Distributed as part of the swarmauri-sdk workspace.
37
+
38
+ ## Installation
39
+
40
+ ### uv
41
+
42
+ ```bash
43
+ uv add tigrbl-canon
44
+ ```
45
+
46
+ ### pip
47
+
48
+ ```bash
49
+ pip install tigrbl-canon
50
+ ```
51
+
52
+ ## Usage
53
+
54
+ Import from the shared package-specific module namespaces after installation in your environment.
55
+
@@ -0,0 +1,29 @@
1
+ ![Tigrbl branding](https://github.com/swarmauri/swarmauri-sdk/blob/a170683ecda8ca1c4f912c966d4499649ffb8224/assets/tigrbl.brand.theme.svg)
2
+
3
+ # tigrbl-canon
4
+
5
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/tigrbl-canon.svg) ![Hits](https://hits.sh/github.com/swarmauri/swarmauri-sdk.svg) ![Python Versions](https://img.shields.io/pypi/pyversions/tigrbl-canon.svg) ![License](https://img.shields.io/pypi/l/tigrbl-canon.svg) ![Version](https://img.shields.io/pypi/v/tigrbl-canon.svg)
6
+
7
+ ## Features
8
+
9
+ - Modular package in the Tigrbl namespace.
10
+ - Supports Python 3.10 through 3.12.
11
+ - Distributed as part of the swarmauri-sdk workspace.
12
+
13
+ ## Installation
14
+
15
+ ### uv
16
+
17
+ ```bash
18
+ uv add tigrbl-canon
19
+ ```
20
+
21
+ ### pip
22
+
23
+ ```bash
24
+ pip install tigrbl-canon
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ Import from the shared package-specific module namespaces after installation in your environment.
@@ -0,0 +1,52 @@
1
+ [project]
2
+ name = "tigrbl-canon"
3
+ version = "0.1.0"
4
+ description = "Canonical mapping and routing resolution utilities for Tigrbl."
5
+ license = "Apache-2.0"
6
+ readme = "README.md"
7
+ repository = "http://github.com/swarmauri/swarmauri-sdk"
8
+ requires-python = ">=3.10,<3.13"
9
+ classifiers = [
10
+ "License :: OSI Approved :: Apache Software License",
11
+ "Development Status :: 1 - Planning",
12
+ "Programming Language :: Python :: 3.10",
13
+ "Programming Language :: Python :: 3.11",
14
+ "Programming Language :: Python :: 3.12",
15
+ "Programming Language :: Python",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3 :: Only",
18
+ ]
19
+ authors = [{ name = "Jacob Stewart", email = "jacob@swarmauri.com" }]
20
+ dependencies = [
21
+ "tigrbl-ops-oltp",
22
+ "tigrbl-base",
23
+ "tigrbl-core",
24
+ "tigrbl-runtime",
25
+ "tigrbl-atoms",
26
+ "tigrbl",
27
+ ]
28
+ keywords = ["tigrbl", "sdk", "standards", "framework"]
29
+
30
+ [tool.uv.sources]
31
+ "tigrbl-ops-oltp" = { workspace = true }
32
+ "tigrbl-base" = { workspace = true }
33
+ "tigrbl-core" = { workspace = true }
34
+ "tigrbl-runtime" = { workspace = true }
35
+ "tigrbl-atoms" = { workspace = true }
36
+ "tigrbl" = { workspace = true }
37
+
38
+ [build-system]
39
+ requires = ["poetry-core>=1.0.0"]
40
+ build-backend = "poetry.core.masonry.api"
41
+
42
+
43
+ [tool.poetry]
44
+ packages = [
45
+ { include = "tigrbl_canon" },
46
+ ]
47
+
48
+ [dependency-groups]
49
+ dev = [
50
+ "pytest>=8.0",
51
+ "ruff>=0.9",
52
+ ]
@@ -0,0 +1,14 @@
1
+ """Tigrbl canon compatibility package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import warnings
6
+
7
+ _DEPRECATION_MESSAGE = (
8
+ "tigrbl_canon is deprecated, not supported anymore, and likely to break. "
9
+ "Migrate away from tigrbl_canon imports as soon as possible."
10
+ )
11
+
12
+ warnings.warn(_DEPRECATION_MESSAGE, DeprecationWarning, stacklevel=2)
13
+
14
+ __all__ = ["_DEPRECATION_MESSAGE"]
@@ -0,0 +1,25 @@
1
+ from .core import infer
2
+ from .types import (
3
+ Email,
4
+ Phone,
5
+ DataKind,
6
+ PyTypeInfo,
7
+ SATypePlan,
8
+ JsonHint,
9
+ Inferred,
10
+ InferenceError,
11
+ UnsupportedType,
12
+ )
13
+
14
+ __all__ = [
15
+ "infer",
16
+ "Email",
17
+ "Phone",
18
+ "DataKind",
19
+ "PyTypeInfo",
20
+ "SATypePlan",
21
+ "JsonHint",
22
+ "Inferred",
23
+ "InferenceError",
24
+ "UnsupportedType",
25
+ ]
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, Optional, get_origin
4
+
5
+ from .types import DataKind, PyTypeInfo, Inferred
6
+ from .utils import _strip_optional, _strip_annotated, _array_item, _is_enum
7
+ from .planning import _plan_sa_type
8
+ from .jsonhints import _json_hint
9
+
10
+
11
+ def infer(
12
+ annotation: Any,
13
+ *,
14
+ prefer_dialect: Optional[str] = "postgresql",
15
+ max_length: Optional[int] = None,
16
+ decimal_precision: Optional[int] = None,
17
+ decimal_scale: Optional[int] = None,
18
+ ) -> Inferred:
19
+ """Bind-time inference from Python annotation to DataKind and hints."""
20
+ base, is_opt = _strip_optional(annotation)
21
+ base, meta = _strip_annotated(base)
22
+
23
+ enum_cls = _is_enum(base)
24
+ item_tp = _array_item(base)
25
+
26
+ array_item_info: Optional[PyTypeInfo] = None
27
+ if item_tp is not None:
28
+ nested = infer(
29
+ item_tp,
30
+ prefer_dialect=prefer_dialect,
31
+ max_length=max_length,
32
+ decimal_precision=decimal_precision,
33
+ decimal_scale=decimal_scale,
34
+ )
35
+ array_item_info = nested.py
36
+
37
+ py_info = PyTypeInfo(
38
+ base=base,
39
+ is_optional=is_opt,
40
+ enum_cls=enum_cls,
41
+ array_item=array_item_info,
42
+ annotated=meta,
43
+ )
44
+
45
+ import datetime as _dt
46
+ import decimal as _dc
47
+ import uuid as _uuid
48
+
49
+ origin = get_origin(base)
50
+
51
+ if enum_cls is not None:
52
+ kind = DataKind.ENUM
53
+ elif item_tp is not None:
54
+ kind = DataKind.ARRAY
55
+ elif base in (str,):
56
+ kind = DataKind.STRING
57
+ elif base in (bytes, bytearray, memoryview):
58
+ kind = DataKind.BYTES
59
+ elif base in (bool,):
60
+ kind = DataKind.BOOL
61
+ elif base in (int,):
62
+ kind = DataKind.INT
63
+ elif base in (float,):
64
+ kind = DataKind.FLOAT
65
+ elif base in (_dc.Decimal,):
66
+ kind = DataKind.DECIMAL
67
+ elif base in (_dt.datetime,):
68
+ kind = DataKind.DATETIME
69
+ elif base in (_dt.date,):
70
+ kind = DataKind.DATE
71
+ elif base in (_dt.time,):
72
+ kind = DataKind.TIME
73
+ elif base in (_uuid.UUID,):
74
+ kind = DataKind.UUID
75
+ else:
76
+ if origin in (dict, Dict):
77
+ kind = DataKind.JSON
78
+ else:
79
+ kind = DataKind.JSON
80
+
81
+ sa = _plan_sa_type(
82
+ kind,
83
+ py_info,
84
+ prefer_dialect=prefer_dialect,
85
+ max_length=max_length,
86
+ decimal_precision=decimal_precision,
87
+ decimal_scale=decimal_scale,
88
+ )
89
+
90
+ jh = _json_hint(kind, py_info, max_length=max_length)
91
+
92
+ return Inferred(kind=kind, py=py_info, sa=sa, json=jh, nullable=is_opt)
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ from .types import DataKind, PyTypeInfo, JsonHint, Email, Phone
6
+ from .planning import _nested_kind_from_py
7
+
8
+
9
+ def _json_hint(
10
+ kind: DataKind, py: PyTypeInfo, *, max_length: Optional[int]
11
+ ) -> JsonHint:
12
+ if kind is DataKind.STRING:
13
+ fmt = None
14
+ if Email in py.annotated:
15
+ fmt = "email"
16
+ if Phone in py.annotated:
17
+ fmt = "phone"
18
+ return JsonHint(type="string", format=fmt, maxLength=max_length)
19
+ if kind is DataKind.BYTES:
20
+ return JsonHint(type="string", format="byte")
21
+ if kind is DataKind.BOOL:
22
+ return JsonHint(type="boolean")
23
+ if kind is DataKind.INT or kind is DataKind.BIGINT:
24
+ return JsonHint(type="integer")
25
+ if kind is DataKind.FLOAT or kind is DataKind.DECIMAL:
26
+ return JsonHint(type="number")
27
+ if kind is DataKind.DATE:
28
+ return JsonHint(type="string", format="date")
29
+ if kind is DataKind.TIME:
30
+ return JsonHint(type="string", format="time")
31
+ if kind is DataKind.DATETIME:
32
+ return JsonHint(type="string", format="date-time")
33
+ if kind is DataKind.UUID:
34
+ return JsonHint(type="string", format="uuid")
35
+ if kind is DataKind.JSON:
36
+ return JsonHint(type="object")
37
+ if kind is DataKind.ENUM and py.enum_cls:
38
+ return JsonHint(type="string", enum=[e.name for e in py.enum_cls])
39
+ if kind is DataKind.ARRAY and py.array_item:
40
+ elem_kind = _nested_kind_from_py(py.array_item)
41
+ return JsonHint(
42
+ type="array", items=_json_hint(elem_kind, py.array_item, max_length=None)
43
+ )
44
+ return JsonHint(type="string")
@@ -0,0 +1,133 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, Optional
4
+
5
+ from .types import DataKind, PyTypeInfo, SATypePlan, InferenceError
6
+
7
+
8
+ def _plan_sa_type(
9
+ kind: DataKind,
10
+ py: PyTypeInfo,
11
+ *,
12
+ prefer_dialect: Optional[str],
13
+ max_length: Optional[int],
14
+ decimal_precision: Optional[int],
15
+ decimal_scale: Optional[int],
16
+ ) -> SATypePlan:
17
+ d = prefer_dialect
18
+
19
+ if kind is DataKind.STRING:
20
+ if max_length and max_length > 0:
21
+ return SATypePlan(
22
+ name="String", args=(max_length,), kwargs={}, dialect=None
23
+ )
24
+ return SATypePlan(name="String", args=(), kwargs={}, dialect=None)
25
+
26
+ if kind is DataKind.TEXT:
27
+ return SATypePlan(name="Text", args=(), kwargs={}, dialect=None)
28
+
29
+ if kind is DataKind.BYTES:
30
+ return SATypePlan(name="LargeBinary", args=(), kwargs={}, dialect=None)
31
+
32
+ if kind is DataKind.BOOL:
33
+ return SATypePlan(name="Boolean", args=(), kwargs={}, dialect=None)
34
+
35
+ if kind is DataKind.INT:
36
+ return SATypePlan(name="Integer", args=(), kwargs={}, dialect=None)
37
+
38
+ if kind is DataKind.BIGINT:
39
+ return SATypePlan(name="BigInteger", args=(), kwargs={}, dialect=None)
40
+
41
+ if kind is DataKind.FLOAT:
42
+ return SATypePlan(name="Float", args=(), kwargs={}, dialect=None)
43
+
44
+ if kind is DataKind.DECIMAL:
45
+ kwargs: Dict[str, Any] = {}
46
+ if decimal_precision is not None:
47
+ kwargs["precision"] = decimal_precision
48
+ if decimal_scale is not None:
49
+ kwargs["scale"] = decimal_scale
50
+ return SATypePlan(name="Numeric", args=(), kwargs=kwargs, dialect=None)
51
+
52
+ if kind is DataKind.DATE:
53
+ return SATypePlan(name="Date", args=(), kwargs={}, dialect=None)
54
+
55
+ if kind is DataKind.TIME:
56
+ return SATypePlan(name="Time", args=(), kwargs={"timezone": True}, dialect=None)
57
+
58
+ if kind is DataKind.DATETIME:
59
+ return SATypePlan(
60
+ name="DateTime", args=(), kwargs={"timezone": True}, dialect=None
61
+ )
62
+
63
+ if kind is DataKind.UUID:
64
+ if d == "postgresql":
65
+ return SATypePlan(
66
+ name="UUID", args=(), kwargs={"as_uuid": True}, dialect="postgresql"
67
+ )
68
+ return SATypePlan(name="String", args=(36,), kwargs={}, dialect=None)
69
+
70
+ if kind is DataKind.JSON:
71
+ if d == "postgresql":
72
+ return SATypePlan(name="JSONB", args=(), kwargs={}, dialect="postgresql")
73
+ return SATypePlan(name="JSON", args=(), kwargs={}, dialect=None)
74
+
75
+ if kind is DataKind.ENUM:
76
+ if not py.enum_cls:
77
+ raise InferenceError("ENUM kind requires enum_cls in PyTypeInfo")
78
+ return SATypePlan(
79
+ name="Enum", args=(py.enum_cls,), kwargs={"native_enum": True}, dialect=None
80
+ )
81
+
82
+ if kind is DataKind.ARRAY:
83
+ if not py.array_item:
84
+ raise InferenceError("ARRAY kind requires array_item in PyTypeInfo")
85
+ elem_plan = _plan_sa_type(
86
+ _nested_kind_from_py(py.array_item),
87
+ py.array_item,
88
+ prefer_dialect=prefer_dialect,
89
+ max_length=None,
90
+ decimal_precision=None,
91
+ decimal_scale=None,
92
+ )
93
+ if prefer_dialect == "postgresql":
94
+ return SATypePlan(
95
+ name="ARRAY",
96
+ args=(elem_plan.name,),
97
+ kwargs={},
98
+ dialect="postgresql",
99
+ )
100
+ return SATypePlan(name="JSON", args=(), kwargs={}, dialect=None)
101
+
102
+ raise InferenceError(f"Cannot plan SA type for kind={kind!r}")
103
+
104
+
105
+ def _nested_kind_from_py(nested_py: PyTypeInfo) -> DataKind:
106
+ if nested_py.enum_cls is not None:
107
+ return DataKind.ENUM
108
+ b = nested_py.base
109
+ import datetime as _dt
110
+ import decimal as _dc
111
+ import uuid as _uuid
112
+
113
+ if b is str:
114
+ return DataKind.STRING
115
+ if b in (bytes, bytearray, memoryview):
116
+ return DataKind.BYTES
117
+ if b is bool:
118
+ return DataKind.BOOL
119
+ if b is int:
120
+ return DataKind.INT
121
+ if b is float:
122
+ return DataKind.FLOAT
123
+ if b is _dc.Decimal:
124
+ return DataKind.DECIMAL
125
+ if b is _dt.datetime:
126
+ return DataKind.DATETIME
127
+ if b is _dt.date:
128
+ return DataKind.DATE
129
+ if b is _dt.time:
130
+ return DataKind.TIME
131
+ if b is _uuid.UUID:
132
+ return DataKind.UUID
133
+ return DataKind.JSON
@@ -0,0 +1,102 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from enum import Enum
5
+ from typing import Any, Dict, List, Optional, Tuple, Type
6
+
7
+
8
+ # ---------------------------------------------------------------------------
9
+ # Public markers (wire/adapters provide validation/normalization at runtime)
10
+ # ---------------------------------------------------------------------------
11
+
12
+
13
+ class Email:
14
+ """Marker indicating email string semantics."""
15
+
16
+
17
+ class Phone:
18
+ """Marker indicating E.164 phone string semantics."""
19
+
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Portable data-kind (DB- and adapter-agnostic)
23
+ # ---------------------------------------------------------------------------
24
+
25
+
26
+ class DataKind(str, Enum):
27
+ STRING = "string"
28
+ TEXT = "text"
29
+ BYTES = "bytes"
30
+ BOOL = "bool"
31
+ INT = "int"
32
+ BIGINT = "bigint"
33
+ FLOAT = "float"
34
+ DECIMAL = "decimal"
35
+ DATE = "date"
36
+ TIME = "time"
37
+ DATETIME = "datetime"
38
+ UUID = "uuid"
39
+ JSON = "json"
40
+ ENUM = "enum"
41
+ ARRAY = "array"
42
+
43
+
44
+ # ---------------------------------------------------------------------------
45
+ # Structured outputs
46
+ # ---------------------------------------------------------------------------
47
+
48
+
49
+ @dataclass(frozen=True)
50
+ class PyTypeInfo:
51
+ """Normalized info about the Python-side type annotation."""
52
+
53
+ base: Any
54
+ is_optional: bool = False
55
+ enum_cls: Optional[Type[Enum]] = None
56
+ array_item: Optional["PyTypeInfo"] = None
57
+ annotated: Tuple[Any, ...] = ()
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class SATypePlan:
62
+ """Declarative plan for constructing an SA column type downstream."""
63
+
64
+ name: str # e.g., "UUID", "String", "JSONB", "Enum", "ARRAY"
65
+ args: Tuple[Any, ...] # positional args (e.g., (enum_cls,))
66
+ kwargs: Dict[str, Any] # keyword args (e.g., {"as_uuid": True})
67
+ dialect: Optional[str] = None # e.g., "postgresql" for JSONB/UUID/ARRAY
68
+
69
+
70
+ @dataclass(frozen=True)
71
+ class JsonHint:
72
+ """Minimal JSON Schema-ish hints for docs."""
73
+
74
+ type: str
75
+ format: Optional[str] = None
76
+ maxLength: Optional[int] = None
77
+ enum: Optional[List[str]] = None
78
+ items: Optional["JsonHint"] = None
79
+
80
+
81
+ @dataclass(frozen=True)
82
+ class Inferred:
83
+ """Primary product of inference."""
84
+
85
+ kind: DataKind
86
+ py: PyTypeInfo
87
+ sa: SATypePlan
88
+ json: JsonHint
89
+ nullable: bool
90
+
91
+
92
+ # ---------------------------------------------------------------------------
93
+ # Errors
94
+ # ---------------------------------------------------------------------------
95
+
96
+
97
+ class InferenceError(ValueError):
98
+ """Base class for inference-related errors."""
99
+
100
+
101
+ class UnsupportedType(InferenceError):
102
+ """Raised when a type cannot be inferred."""
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+ from typing import (
5
+ Any,
6
+ List,
7
+ Optional,
8
+ Tuple,
9
+ Type,
10
+ Union,
11
+ get_args,
12
+ get_origin,
13
+ Annotated,
14
+ )
15
+
16
+
17
+ def _strip_optional(tp: Any) -> Tuple[Any, bool]:
18
+ """Return (inner_type, is_optional) for Optional[T] / Union[T, None]."""
19
+ origin = get_origin(tp)
20
+ if origin is Union:
21
+ args = tuple(a for a in get_args(tp))
22
+ if len(args) == 2 and type(None) in args:
23
+ inner = args[0] if args[1] is type(None) else args[1]
24
+ return inner, True
25
+ return tp, False
26
+
27
+
28
+ def _strip_annotated(tp: Any) -> Tuple[Any, Tuple[Any, ...]]:
29
+ """Return (base, metadata) for Annotated[base, *meta]; otherwise (tp, ())."""
30
+ origin = get_origin(tp)
31
+ if origin is Annotated:
32
+ args = get_args(tp)
33
+ if len(args) >= 1:
34
+ base, meta = args[0], tuple(args[1:])
35
+ return base, meta
36
+ return tp, ()
37
+
38
+
39
+ def _array_item(tp: Any) -> Optional[Any]:
40
+ origin = get_origin(tp)
41
+ if origin in (list, List, tuple, Tuple, set, frozenset):
42
+ args = get_args(tp)
43
+ if not args:
44
+ return Any
45
+ if origin in (tuple, Tuple) and len(args) == 2 and args[1] is Ellipsis:
46
+ return args[0]
47
+ if len(args) == 1:
48
+ return args[0]
49
+ return Any
50
+ return None
51
+
52
+
53
+ def _is_enum(tp: Any) -> Optional[Type[Enum]]:
54
+ try:
55
+ if isinstance(tp, type) and issubclass(tp, Enum):
56
+ return tp
57
+ except Exception:
58
+ pass
59
+ return None
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib import import_module
4
+ import warnings
5
+
6
+ from tigrbl_canon import _DEPRECATION_MESSAGE
7
+
8
+ warnings.warn(_DEPRECATION_MESSAGE, DeprecationWarning, stacklevel=2)
9
+
10
+ __all__ = [
11
+ "bind",
12
+ "rebind",
13
+ "build_schemas",
14
+ "build_hooks",
15
+ "build_handlers",
16
+ "register_rpc",
17
+ "build_rest",
18
+ "bind_response",
19
+ "include_table",
20
+ "include_tables",
21
+ "rpc_call",
22
+ "MRO_COLLECTORS",
23
+ "RESOLVERS",
24
+ "INSTALLS",
25
+ "collect",
26
+ "install",
27
+ "collect_engine_bindings",
28
+ "install_engine_bindings",
29
+ "install_from_objects",
30
+ ]
31
+
32
+
33
+ def __getattr__(name: str):
34
+ if name in {"bind", "rebind"}:
35
+ return getattr(import_module(".model", __name__), name)
36
+ if name == "build_schemas":
37
+ return import_module(".schemas", __name__).build_and_attach
38
+ if name == "build_hooks":
39
+ return import_module(".hooks", __name__).normalize_and_attach
40
+ if name == "build_handlers":
41
+ return import_module(".handlers", __name__).build_and_attach
42
+ if name == "register_rpc":
43
+ return import_module(".rpc", __name__).register_and_attach
44
+ if name == "build_rest":
45
+ return import_module(".rest", __name__).build_router_and_attach
46
+ if name == "bind_response":
47
+ return import_module(".responses_resolver", __name__).resolve_response_spec
48
+ if name in {"include_table", "include_tables"}:
49
+ return getattr(import_module(".router.include", __name__), name)
50
+ if name == "rpc_call":
51
+ # ``tigrbl.mapping.rpc_call`` should execute an RPC op end-to-end and
52
+ # return the serialized operation result.
53
+ return getattr(import_module(".router.rpc", __name__), name)
54
+ if name in {
55
+ "INSTALLS",
56
+ "MRO_COLLECTORS",
57
+ "RESOLVERS",
58
+ "collect",
59
+ "install",
60
+ "collect_engine_bindings",
61
+ "install_engine_bindings",
62
+ "install_from_objects",
63
+ }:
64
+ return getattr(import_module(".traversal", __name__), name)
65
+ raise AttributeError(name)
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import lru_cache
4
+
5
+ from tigrbl_core._spec.app_spec import AppSpec
6
+
7
+
8
+ @lru_cache(maxsize=None)
9
+ def mro_collect_app_spec(app: type) -> AppSpec:
10
+ """Collect AppSpec-like declarations across the app's MRO."""
11
+ return AppSpec.collect(app)
12
+
13
+
14
+ __all__ = ["mro_collect_app_spec"]