tigrbl 0.0.1.dev1__py3-none-any.whl → 0.3.0__py3-none-any.whl
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/README.md +94 -0
- tigrbl/__init__.py +139 -14
- tigrbl/api/__init__.py +6 -0
- tigrbl/api/_api.py +97 -0
- tigrbl/api/api_spec.py +30 -0
- tigrbl/api/mro_collect.py +43 -0
- tigrbl/api/shortcuts.py +56 -0
- tigrbl/api/tigrbl_api.py +291 -0
- tigrbl/app/__init__.py +0 -0
- tigrbl/app/_app.py +86 -0
- tigrbl/app/_model_registry.py +41 -0
- tigrbl/app/app_spec.py +42 -0
- tigrbl/app/mro_collect.py +67 -0
- tigrbl/app/shortcuts.py +65 -0
- tigrbl/app/tigrbl_app.py +319 -0
- tigrbl/bindings/__init__.py +73 -0
- tigrbl/bindings/api/__init__.py +12 -0
- tigrbl/bindings/api/common.py +109 -0
- tigrbl/bindings/api/include.py +256 -0
- tigrbl/bindings/api/resource_proxy.py +149 -0
- tigrbl/bindings/api/rpc.py +111 -0
- tigrbl/bindings/columns.py +49 -0
- tigrbl/bindings/handlers/__init__.py +11 -0
- tigrbl/bindings/handlers/builder.py +119 -0
- tigrbl/bindings/handlers/ctx.py +74 -0
- tigrbl/bindings/handlers/identifiers.py +228 -0
- tigrbl/bindings/handlers/namespaces.py +51 -0
- tigrbl/bindings/handlers/steps.py +276 -0
- tigrbl/bindings/hooks.py +311 -0
- tigrbl/bindings/model.py +194 -0
- tigrbl/bindings/model_helpers.py +139 -0
- tigrbl/bindings/model_registry.py +77 -0
- tigrbl/bindings/rest/__init__.py +7 -0
- tigrbl/bindings/rest/attach.py +34 -0
- tigrbl/bindings/rest/collection.py +286 -0
- tigrbl/bindings/rest/common.py +120 -0
- tigrbl/bindings/rest/fastapi.py +76 -0
- tigrbl/bindings/rest/helpers.py +119 -0
- tigrbl/bindings/rest/io.py +317 -0
- tigrbl/bindings/rest/io_headers.py +49 -0
- tigrbl/bindings/rest/member.py +386 -0
- tigrbl/bindings/rest/router.py +296 -0
- tigrbl/bindings/rest/routing.py +153 -0
- tigrbl/bindings/rpc.py +364 -0
- tigrbl/bindings/schemas/__init__.py +11 -0
- tigrbl/bindings/schemas/builder.py +348 -0
- tigrbl/bindings/schemas/defaults.py +260 -0
- tigrbl/bindings/schemas/utils.py +193 -0
- tigrbl/column/README.md +62 -0
- tigrbl/column/__init__.py +72 -0
- tigrbl/column/_column.py +96 -0
- tigrbl/column/column_spec.py +40 -0
- tigrbl/column/field_spec.py +31 -0
- tigrbl/column/infer/__init__.py +25 -0
- tigrbl/column/infer/core.py +92 -0
- tigrbl/column/infer/jsonhints.py +44 -0
- tigrbl/column/infer/planning.py +133 -0
- tigrbl/column/infer/types.py +102 -0
- tigrbl/column/infer/utils.py +59 -0
- tigrbl/column/io_spec.py +136 -0
- tigrbl/column/mro_collect.py +59 -0
- tigrbl/column/shortcuts.py +89 -0
- tigrbl/column/storage_spec.py +65 -0
- tigrbl/config/__init__.py +19 -0
- tigrbl/config/constants.py +224 -0
- tigrbl/config/defaults.py +29 -0
- tigrbl/config/resolver.py +295 -0
- tigrbl/core/__init__.py +47 -0
- tigrbl/core/crud/__init__.py +36 -0
- tigrbl/core/crud/bulk.py +168 -0
- tigrbl/core/crud/helpers/__init__.py +76 -0
- tigrbl/core/crud/helpers/db.py +92 -0
- tigrbl/core/crud/helpers/enum.py +86 -0
- tigrbl/core/crud/helpers/filters.py +162 -0
- tigrbl/core/crud/helpers/model.py +123 -0
- tigrbl/core/crud/helpers/normalize.py +99 -0
- tigrbl/core/crud/ops.py +235 -0
- tigrbl/ddl/__init__.py +344 -0
- tigrbl/decorators.py +17 -0
- tigrbl/deps/__init__.py +20 -0
- tigrbl/deps/fastapi.py +45 -0
- tigrbl/deps/favicon.svg +4 -0
- tigrbl/deps/jinja.py +27 -0
- tigrbl/deps/pydantic.py +10 -0
- tigrbl/deps/sqlalchemy.py +94 -0
- tigrbl/deps/starlette.py +36 -0
- tigrbl/engine/__init__.py +45 -0
- tigrbl/engine/_engine.py +144 -0
- tigrbl/engine/bind.py +33 -0
- tigrbl/engine/builders.py +236 -0
- tigrbl/engine/capabilities.py +29 -0
- tigrbl/engine/collect.py +111 -0
- tigrbl/engine/decorators.py +110 -0
- tigrbl/engine/docs/PLUGINS.md +49 -0
- tigrbl/engine/engine_spec.py +355 -0
- tigrbl/engine/plugins.py +52 -0
- tigrbl/engine/registry.py +36 -0
- tigrbl/engine/resolver.py +224 -0
- tigrbl/engine/shortcuts.py +216 -0
- tigrbl/hook/__init__.py +21 -0
- tigrbl/hook/_hook.py +22 -0
- tigrbl/hook/decorators.py +28 -0
- tigrbl/hook/hook_spec.py +24 -0
- tigrbl/hook/mro_collect.py +98 -0
- tigrbl/hook/shortcuts.py +44 -0
- tigrbl/hook/types.py +76 -0
- tigrbl/op/__init__.py +50 -0
- tigrbl/op/_op.py +31 -0
- tigrbl/op/canonical.py +31 -0
- tigrbl/op/collect.py +11 -0
- tigrbl/op/decorators.py +238 -0
- tigrbl/op/model_registry.py +301 -0
- tigrbl/op/mro_collect.py +99 -0
- tigrbl/op/resolver.py +216 -0
- tigrbl/op/types.py +136 -0
- tigrbl/orm/__init__.py +1 -0
- tigrbl/orm/mixins/_RowBound.py +83 -0
- tigrbl/orm/mixins/__init__.py +95 -0
- tigrbl/orm/mixins/bootstrappable.py +113 -0
- tigrbl/orm/mixins/bound.py +47 -0
- tigrbl/orm/mixins/edges.py +40 -0
- tigrbl/orm/mixins/fields.py +165 -0
- tigrbl/orm/mixins/hierarchy.py +54 -0
- tigrbl/orm/mixins/key_digest.py +44 -0
- tigrbl/orm/mixins/lifecycle.py +115 -0
- tigrbl/orm/mixins/locks.py +51 -0
- tigrbl/orm/mixins/markers.py +16 -0
- tigrbl/orm/mixins/operations.py +57 -0
- tigrbl/orm/mixins/ownable.py +337 -0
- tigrbl/orm/mixins/principals.py +98 -0
- tigrbl/orm/mixins/tenant_bound.py +301 -0
- tigrbl/orm/mixins/upsertable.py +118 -0
- tigrbl/orm/mixins/utils.py +49 -0
- tigrbl/orm/tables/__init__.py +72 -0
- tigrbl/orm/tables/_base.py +8 -0
- tigrbl/orm/tables/audit.py +56 -0
- tigrbl/orm/tables/client.py +25 -0
- tigrbl/orm/tables/group.py +29 -0
- tigrbl/orm/tables/org.py +30 -0
- tigrbl/orm/tables/rbac.py +76 -0
- tigrbl/orm/tables/status.py +106 -0
- tigrbl/orm/tables/tenant.py +22 -0
- tigrbl/orm/tables/user.py +39 -0
- tigrbl/response/README.md +34 -0
- tigrbl/response/__init__.py +33 -0
- tigrbl/response/bind.py +12 -0
- tigrbl/response/decorators.py +37 -0
- tigrbl/response/resolver.py +83 -0
- tigrbl/response/shortcuts.py +171 -0
- tigrbl/response/types.py +49 -0
- tigrbl/rest/__init__.py +27 -0
- tigrbl/runtime/README.md +129 -0
- tigrbl/runtime/__init__.py +20 -0
- tigrbl/runtime/atoms/__init__.py +102 -0
- tigrbl/runtime/atoms/emit/__init__.py +42 -0
- tigrbl/runtime/atoms/emit/paired_post.py +158 -0
- tigrbl/runtime/atoms/emit/paired_pre.py +106 -0
- tigrbl/runtime/atoms/emit/readtime_alias.py +120 -0
- tigrbl/runtime/atoms/out/__init__.py +38 -0
- tigrbl/runtime/atoms/out/masking.py +135 -0
- tigrbl/runtime/atoms/refresh/__init__.py +38 -0
- tigrbl/runtime/atoms/refresh/demand.py +130 -0
- tigrbl/runtime/atoms/resolve/__init__.py +40 -0
- tigrbl/runtime/atoms/resolve/assemble.py +167 -0
- tigrbl/runtime/atoms/resolve/paired_gen.py +147 -0
- tigrbl/runtime/atoms/response/__init__.py +19 -0
- tigrbl/runtime/atoms/response/headers_from_payload.py +57 -0
- tigrbl/runtime/atoms/response/negotiate.py +30 -0
- tigrbl/runtime/atoms/response/negotiation.py +43 -0
- tigrbl/runtime/atoms/response/render.py +36 -0
- tigrbl/runtime/atoms/response/renderer.py +116 -0
- tigrbl/runtime/atoms/response/template.py +44 -0
- tigrbl/runtime/atoms/response/templates.py +88 -0
- tigrbl/runtime/atoms/schema/__init__.py +40 -0
- tigrbl/runtime/atoms/schema/collect_in.py +21 -0
- tigrbl/runtime/atoms/schema/collect_out.py +21 -0
- tigrbl/runtime/atoms/storage/__init__.py +38 -0
- tigrbl/runtime/atoms/storage/to_stored.py +167 -0
- tigrbl/runtime/atoms/wire/__init__.py +45 -0
- tigrbl/runtime/atoms/wire/build_in.py +166 -0
- tigrbl/runtime/atoms/wire/build_out.py +87 -0
- tigrbl/runtime/atoms/wire/dump.py +206 -0
- tigrbl/runtime/atoms/wire/validate_in.py +227 -0
- tigrbl/runtime/context.py +206 -0
- tigrbl/runtime/errors/__init__.py +61 -0
- tigrbl/runtime/errors/converters.py +214 -0
- tigrbl/runtime/errors/exceptions.py +124 -0
- tigrbl/runtime/errors/mappings.py +71 -0
- tigrbl/runtime/errors/utils.py +150 -0
- tigrbl/runtime/events.py +209 -0
- tigrbl/runtime/executor/__init__.py +6 -0
- tigrbl/runtime/executor/guards.py +132 -0
- tigrbl/runtime/executor/helpers.py +88 -0
- tigrbl/runtime/executor/invoke.py +150 -0
- tigrbl/runtime/executor/types.py +84 -0
- tigrbl/runtime/kernel.py +644 -0
- tigrbl/runtime/labels.py +353 -0
- tigrbl/runtime/opview.py +89 -0
- tigrbl/runtime/ordering.py +256 -0
- tigrbl/runtime/system.py +279 -0
- tigrbl/runtime/trace.py +330 -0
- tigrbl/schema/__init__.py +38 -0
- tigrbl/schema/_schema.py +27 -0
- tigrbl/schema/builder/__init__.py +17 -0
- tigrbl/schema/builder/build_schema.py +209 -0
- tigrbl/schema/builder/cache.py +24 -0
- tigrbl/schema/builder/compat.py +16 -0
- tigrbl/schema/builder/extras.py +85 -0
- tigrbl/schema/builder/helpers.py +51 -0
- tigrbl/schema/builder/list_params.py +117 -0
- tigrbl/schema/builder/strip_parent_fields.py +70 -0
- tigrbl/schema/collect.py +79 -0
- tigrbl/schema/decorators.py +68 -0
- tigrbl/schema/get_schema.py +86 -0
- tigrbl/schema/schema_spec.py +20 -0
- tigrbl/schema/shortcuts.py +42 -0
- tigrbl/schema/types.py +34 -0
- tigrbl/schema/utils.py +143 -0
- tigrbl/session/README.md +14 -0
- tigrbl/session/__init__.py +28 -0
- tigrbl/session/abc.py +76 -0
- tigrbl/session/base.py +151 -0
- tigrbl/session/decorators.py +43 -0
- tigrbl/session/default.py +118 -0
- tigrbl/session/shortcuts.py +50 -0
- tigrbl/session/spec.py +112 -0
- tigrbl/shortcuts.py +22 -0
- tigrbl/specs.py +44 -0
- tigrbl/system/__init__.py +13 -0
- tigrbl/system/diagnostics/__init__.py +24 -0
- tigrbl/system/diagnostics/compat.py +31 -0
- tigrbl/system/diagnostics/healthz.py +41 -0
- tigrbl/system/diagnostics/hookz.py +51 -0
- tigrbl/system/diagnostics/kernelz.py +20 -0
- tigrbl/system/diagnostics/methodz.py +43 -0
- tigrbl/system/diagnostics/router.py +73 -0
- tigrbl/system/diagnostics/utils.py +43 -0
- tigrbl/system/uvicorn.py +60 -0
- tigrbl/table/__init__.py +9 -0
- tigrbl/table/_base.py +260 -0
- tigrbl/table/_table.py +54 -0
- tigrbl/table/mro_collect.py +69 -0
- tigrbl/table/shortcuts.py +57 -0
- tigrbl/table/table_spec.py +28 -0
- tigrbl/transport/__init__.py +74 -0
- tigrbl/transport/jsonrpc/__init__.py +19 -0
- tigrbl/transport/jsonrpc/dispatcher.py +352 -0
- tigrbl/transport/jsonrpc/helpers.py +115 -0
- tigrbl/transport/jsonrpc/models.py +41 -0
- tigrbl/transport/rest/__init__.py +25 -0
- tigrbl/transport/rest/aggregator.py +132 -0
- tigrbl/types/__init__.py +170 -0
- tigrbl/types/allow_anon_provider.py +19 -0
- tigrbl/types/authn_abc.py +30 -0
- tigrbl/types/nested_path_provider.py +22 -0
- tigrbl/types/op.py +35 -0
- tigrbl/types/op_config_provider.py +17 -0
- tigrbl/types/op_verb_alias_provider.py +33 -0
- tigrbl/types/request_extras_provider.py +22 -0
- tigrbl/types/response_extras_provider.py +22 -0
- tigrbl/types/table_config_provider.py +13 -0
- tigrbl/types/uuid.py +55 -0
- tigrbl-0.3.0.dist-info/METADATA +516 -0
- tigrbl-0.3.0.dist-info/RECORD +266 -0
- {tigrbl-0.0.1.dev1.dist-info → tigrbl-0.3.0.dist-info}/WHEEL +1 -1
- tigrbl-0.3.0.dist-info/licenses/LICENSE +201 -0
- tigrbl/ExampleAgent.py +0 -1
- tigrbl-0.0.1.dev1.dist-info/METADATA +0 -18
- tigrbl-0.0.1.dev1.dist-info/RECORD +0 -5
tigrbl/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Tigrbl v3 Engine Conformance
|
|
2
|
+
|
|
3
|
+
Applications built on Tigrbl v3 **must** create database engines and sessions
|
|
4
|
+
through the `tigrbl.engine` package. Direct imports from
|
|
5
|
+
`sqlalchemy.ext.asyncio`—such as `AsyncSession`, `create_async_engine`, or
|
|
6
|
+
`async_sessionmaker`—are **not permitted**.
|
|
7
|
+
|
|
8
|
+
Instead, construct an engine via `Engine` or the helper `engine()` function:
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
from tigrbl.engine import engine
|
|
12
|
+
|
|
13
|
+
DB = engine("sqlite+aiosqlite:///./app.db")
|
|
14
|
+
app = TigrblApp(engine=DB)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Use `DB.get_db` as the FastAPI dependency for acquiring sessions and avoid
|
|
18
|
+
exporting custom `get_async_db` helpers.
|
|
19
|
+
|
|
20
|
+
These rules apply to all first-party applications, including
|
|
21
|
+
`tigrbl_kms`, `tigrbl_auth`, and the `peagen` gateway.
|
|
22
|
+
|
|
23
|
+
## Column-Level Configuration
|
|
24
|
+
|
|
25
|
+
Tigrbl v3 models declare their database and API behavior through
|
|
26
|
+
`ColumnSpec` helpers exposed in `tigrbl.column`. Use `acol` for
|
|
27
|
+
persisted columns and `vcol` for wire-only virtual values. Each column
|
|
28
|
+
can combine three optional specs:
|
|
29
|
+
|
|
30
|
+
- `S` (`StorageSpec`) – database shape such as types, keys, indexes, and
|
|
31
|
+
other SQLAlchemy column arguments.
|
|
32
|
+
- `F` (`FieldSpec`) – Python and schema metadata including validation
|
|
33
|
+
constraints or example values.
|
|
34
|
+
- `IO` (`IOSpec`) – inbound/outbound exposure settings, aliases,
|
|
35
|
+
sensitivity flags, and filtering/sorting capabilities.
|
|
36
|
+
|
|
37
|
+
For a deeper look at these helpers, see [column/README.md](column/README.md).
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from tigrbl.column import acol, vcol, F, S, IO
|
|
43
|
+
|
|
44
|
+
class Widget(Base):
|
|
45
|
+
__tablename__ = "widgets"
|
|
46
|
+
|
|
47
|
+
id: Mapped[int] = acol(storage=S(primary_key=True))
|
|
48
|
+
name: Mapped[str] = acol(
|
|
49
|
+
field=F(constraints={"max_length": 50}),
|
|
50
|
+
storage=S(nullable=False, index=True),
|
|
51
|
+
io=IO(
|
|
52
|
+
in_verbs=("create", "update"),
|
|
53
|
+
out_verbs=("read", "list"),
|
|
54
|
+
sortable=True,
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
checksum: Mapped[str] = vcol(
|
|
58
|
+
field=F(),
|
|
59
|
+
io=IO(out_verbs=("read",)),
|
|
60
|
+
read_producer=lambda obj, ctx: f"{obj.name}:{obj.id}",
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Virtual columns like `checksum` use a `read_producer` (or `producer`)
|
|
65
|
+
function to compute values on the fly. Leveraging these specs keeps
|
|
66
|
+
column behavior declarative and consistent across the ORM, schema
|
|
67
|
+
generation, and runtime I/O.
|
|
68
|
+
|
|
69
|
+
## 🧩 First-Class Object Pattern
|
|
70
|
+
|
|
71
|
+
Tigrbl v3 organizes its core building blocks with a common structure:
|
|
72
|
+
|
|
73
|
+
- 📄 **Spec** – declarative metadata describing behavior.
|
|
74
|
+
- 🏛️ **Class** – runtime implementation of the object.
|
|
75
|
+
- 🎀 **Decorators** – syntactic sugar for declaring features.
|
|
76
|
+
- ⚡️ **Shortcuts** – handy constructors for common setups.
|
|
77
|
+
|
|
78
|
+
Some objects also expose optional helpers:
|
|
79
|
+
|
|
80
|
+
- **Collect** – gathers declarations from a class hierarchy.
|
|
81
|
+
- 🧩 **Resolver** – finalizes configuration from specs.
|
|
82
|
+
- 🎧 **Builder** – assembles complex runtime resources.
|
|
83
|
+
|
|
84
|
+
| Object | 📄 Spec | 🏛️ Class | 🎀 Decorators | ⚡️ Shortcuts | Collect | 🧩 Resolver | 🎧 Builder |
|
|
85
|
+
|--------|----------|-----------|----------------|----------------|----------------|----------------|----------------|
|
|
86
|
+
| Column | `column_spec.py` | `_column.py` | — | `shortcuts.py` | `collect.py` | — | — |
|
|
87
|
+
| Engine | `engine_spec.py` | `_engine.py` | `decorators.py` | `shortcuts.py` | `collect.py` | `resolver.py` | `builders.py` |
|
|
88
|
+
| Op | `types.py` | `_op.py` | `decorators.py` | — | `collect.py` | — | — |
|
|
89
|
+
| API | `api_spec.py` | `_api.py` | — | `shortcuts.py` | — | — | — |
|
|
90
|
+
| App | `app_spec.py` | `_app.py` | — | `shortcuts.py` | — | — | — |
|
|
91
|
+
| Table | `table_spec.py` | `_table.py` | — | `shortcuts.py` | — | — | — |
|
|
92
|
+
|
|
93
|
+
This pattern keeps the system modular and predictable, making it easy to
|
|
94
|
+
discover related modules for any given concept.
|
tigrbl/__init__.py
CHANGED
|
@@ -1,14 +1,139 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
""
|
|
1
|
+
# tigrbl/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Tigrbl – public API
|
|
4
|
+
|
|
5
|
+
OpSpec-centric building blocks to bind models, wire schemas/handlers/hooks,
|
|
6
|
+
register RPC & REST, and (optionally) mount JSON-RPC and diagnostics.
|
|
7
|
+
|
|
8
|
+
Quick start:
|
|
9
|
+
from tigrbl import include_model, build_jsonrpc_router, mount_diagnostics
|
|
10
|
+
from tigrbl import OpSpec, hook_ctx, op_ctx, alias_ctx, schema_ctx, SchemaRef
|
|
11
|
+
|
|
12
|
+
include_model(api, User, app=fastapi_app)
|
|
13
|
+
app.include_router(build_jsonrpc_router(api), prefix="/rpc")
|
|
14
|
+
app.include_router(mount_diagnostics(api), prefix="/system")
|
|
15
|
+
|
|
16
|
+
# Example: custom op using an existing schema
|
|
17
|
+
@op_ctx(alias="search", target="custom", arity="collection",
|
|
18
|
+
request_schema=SchemaRef("Search", "in"),
|
|
19
|
+
response_schema=SchemaRef("Search", "out"))
|
|
20
|
+
def search(cls, ctx):
|
|
21
|
+
...
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
# ── OpSpec (source of truth) ───────────────────────────────────────────────────
|
|
27
|
+
from .op import (
|
|
28
|
+
OpSpec,
|
|
29
|
+
get_registry,
|
|
30
|
+
# types and helpers re-exported from ops
|
|
31
|
+
TargetOp,
|
|
32
|
+
Arity,
|
|
33
|
+
PersistPolicy,
|
|
34
|
+
PHASE,
|
|
35
|
+
HookPhase,
|
|
36
|
+
PHASES,
|
|
37
|
+
)
|
|
38
|
+
from .schema.types import SchemaRef, SchemaArg
|
|
39
|
+
|
|
40
|
+
# ── Ctx-only decorators (new surface; replaces legacy ops.decorators) ─────────
|
|
41
|
+
|
|
42
|
+
from .op import alias_ctx, op_ctx, alias, op_alias
|
|
43
|
+
from .hook import hook_ctx
|
|
44
|
+
from .engine.decorators import engine_ctx
|
|
45
|
+
from .schema.decorators import schema_ctx
|
|
46
|
+
from .response.decorators import response_ctx
|
|
47
|
+
from .response.types import ResponseSpec
|
|
48
|
+
|
|
49
|
+
# ── Bindings (model + API orchestration) ───────────────────────────────────────
|
|
50
|
+
from .bindings import (
|
|
51
|
+
bind,
|
|
52
|
+
rebind,
|
|
53
|
+
build_schemas,
|
|
54
|
+
build_hooks,
|
|
55
|
+
build_handlers,
|
|
56
|
+
register_rpc,
|
|
57
|
+
build_rest,
|
|
58
|
+
include_model,
|
|
59
|
+
include_models,
|
|
60
|
+
rpc_call,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# ── Runtime (advanced: run a phase pipeline directly) ──────────────────────────
|
|
64
|
+
from .runtime.executor import _invoke
|
|
65
|
+
|
|
66
|
+
# ── Schemas ────────────────────────────────────────────────────────────────────
|
|
67
|
+
from .schema import _build_schema, _build_list_params, get_schema
|
|
68
|
+
|
|
69
|
+
# ── Transport & Diagnostics (optional) ─────────────────────────────────────────
|
|
70
|
+
from .transport.jsonrpc import build_jsonrpc_router
|
|
71
|
+
from .system import mount_diagnostics
|
|
72
|
+
|
|
73
|
+
# ── DB/bootstrap helpers (infra; optional) ─────────────────────────────────────
|
|
74
|
+
from .ddl import ensure_schemas, register_sqlite_attach, bootstrap_dbschema
|
|
75
|
+
|
|
76
|
+
# ── Config constants (defaults used by REST) ───────────────────────────────────
|
|
77
|
+
from .config.constants import DEFAULT_HTTP_METHODS
|
|
78
|
+
from .app.tigrbl_app import TigrblApp
|
|
79
|
+
from .api import Api, TigrblApi
|
|
80
|
+
|
|
81
|
+
from .table import Base
|
|
82
|
+
from .op import Op
|
|
83
|
+
from .app._app import App
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
__all__: list[str] = []
|
|
87
|
+
|
|
88
|
+
__all__ += ["TigrblApp", "TigrblApi", "Api", "Base", "App", "Op"]
|
|
89
|
+
|
|
90
|
+
__all__ += [
|
|
91
|
+
# OpSpec core
|
|
92
|
+
"OpSpec",
|
|
93
|
+
"get_registry",
|
|
94
|
+
# types
|
|
95
|
+
"TargetOp",
|
|
96
|
+
"Arity",
|
|
97
|
+
"PersistPolicy",
|
|
98
|
+
"PHASE",
|
|
99
|
+
"PHASES",
|
|
100
|
+
"HookPhase",
|
|
101
|
+
"SchemaRef",
|
|
102
|
+
"SchemaArg",
|
|
103
|
+
# Ctx-only decorators
|
|
104
|
+
"alias_ctx",
|
|
105
|
+
"op_ctx",
|
|
106
|
+
"hook_ctx",
|
|
107
|
+
"schema_ctx",
|
|
108
|
+
"response_ctx",
|
|
109
|
+
"alias",
|
|
110
|
+
"op_alias",
|
|
111
|
+
"engine_ctx",
|
|
112
|
+
"ResponseSpec",
|
|
113
|
+
# Bindings
|
|
114
|
+
"bind",
|
|
115
|
+
"rebind",
|
|
116
|
+
"build_schemas",
|
|
117
|
+
"build_hooks",
|
|
118
|
+
"build_handlers",
|
|
119
|
+
"register_rpc",
|
|
120
|
+
"build_rest",
|
|
121
|
+
"include_model",
|
|
122
|
+
"include_models",
|
|
123
|
+
"rpc_call",
|
|
124
|
+
# Runtime
|
|
125
|
+
"_invoke",
|
|
126
|
+
# Schemas
|
|
127
|
+
"_build_schema",
|
|
128
|
+
"_build_list_params",
|
|
129
|
+
"get_schema",
|
|
130
|
+
# Transport / Diagnostics
|
|
131
|
+
"build_jsonrpc_router",
|
|
132
|
+
"mount_diagnostics",
|
|
133
|
+
# DB/infra
|
|
134
|
+
"ensure_schemas",
|
|
135
|
+
"register_sqlite_attach",
|
|
136
|
+
"bootstrap_dbschema",
|
|
137
|
+
# Config
|
|
138
|
+
"DEFAULT_HTTP_METHODS",
|
|
139
|
+
]
|
tigrbl/api/__init__.py
ADDED
tigrbl/api/_api.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# tigrbl/tigrbl/v3/api/_api.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import Any
|
|
4
|
+
from types import SimpleNamespace
|
|
5
|
+
|
|
6
|
+
from ..deps.fastapi import APIRouter as ApiRouter
|
|
7
|
+
from ..engine.engine_spec import EngineCfg
|
|
8
|
+
from ..engine import install_from_objects
|
|
9
|
+
from ..ddl import initialize as _ddl_initialize
|
|
10
|
+
from ..engine import resolver as _resolver
|
|
11
|
+
from ..app._model_registry import initialize_model_registry
|
|
12
|
+
from .api_spec import APISpec
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Api(APISpec, ApiRouter):
|
|
16
|
+
"""API router with model and table registries."""
|
|
17
|
+
|
|
18
|
+
MODELS: tuple[Any, ...] = ()
|
|
19
|
+
TABLES: tuple[Any, ...] = ()
|
|
20
|
+
|
|
21
|
+
# dataclass inheritance makes instances unhashable; use identity semantics
|
|
22
|
+
# for both hashing and equality so objects can participate in sets/dicts
|
|
23
|
+
def __hash__(self) -> int: # pragma: no cover - simple identity hash
|
|
24
|
+
return id(self)
|
|
25
|
+
|
|
26
|
+
def __eq__(self, other: object) -> bool: # pragma: no cover - identity compare
|
|
27
|
+
return self is other
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self, *, engine: EngineCfg | None = None, **router_kwargs: Any
|
|
31
|
+
) -> None:
|
|
32
|
+
# Manually initialize fields from ``APISpec`` so ``repr`` and other
|
|
33
|
+
# dataclass-generated helpers have the expected attributes, while also
|
|
34
|
+
# preparing mutable containers used at runtime.
|
|
35
|
+
self.name = getattr(self, "NAME", "api")
|
|
36
|
+
self.prefix = self.PREFIX
|
|
37
|
+
self.engine = engine if engine is not None else getattr(self, "ENGINE", None)
|
|
38
|
+
self.tags = list(getattr(self, "TAGS", []))
|
|
39
|
+
self.ops = tuple(getattr(self, "OPS", ()))
|
|
40
|
+
self.schemas = SimpleNamespace()
|
|
41
|
+
self.hooks = SimpleNamespace()
|
|
42
|
+
self.security_deps = tuple(getattr(self, "SECURITY_DEPS", ()))
|
|
43
|
+
self.deps = tuple(getattr(self, "DEPS", ()))
|
|
44
|
+
self.response = getattr(self, "RESPONSE", None)
|
|
45
|
+
# ``models`` is expected to be a dict at runtime for registry lookups.
|
|
46
|
+
self.models = initialize_model_registry(getattr(self, "MODELS", ()))
|
|
47
|
+
|
|
48
|
+
ApiRouter.__init__(
|
|
49
|
+
self,
|
|
50
|
+
prefix=self.PREFIX,
|
|
51
|
+
tags=self.tags,
|
|
52
|
+
dependencies=list(self.security_deps) + list(self.deps),
|
|
53
|
+
**router_kwargs,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# namespace containers
|
|
57
|
+
self.tables: dict[str, Any] = {}
|
|
58
|
+
|
|
59
|
+
_engine_ctx = engine if engine is not None else getattr(self, "ENGINE", None)
|
|
60
|
+
if _engine_ctx is not None:
|
|
61
|
+
_resolver.register_api(self, _engine_ctx)
|
|
62
|
+
_resolver.resolve_provider(api=self)
|
|
63
|
+
|
|
64
|
+
def install_engines(
|
|
65
|
+
self, *, api: Any = None, models: tuple[Any, ...] | None = None
|
|
66
|
+
) -> None:
|
|
67
|
+
# If class declared APIS/MODELS, use them unless explicit args are passed.
|
|
68
|
+
apis = (api,) if api is not None else self.APIS
|
|
69
|
+
models = models if models is not None else self.MODELS
|
|
70
|
+
if apis:
|
|
71
|
+
for a in apis:
|
|
72
|
+
install_from_objects(app=self, api=a, models=models)
|
|
73
|
+
else:
|
|
74
|
+
install_from_objects(app=self, api=None, models=models)
|
|
75
|
+
|
|
76
|
+
def _collect_tables(self) -> list[Any]:
|
|
77
|
+
seen = set()
|
|
78
|
+
tables = []
|
|
79
|
+
for model in self.models.values():
|
|
80
|
+
if not hasattr(model, "__table__"):
|
|
81
|
+
try: # pragma: no cover - defensive remap
|
|
82
|
+
from ..table import Base
|
|
83
|
+
from ..table._base import _materialize_colspecs_to_sqla
|
|
84
|
+
|
|
85
|
+
_materialize_colspecs_to_sqla(model)
|
|
86
|
+
Base.registry.map_declaratively(model)
|
|
87
|
+
except Exception:
|
|
88
|
+
pass
|
|
89
|
+
table = getattr(model, "__table__", None)
|
|
90
|
+
if table is not None and not table.columns:
|
|
91
|
+
continue
|
|
92
|
+
if table is not None and table not in seen:
|
|
93
|
+
seen.add(table)
|
|
94
|
+
tables.append(table)
|
|
95
|
+
return tables
|
|
96
|
+
|
|
97
|
+
initialize = _ddl_initialize
|
tigrbl/api/api_spec.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# tigrbl/tigrbl/v3/api/api_spec.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Callable, Optional, Sequence
|
|
5
|
+
from ..engine.engine_spec import EngineCfg
|
|
6
|
+
from ..response.types import ResponseSpec
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class APISpec:
|
|
11
|
+
"""
|
|
12
|
+
Used to *produce an API subclass* via API.from_spec().
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
name: str = "api"
|
|
16
|
+
prefix: str = ""
|
|
17
|
+
engine: Optional[EngineCfg] = None
|
|
18
|
+
tags: Sequence[str] = field(default_factory=tuple)
|
|
19
|
+
|
|
20
|
+
# NEW
|
|
21
|
+
ops: Sequence[Any] = field(default_factory=tuple)
|
|
22
|
+
schemas: Sequence[Any] = field(default_factory=tuple)
|
|
23
|
+
hooks: Sequence[Callable[..., Any]] = field(default_factory=tuple)
|
|
24
|
+
security_deps: Sequence[Callable[..., Any]] = field(default_factory=tuple)
|
|
25
|
+
deps: Sequence[Callable[..., Any]] = field(default_factory=tuple)
|
|
26
|
+
|
|
27
|
+
response: Optional[ResponseSpec] = None
|
|
28
|
+
|
|
29
|
+
# optional: models this API exposes (auto-install)
|
|
30
|
+
models: Sequence[Any] = field(default_factory=tuple)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# tigrbl/v3/api/mro_collect.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from typing import Any, Callable, Dict, Iterable, Mapping
|
|
7
|
+
|
|
8
|
+
from ..config.constants import TIGRBL_API_HOOKS_ATTR
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("uvicorn")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@lru_cache(maxsize=None)
|
|
14
|
+
def mro_collect_api_hooks(api: type) -> Dict[str, Dict[str, list[Callable[..., Any]]]]:
|
|
15
|
+
"""Collect API-level hook declarations across ``api``'s MRO.
|
|
16
|
+
|
|
17
|
+
The accepted shape mirrors the hooks mapping used by the bindings:
|
|
18
|
+
{alias: {phase: Iterable[callable]}}
|
|
19
|
+
Hooks from base classes are merged with subclass definitions taking precedence.
|
|
20
|
+
"""
|
|
21
|
+
logger.info("Collecting API hooks for %s", api.__name__)
|
|
22
|
+
out: Dict[str, Dict[str, list[Callable[..., Any]]]] = {}
|
|
23
|
+
for base in reversed(api.__mro__):
|
|
24
|
+
mapping = getattr(base, TIGRBL_API_HOOKS_ATTR, None)
|
|
25
|
+
if not isinstance(mapping, Mapping):
|
|
26
|
+
continue
|
|
27
|
+
for alias, phase_map in mapping.items():
|
|
28
|
+
bucket = out.setdefault(str(alias), {})
|
|
29
|
+
if not isinstance(phase_map, Mapping):
|
|
30
|
+
continue
|
|
31
|
+
for phase, items in phase_map.items():
|
|
32
|
+
lst = bucket.setdefault(str(phase), [])
|
|
33
|
+
if isinstance(items, Iterable):
|
|
34
|
+
for fn in items:
|
|
35
|
+
if callable(fn):
|
|
36
|
+
lst.append(fn)
|
|
37
|
+
elif callable(items):
|
|
38
|
+
lst.append(items)
|
|
39
|
+
logger.debug("Collected API hooks for aliases: %s", list(out.keys()))
|
|
40
|
+
return out
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__all__ = ["mro_collect_api_hooks"]
|
tigrbl/api/shortcuts.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# tigrbl/tigrbl/v3/api/shortcuts.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Sequence, Type
|
|
5
|
+
|
|
6
|
+
from .api_spec import APISpec
|
|
7
|
+
from ._api import Api
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def defineApiSpec(
|
|
11
|
+
*,
|
|
12
|
+
# identity
|
|
13
|
+
name: str = "api",
|
|
14
|
+
prefix: str = "",
|
|
15
|
+
tags: Sequence[str] = (),
|
|
16
|
+
# engine
|
|
17
|
+
engine: Any = None,
|
|
18
|
+
# composition
|
|
19
|
+
ops: Sequence[Any] = (),
|
|
20
|
+
schemas: Sequence[Any] = (),
|
|
21
|
+
hooks: Sequence[Any] = (),
|
|
22
|
+
security_deps: Sequence[Any] = (),
|
|
23
|
+
deps: Sequence[Any] = (),
|
|
24
|
+
models: Sequence[Any] = (),
|
|
25
|
+
) -> Type[APISpec]:
|
|
26
|
+
"""
|
|
27
|
+
Build an API-spec class with class attributes only (no instances).
|
|
28
|
+
Use it directly in your class MRO:
|
|
29
|
+
|
|
30
|
+
class TenantA(defineApiSpec(name="tenantA", engine=...)):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
or pass it to `deriveApi(...)` to get a concrete API subclass.
|
|
34
|
+
"""
|
|
35
|
+
attrs = dict(
|
|
36
|
+
NAME=name,
|
|
37
|
+
PREFIX=prefix,
|
|
38
|
+
TAGS=tuple(tags or ()),
|
|
39
|
+
ENGINE=engine,
|
|
40
|
+
OPS=tuple(ops or ()),
|
|
41
|
+
SCHEMAS=tuple(schemas or ()),
|
|
42
|
+
HOOKS=tuple(hooks or ()),
|
|
43
|
+
SECURITY_DEPS=tuple(security_deps or ()),
|
|
44
|
+
DEPS=tuple(deps or ()),
|
|
45
|
+
MODELS=tuple(models or ()),
|
|
46
|
+
)
|
|
47
|
+
return type("APISpec", (APISpec,), attrs)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def deriveApi(**kw: Any) -> Type[Api]:
|
|
51
|
+
"""Produce a concrete :class:`Api` subclass that inherits the spec."""
|
|
52
|
+
Spec = defineApiSpec(**kw)
|
|
53
|
+
return type("APIWithSpec", (Spec, Api), {})
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
__all__ = ["defineApiSpec", "deriveApi"]
|