tigrbl-orm 0.4.5.dev4__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.
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/PKG-INFO +7 -8
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/README.md +5 -6
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/pyproject.toml +2 -2
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/mixins/__init__.py +11 -1
- tigrbl_orm-0.4.5.dev6/tigrbl_orm/orm/mixins/principals.py +214 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/mixins/utils.py +1 -13
- tigrbl_orm-0.4.5.dev4/tigrbl_orm/orm/mixins/principals.py +0 -98
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/LICENSE +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/NOTICE +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/__init__.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/mixins/_RowBound.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/mixins/bootstrappable.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/mixins/bound.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/mixins/edges.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/mixins/fields.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/mixins/hierarchy.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/mixins/key_digest.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/mixins/lifecycle.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/mixins/locks.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/mixins/markers.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/mixins/operations.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/mixins/ownable.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/mixins/tenant_bound.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/mixins/upsertable.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/tables/__init__.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/tables/_base.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/tables/audit.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/tables/client.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/tables/group.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/tables/org.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/tables/rbac.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/tables/status.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/tables/tenant.py +0 -0
- {tigrbl_orm-0.4.5.dev4 → tigrbl_orm-0.4.5.dev6}/tigrbl_orm/orm/tables/user.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tigrbl-orm
|
|
3
|
-
Version: 0.4.5.
|
|
3
|
+
Version: 0.4.5.dev6
|
|
4
4
|
Summary: SQLAlchemy ORM tables, mixins, columns, model helpers, and persistence primitives for Tigrbl applications.
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -230,7 +230,7 @@ Project-URL: Discord, https://discord.gg/K4YTAPapjR
|
|
|
230
230
|
Project-URL: Homepage, https://github.com/tigrbl/tigrbl
|
|
231
231
|
Project-URL: Issues, https://github.com/tigrbl/tigrbl/issues
|
|
232
232
|
Project-URL: Organization, https://github.com/tigrbl
|
|
233
|
-
Project-URL: Repository, https://github.com/tigrbl/tigrbl/tree/master/pkgs/
|
|
233
|
+
Project-URL: Repository, https://github.com/tigrbl/tigrbl/tree/master/pkgs/30_orm/tigrbl_orm
|
|
234
234
|
Description-Content-Type: text/markdown
|
|
235
235
|
|
|
236
236
|
<div align="center">
|
|
@@ -240,7 +240,7 @@ Description-Content-Type: text/markdown
|
|
|
240
240
|
<a href="https://pypi.org/project/tigrbl-orm/"><img src="https://img.shields.io/pypi/v/tigrbl-orm?label=PyPI" alt="PyPI version for tigrbl-orm"/></a>
|
|
241
241
|
<a href="https://pypi.org/project/tigrbl-orm/"><img src="https://static.pepy.tech/badge/tigrbl-orm" alt="Downloads for tigrbl-orm"/></a>
|
|
242
242
|
<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-orm"/></a>
|
|
243
|
-
<a href="https://github.com/tigrbl/tigrbl/blob/master/pkgs/
|
|
243
|
+
<a href="https://github.com/tigrbl/tigrbl/blob/master/pkgs/30_orm/tigrbl_orm/README.md"><img src="https://hits.sh/github.com/tigrbl/tigrbl/blob/master/pkgs/30_orm/tigrbl_orm/README.md.svg?label=hits" alt="Repository hits for tigrbl-orm README"/></a>
|
|
244
244
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-525252" alt="Apache 2.0 license"/></a>
|
|
245
245
|
<a href="pyproject.toml"><img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-3776ab" alt="Python versions 3.10 | 3.11 | 3.12 | 3.13 | 3.14 for tigrbl-orm"/></a>
|
|
246
246
|
<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-orm"/></a>
|
|
@@ -264,7 +264,7 @@ Framework maintainers, extension authors, and advanced users composing Tigrbl fr
|
|
|
264
264
|
|
|
265
265
|
## Where does tigrbl-orm fit?
|
|
266
266
|
|
|
267
|
-
`tigrbl-orm` lives at `pkgs/
|
|
267
|
+
`tigrbl-orm` lives at `pkgs/30_orm/tigrbl_orm` and serves a focused layer in the split Tigrbl framework.
|
|
268
268
|
|
|
269
269
|
## How does tigrbl-orm work?
|
|
270
270
|
|
|
@@ -286,7 +286,7 @@ pip install tigrbl-orm
|
|
|
286
286
|
| Surface | Value |
|
|
287
287
|
|---|---|
|
|
288
288
|
| PyPI package | [`tigrbl-orm`](https://pypi.org/project/tigrbl-orm/) |
|
|
289
|
-
| Repository path | [`pkgs/
|
|
289
|
+
| Repository path | [`pkgs/30_orm/tigrbl_orm`](https://github.com/tigrbl/tigrbl/tree/master/pkgs/30_orm/tigrbl_orm) |
|
|
290
290
|
| Python import root | `tigrbl_orm` |
|
|
291
291
|
| Console scripts | none declared |
|
|
292
292
|
| Entry points | none declared |
|
|
@@ -395,7 +395,6 @@ Choose `tigrbl-orm` when the quick-answer table matches your use case. Choose [`
|
|
|
395
395
|
- [Package catalog](https://github.com/tigrbl/tigrbl/blob/master/docs/developer/PACKAGE_CATALOG.md)
|
|
396
396
|
- [Package layout](https://github.com/tigrbl/tigrbl/blob/master/docs/developer/PACKAGE_LAYOUT.md)
|
|
397
397
|
- [Current target](https://github.com/tigrbl/tigrbl/blob/master/docs/conformance/CURRENT_TARGET.md)
|
|
398
|
-
- [Current state](https://github.com/tigrbl/tigrbl/blob/master/docs/conformance/CURRENT_STATE.md)
|
|
399
398
|
- [SSOT registry](https://github.com/tigrbl/tigrbl/blob/master/.ssot/registry.json)
|
|
400
399
|
- [Release workflow](https://github.com/tigrbl/tigrbl/actions/workflows/publish.yml)
|
|
401
400
|
|
|
@@ -403,7 +402,7 @@ Choose `tigrbl-orm` when the quick-answer table matches your use case. Choose [`
|
|
|
403
402
|
|
|
404
403
|
- Community: [Discord](https://discord.gg/K4YTAPapjR).
|
|
405
404
|
- Issues: [GitHub Issues](https://github.com/tigrbl/tigrbl/issues).
|
|
406
|
-
- Repository: [pkgs/
|
|
405
|
+
- Repository: [pkgs/30_orm/tigrbl_orm](https://github.com/tigrbl/tigrbl/tree/master/pkgs/30_orm/tigrbl_orm).
|
|
407
406
|
|
|
408
407
|
## Package-local Boundary
|
|
409
408
|
|
|
@@ -414,7 +413,7 @@ This README is the package-local distribution entry point for `tigrbl-orm`. It a
|
|
|
414
413
|
- Package status: governed package in the `tigrbl/tigrbl` workspace.
|
|
415
414
|
- Governance source: [SSOT registry](https://github.com/tigrbl/tigrbl/blob/master/.ssot/registry.json).
|
|
416
415
|
- 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.
|
|
417
|
-
- Local certification guard: `pkgs/
|
|
416
|
+
- 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`.
|
|
418
417
|
- Scope note: this README documents the package boundary. Runtime feature support remains governed by `.ssot/` entities and the conformance docs linked below.
|
|
419
418
|
|
|
420
419
|
## License
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<a href="https://pypi.org/project/tigrbl-orm/"><img src="https://img.shields.io/pypi/v/tigrbl-orm?label=PyPI" alt="PyPI version for tigrbl-orm"/></a>
|
|
6
6
|
<a href="https://pypi.org/project/tigrbl-orm/"><img src="https://static.pepy.tech/badge/tigrbl-orm" alt="Downloads for tigrbl-orm"/></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-orm"/></a>
|
|
8
|
-
<a href="https://github.com/tigrbl/tigrbl/blob/master/pkgs/
|
|
8
|
+
<a href="https://github.com/tigrbl/tigrbl/blob/master/pkgs/30_orm/tigrbl_orm/README.md"><img src="https://hits.sh/github.com/tigrbl/tigrbl/blob/master/pkgs/30_orm/tigrbl_orm/README.md.svg?label=hits" alt="Repository hits for tigrbl-orm 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%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-3776ab" alt="Python versions 3.10 | 3.11 | 3.12 | 3.13 | 3.14 for tigrbl-orm"/></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-orm"/></a>
|
|
@@ -29,7 +29,7 @@ Framework maintainers, extension authors, and advanced users composing Tigrbl fr
|
|
|
29
29
|
|
|
30
30
|
## Where does tigrbl-orm fit?
|
|
31
31
|
|
|
32
|
-
`tigrbl-orm` lives at `pkgs/
|
|
32
|
+
`tigrbl-orm` lives at `pkgs/30_orm/tigrbl_orm` and serves a focused layer in the split Tigrbl framework.
|
|
33
33
|
|
|
34
34
|
## How does tigrbl-orm work?
|
|
35
35
|
|
|
@@ -51,7 +51,7 @@ pip install tigrbl-orm
|
|
|
51
51
|
| Surface | Value |
|
|
52
52
|
|---|---|
|
|
53
53
|
| PyPI package | [`tigrbl-orm`](https://pypi.org/project/tigrbl-orm/) |
|
|
54
|
-
| Repository path | [`pkgs/
|
|
54
|
+
| Repository path | [`pkgs/30_orm/tigrbl_orm`](https://github.com/tigrbl/tigrbl/tree/master/pkgs/30_orm/tigrbl_orm) |
|
|
55
55
|
| Python import root | `tigrbl_orm` |
|
|
56
56
|
| Console scripts | none declared |
|
|
57
57
|
| Entry points | none declared |
|
|
@@ -160,7 +160,6 @@ Choose `tigrbl-orm` when the quick-answer table matches your use case. Choose [`
|
|
|
160
160
|
- [Package catalog](https://github.com/tigrbl/tigrbl/blob/master/docs/developer/PACKAGE_CATALOG.md)
|
|
161
161
|
- [Package layout](https://github.com/tigrbl/tigrbl/blob/master/docs/developer/PACKAGE_LAYOUT.md)
|
|
162
162
|
- [Current target](https://github.com/tigrbl/tigrbl/blob/master/docs/conformance/CURRENT_TARGET.md)
|
|
163
|
-
- [Current state](https://github.com/tigrbl/tigrbl/blob/master/docs/conformance/CURRENT_STATE.md)
|
|
164
163
|
- [SSOT registry](https://github.com/tigrbl/tigrbl/blob/master/.ssot/registry.json)
|
|
165
164
|
- [Release workflow](https://github.com/tigrbl/tigrbl/actions/workflows/publish.yml)
|
|
166
165
|
|
|
@@ -168,7 +167,7 @@ Choose `tigrbl-orm` when the quick-answer table matches your use case. Choose [`
|
|
|
168
167
|
|
|
169
168
|
- Community: [Discord](https://discord.gg/K4YTAPapjR).
|
|
170
169
|
- Issues: [GitHub Issues](https://github.com/tigrbl/tigrbl/issues).
|
|
171
|
-
- Repository: [pkgs/
|
|
170
|
+
- Repository: [pkgs/30_orm/tigrbl_orm](https://github.com/tigrbl/tigrbl/tree/master/pkgs/30_orm/tigrbl_orm).
|
|
172
171
|
|
|
173
172
|
## Package-local Boundary
|
|
174
173
|
|
|
@@ -179,7 +178,7 @@ This README is the package-local distribution entry point for `tigrbl-orm`. It a
|
|
|
179
178
|
- Package status: governed package in the `tigrbl/tigrbl` workspace.
|
|
180
179
|
- Governance source: [SSOT registry](https://github.com/tigrbl/tigrbl/blob/master/.ssot/registry.json).
|
|
181
180
|
- 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.
|
|
182
|
-
- Local certification guard: `pkgs/
|
|
181
|
+
- 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`.
|
|
183
182
|
- Scope note: this README documents the package boundary. Runtime feature support remains governed by `.ssot/` entities and the conformance docs linked below.
|
|
184
183
|
|
|
185
184
|
## License
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tigrbl-orm"
|
|
3
|
-
version = "0.4.5.
|
|
3
|
+
version = "0.4.5.dev6"
|
|
4
4
|
description = "SQLAlchemy ORM tables, mixins, columns, model helpers, and persistence primitives for Tigrbl applications."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { file = "LICENSE" }
|
|
@@ -48,7 +48,7 @@ dependencies = [
|
|
|
48
48
|
Organization = "https://github.com/tigrbl"
|
|
49
49
|
Discord = "https://discord.gg/K4YTAPapjR"
|
|
50
50
|
Homepage = "https://github.com/tigrbl/tigrbl"
|
|
51
|
-
Repository = "https://github.com/tigrbl/tigrbl/tree/master/pkgs/
|
|
51
|
+
Repository = "https://github.com/tigrbl/tigrbl/tree/master/pkgs/30_orm/tigrbl_orm"
|
|
52
52
|
Issues = "https://github.com/tigrbl/tigrbl/issues"
|
|
53
53
|
|
|
54
54
|
[build-system]
|
|
@@ -16,7 +16,15 @@ from .utils import (
|
|
|
16
16
|
CRUD_IO,
|
|
17
17
|
RO_IO,
|
|
18
18
|
)
|
|
19
|
-
from .principals import
|
|
19
|
+
from .principals import (
|
|
20
|
+
GUIDPk,
|
|
21
|
+
UUID7Pk,
|
|
22
|
+
ULIDPk,
|
|
23
|
+
TenantColumn,
|
|
24
|
+
UserColumn,
|
|
25
|
+
OrgColumn,
|
|
26
|
+
Principal,
|
|
27
|
+
)
|
|
20
28
|
from .bound import OwnerBound, UserBound
|
|
21
29
|
from .lifecycle import (
|
|
22
30
|
Created,
|
|
@@ -59,6 +67,8 @@ __all__ = [
|
|
|
59
67
|
"CRUD_IO",
|
|
60
68
|
"RO_IO",
|
|
61
69
|
"GUIDPk",
|
|
70
|
+
"UUID7Pk",
|
|
71
|
+
"ULIDPk",
|
|
62
72
|
"TenantColumn",
|
|
63
73
|
"UserColumn",
|
|
64
74
|
"OrgColumn",
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from secrets import randbits
|
|
5
|
+
from time import time_ns
|
|
6
|
+
|
|
7
|
+
from pydantic import UUID7
|
|
8
|
+
from sqlalchemy import String, TypeDecorator
|
|
9
|
+
|
|
10
|
+
from tigrbl.specs import ColumnSpec, F, S, acol
|
|
11
|
+
from tigrbl._spec.storage_spec import ForeignKeySpec
|
|
12
|
+
from tigrbl.types import PgUUID, UUID, declarative_mixin, declared_attr, uuid4, Mapped
|
|
13
|
+
from tigrbl_core._spec.datatypes import DataTypeSpec
|
|
14
|
+
|
|
15
|
+
from .utils import _infer_schema, uuid_example, CRUD_IO, RO_IO
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_ULID_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
|
|
19
|
+
_ULID_PATTERN = rf"^[0-7][{_ULID_ALPHABET}]{{25}}$"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _new_ulid() -> str:
|
|
23
|
+
timestamp_ms = time_ns() // 1_000_000
|
|
24
|
+
value = (timestamp_ms << 80) | randbits(80)
|
|
25
|
+
return "".join(
|
|
26
|
+
_ULID_ALPHABET[(value >> shift) & 31] for shift in range(125, -1, -5)
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _new_uuid7() -> UUID:
|
|
31
|
+
timestamp_ms = time_ns() // 1_000_000
|
|
32
|
+
# RFC 9562 §5.7: 48-bit time, version, 12 random bits, variant, 62 random bits.
|
|
33
|
+
return UUID(
|
|
34
|
+
int=(timestamp_ms << 80)
|
|
35
|
+
| (7 << 76)
|
|
36
|
+
| (randbits(12) << 64)
|
|
37
|
+
| (2 << 62)
|
|
38
|
+
| randbits(62)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class _UUID7Type(TypeDecorator[UUID]):
|
|
43
|
+
impl = PgUUID
|
|
44
|
+
cache_ok = True
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def python_type(self) -> type[UUID]:
|
|
48
|
+
return UUID
|
|
49
|
+
|
|
50
|
+
def process_bind_param(self, value: UUID | str | None, dialect) -> UUID | None:
|
|
51
|
+
if value is None:
|
|
52
|
+
return None
|
|
53
|
+
if isinstance(value, str):
|
|
54
|
+
value = UUID(value)
|
|
55
|
+
if not isinstance(value, UUID) or value.version != 7:
|
|
56
|
+
raise ValueError("Expected an RFC 9562 UUID version 7")
|
|
57
|
+
return value
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class _ULIDString(TypeDecorator[str]):
|
|
61
|
+
impl = String(26)
|
|
62
|
+
cache_ok = True
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def python_type(self) -> type[str]:
|
|
66
|
+
return str
|
|
67
|
+
|
|
68
|
+
def process_bind_param(self, value: str | None, dialect) -> str | None:
|
|
69
|
+
if value is not None and (
|
|
70
|
+
not isinstance(value, str) or re.fullmatch(_ULID_PATTERN, value) is None
|
|
71
|
+
):
|
|
72
|
+
raise ValueError("Expected a canonical 26-character uppercase ULID")
|
|
73
|
+
return value
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@declarative_mixin
|
|
77
|
+
class GUIDPk:
|
|
78
|
+
"""Universal surrogate primary key."""
|
|
79
|
+
|
|
80
|
+
id: Mapped[UUID] = acol(
|
|
81
|
+
spec=ColumnSpec(
|
|
82
|
+
storage=S(
|
|
83
|
+
type_=PgUUID(as_uuid=True),
|
|
84
|
+
primary_key=True,
|
|
85
|
+
default=uuid4,
|
|
86
|
+
),
|
|
87
|
+
field=F(py_type=UUID, constraints={"examples": [uuid_example]}),
|
|
88
|
+
io=RO_IO,
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@declarative_mixin
|
|
94
|
+
class UUID7Pk:
|
|
95
|
+
"""Add a UUIDv7 primary key generated on insert."""
|
|
96
|
+
|
|
97
|
+
id: Mapped[UUID] = acol(
|
|
98
|
+
spec=ColumnSpec(
|
|
99
|
+
storage=S(
|
|
100
|
+
type_=_UUID7Type(),
|
|
101
|
+
primary_key=True,
|
|
102
|
+
nullable=False,
|
|
103
|
+
default=_new_uuid7,
|
|
104
|
+
),
|
|
105
|
+
field=F(
|
|
106
|
+
py_type=UUID7,
|
|
107
|
+
constraints={"examples": ["017f22e2-79b0-7cc3-98c4-dc0c0c07398f"]},
|
|
108
|
+
),
|
|
109
|
+
io=RO_IO,
|
|
110
|
+
datatype=DataTypeSpec(logical_name="uuid"),
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@declarative_mixin
|
|
116
|
+
class ULIDPk:
|
|
117
|
+
"""Add a canonical ULID string primary key, generated on insert."""
|
|
118
|
+
|
|
119
|
+
id: Mapped[str] = acol(
|
|
120
|
+
spec=ColumnSpec(
|
|
121
|
+
storage=S(
|
|
122
|
+
type_=_ULIDString(),
|
|
123
|
+
primary_key=True,
|
|
124
|
+
nullable=False,
|
|
125
|
+
default=_new_ulid,
|
|
126
|
+
),
|
|
127
|
+
field=F(
|
|
128
|
+
py_type=str,
|
|
129
|
+
constraints={
|
|
130
|
+
"min_length": 26,
|
|
131
|
+
"max_length": 26,
|
|
132
|
+
"pattern": _ULID_PATTERN,
|
|
133
|
+
},
|
|
134
|
+
),
|
|
135
|
+
io=RO_IO,
|
|
136
|
+
datatype=DataTypeSpec(logical_name="ulid"),
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@declarative_mixin
|
|
142
|
+
class TenantColumn:
|
|
143
|
+
"""Adds ``tenant_id`` with a schema-qualified FK to ``<schema>.tenants.id``."""
|
|
144
|
+
|
|
145
|
+
@declared_attr
|
|
146
|
+
def tenant_id(cls) -> Mapped[UUID]:
|
|
147
|
+
schema = getattr(cls, "__tenant_table_schema__", None) or _infer_schema(cls)
|
|
148
|
+
spec = ColumnSpec(
|
|
149
|
+
storage=S(
|
|
150
|
+
type_=PgUUID(as_uuid=True),
|
|
151
|
+
fk=ForeignKeySpec(target=f"{schema}.tenants.id"),
|
|
152
|
+
nullable=False,
|
|
153
|
+
index=True,
|
|
154
|
+
),
|
|
155
|
+
field=F(py_type=UUID, constraints={"examples": [uuid_example]}),
|
|
156
|
+
io=CRUD_IO,
|
|
157
|
+
)
|
|
158
|
+
return acol(spec=spec)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@declarative_mixin
|
|
162
|
+
class UserColumn:
|
|
163
|
+
"""Adds ``user_id`` with a schema-qualified FK to ``<schema>.users.id``."""
|
|
164
|
+
|
|
165
|
+
@declared_attr
|
|
166
|
+
def user_id(cls) -> Mapped[UUID]:
|
|
167
|
+
schema = getattr(cls, "__user_table_schema__", None) or _infer_schema(cls)
|
|
168
|
+
spec = ColumnSpec(
|
|
169
|
+
storage=S(
|
|
170
|
+
type_=PgUUID(as_uuid=True),
|
|
171
|
+
fk=ForeignKeySpec(target=f"{schema}.users.id"),
|
|
172
|
+
nullable=False,
|
|
173
|
+
index=True,
|
|
174
|
+
),
|
|
175
|
+
field=F(py_type=UUID, constraints={"examples": [uuid_example]}),
|
|
176
|
+
io=CRUD_IO,
|
|
177
|
+
)
|
|
178
|
+
return acol(spec=spec)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@declarative_mixin
|
|
182
|
+
class OrgColumn:
|
|
183
|
+
"""Adds ``org_id`` with a schema-qualified FK to ``<schema>.orgs.id``."""
|
|
184
|
+
|
|
185
|
+
@declared_attr
|
|
186
|
+
def org_id(cls) -> Mapped[UUID]:
|
|
187
|
+
schema = getattr(cls, "__org_table_schema__", None) or _infer_schema(cls)
|
|
188
|
+
spec = ColumnSpec(
|
|
189
|
+
storage=S(
|
|
190
|
+
type_=PgUUID(as_uuid=True),
|
|
191
|
+
fk=ForeignKeySpec(target=f"{schema}.orgs.id"),
|
|
192
|
+
nullable=False,
|
|
193
|
+
index=True,
|
|
194
|
+
),
|
|
195
|
+
field=F(py_type=UUID, constraints={"examples": [uuid_example]}),
|
|
196
|
+
io=CRUD_IO,
|
|
197
|
+
)
|
|
198
|
+
return acol(spec=spec)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@declarative_mixin
|
|
202
|
+
class Principal:
|
|
203
|
+
__abstract__ = True
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
__all__ = [
|
|
207
|
+
"GUIDPk",
|
|
208
|
+
"UUID7Pk",
|
|
209
|
+
"ULIDPk",
|
|
210
|
+
"TenantColumn",
|
|
211
|
+
"UserColumn",
|
|
212
|
+
"OrgColumn",
|
|
213
|
+
"Principal",
|
|
214
|
+
]
|
|
@@ -1,19 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import datetime as dt
|
|
4
|
-
|
|
5
3
|
from tigrbl.specs import IO
|
|
6
|
-
from tigrbl.types import UUID
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def tzutcnow() -> dt.datetime:
|
|
10
|
-
"""Return an aware UTC ``datetime``."""
|
|
11
|
-
return dt.datetime.now(dt.timezone.utc)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def tzutcnow_plus_day() -> dt.datetime:
|
|
15
|
-
"""Return an aware UTC ``datetime`` one day in the future."""
|
|
16
|
-
return tzutcnow() + dt.timedelta(days=1)
|
|
4
|
+
from tigrbl.types import UUID, tzutcnow, tzutcnow_plus_day
|
|
17
5
|
|
|
18
6
|
|
|
19
7
|
def _infer_schema(cls, default: str = "public") -> str:
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from tigrbl.specs import ColumnSpec, F, S, acol
|
|
4
|
-
from tigrbl._spec.storage_spec import ForeignKeySpec
|
|
5
|
-
from tigrbl.types import PgUUID, UUID, declarative_mixin, declared_attr, uuid4, Mapped
|
|
6
|
-
|
|
7
|
-
from .utils import _infer_schema, uuid_example, CRUD_IO, RO_IO
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@declarative_mixin
|
|
11
|
-
class GUIDPk:
|
|
12
|
-
"""Universal surrogate primary key."""
|
|
13
|
-
|
|
14
|
-
id: Mapped[UUID] = acol(
|
|
15
|
-
spec=ColumnSpec(
|
|
16
|
-
storage=S(
|
|
17
|
-
type_=PgUUID(as_uuid=True),
|
|
18
|
-
primary_key=True,
|
|
19
|
-
default=uuid4,
|
|
20
|
-
),
|
|
21
|
-
field=F(py_type=UUID, constraints={"examples": [uuid_example]}),
|
|
22
|
-
io=RO_IO,
|
|
23
|
-
)
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@declarative_mixin
|
|
28
|
-
class TenantColumn:
|
|
29
|
-
"""Adds ``tenant_id`` with a schema-qualified FK to ``<schema>.tenants.id``."""
|
|
30
|
-
|
|
31
|
-
@declared_attr
|
|
32
|
-
def tenant_id(cls) -> Mapped[UUID]:
|
|
33
|
-
schema = getattr(cls, "__tenant_table_schema__", None) or _infer_schema(cls)
|
|
34
|
-
spec = ColumnSpec(
|
|
35
|
-
storage=S(
|
|
36
|
-
type_=PgUUID(as_uuid=True),
|
|
37
|
-
fk=ForeignKeySpec(target=f"{schema}.tenants.id"),
|
|
38
|
-
nullable=False,
|
|
39
|
-
index=True,
|
|
40
|
-
),
|
|
41
|
-
field=F(py_type=UUID, constraints={"examples": [uuid_example]}),
|
|
42
|
-
io=CRUD_IO,
|
|
43
|
-
)
|
|
44
|
-
return acol(spec=spec)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
@declarative_mixin
|
|
48
|
-
class UserColumn:
|
|
49
|
-
"""Adds ``user_id`` with a schema-qualified FK to ``<schema>.users.id``."""
|
|
50
|
-
|
|
51
|
-
@declared_attr
|
|
52
|
-
def user_id(cls) -> Mapped[UUID]:
|
|
53
|
-
schema = getattr(cls, "__user_table_schema__", None) or _infer_schema(cls)
|
|
54
|
-
spec = ColumnSpec(
|
|
55
|
-
storage=S(
|
|
56
|
-
type_=PgUUID(as_uuid=True),
|
|
57
|
-
fk=ForeignKeySpec(target=f"{schema}.users.id"),
|
|
58
|
-
nullable=False,
|
|
59
|
-
index=True,
|
|
60
|
-
),
|
|
61
|
-
field=F(py_type=UUID, constraints={"examples": [uuid_example]}),
|
|
62
|
-
io=CRUD_IO,
|
|
63
|
-
)
|
|
64
|
-
return acol(spec=spec)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@declarative_mixin
|
|
68
|
-
class OrgColumn:
|
|
69
|
-
"""Adds ``org_id`` with a schema-qualified FK to ``<schema>.orgs.id``."""
|
|
70
|
-
|
|
71
|
-
@declared_attr
|
|
72
|
-
def org_id(cls) -> Mapped[UUID]:
|
|
73
|
-
schema = getattr(cls, "__org_table_schema__", None) or _infer_schema(cls)
|
|
74
|
-
spec = ColumnSpec(
|
|
75
|
-
storage=S(
|
|
76
|
-
type_=PgUUID(as_uuid=True),
|
|
77
|
-
fk=ForeignKeySpec(target=f"{schema}.orgs.id"),
|
|
78
|
-
nullable=False,
|
|
79
|
-
index=True,
|
|
80
|
-
),
|
|
81
|
-
field=F(py_type=UUID, constraints={"examples": [uuid_example]}),
|
|
82
|
-
io=CRUD_IO,
|
|
83
|
-
)
|
|
84
|
-
return acol(spec=spec)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
@declarative_mixin
|
|
88
|
-
class Principal:
|
|
89
|
-
__abstract__ = True
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
__all__ = [
|
|
93
|
-
"GUIDPk",
|
|
94
|
-
"TenantColumn",
|
|
95
|
-
"UserColumn",
|
|
96
|
-
"OrgColumn",
|
|
97
|
-
"Principal",
|
|
98
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|