A modern pure ASGI/WSGI Python framework for building schema-first REST and JSON-RPC APIs with SQLAlchemy models, typed validation, lifecycle hooks, and engine extension support.
Project description
Tigrbl ๐ ๐
A high-leverage ASGI meta-framework that turns plain SQLAlchemy models into a fully-featured REST+RPC surface with near-zero boilerplate. ๐
Features โจ
- โก Zero-boilerplate CRUD for SQLAlchemy models
- ๐ Unified REST and RPC endpoints from a single definition
- ๐ช Hookable phase system for deep customization
- ๐งฉ Pluggable engine and provider abstractions
- ๐ Built as an ASGI-native framework with Pydantic-powered schema generation
Terminology ๐
- Tenant ๐ข โ a namespace used to group related resources.
- Principal ๐ค โ an owner of resources, such as an individual user or an organization.
- Resource ๐ฆ โ a logical collection of data or functionality exposed by the API.
- Engine โ๏ธ โ the database connection and transaction manager backing a resource.
- Model / Table ๐งฑ โ the ORM or database representation of a resource's records.
- Column ๐ โ a field on a model that maps to a table column.
- Operation ๐ ๏ธ โ a verb-driven action executed against a resource.
- Hook ๐ช โ a callback that runs during a phase to customize behavior.
- Phase โฑ๏ธ โ a step in the request lifecycle where hooks may run.
- Verb ๐ค โ the canonical name of an operation such as create or read.
- Runtime ๐ง โ orchestrates phases and hooks while processing a request.
- Kernel ๐งฉ โ the core dispatcher invoked by the runtime to handle operations.
- Schema ๐งฌ โ the structured shape of request or response data.
- Request ๐ฅ โ inbound data and context provided to an operation.
- Response ๐ค โ outbound result returned after an operation completes.
Built-in Verbs ๐งฐ
Tigrbl exposes a canonical set of operations that surface as both REST
and RPC endpoints. The table below summarizes the default REST routes,
RPC methods, arity, and the expected input and output shapes for each
verb. {resource} stands for the collection path and {id} is the
primary key placeholder.
| Verb | REST route | RPC method | Arity | Input type | Output type |
|---|---|---|---|---|---|
create โ |
POST /{resource} |
Model.create |
collection | dict | dict |
read ๐ |
GET /{resource}/{id} |
Model.read |
member | โ | dict |
update โ๏ธ |
PATCH /{resource}/{id} |
Model.update |
member | dict | dict |
replace โป๏ธ |
PUT /{resource}/{id} |
Model.replace |
member | dict | dict |
merge ๐งฌ |
PATCH /{resource}/{id} |
Model.merge |
member | dict | dict |
delete ๐๏ธ |
DELETE /{resource}/{id} |
Model.delete |
member | โ | dict |
list ๐ |
GET /{resource} |
Model.list |
collection | dict | array |
clear ๐งน |
DELETE /{resource} |
Model.clear |
collection | dict | dict |
bulk_create ๐ฆโ |
POST /{resource} |
Model.bulk_create |
collection | array | array |
bulk_update ๐ฆโ๏ธ |
PATCH /{resource} |
Model.bulk_update |
collection | array | array |
bulk_replace ๐ฆโป๏ธ |
PUT /{resource} |
Model.bulk_replace |
collection | array | array |
bulk_merge ๐ฆ๐งฌ |
PATCH /{resource} |
Model.bulk_merge |
collection | array | array |
bulk_delete ๐ฆ๐๏ธ |
DELETE /{resource} |
Model.bulk_delete |
collection | dict | dict |
bulk_read โ |
โ | โ | โ | โ | โ |
Update, Merge, and Replace ๐
update applies a shallow PATCH: only the supplied fields change and
missing fields are left untouched. merge performs a deep merge with
upsert semanticsโif the target row is absent it is created, and nested
mapping fields are merged rather than replaced. replace follows PUT
semantics, overwriting the entire record and nulling any omitted
attributes.
Verb Overrides ๐งญ
Because create and bulk_create share the same collection POST
route, enabling bulk_create removes the REST create endpoint; the
Model.create RPC method remains available. Likewise, bulk_delete
supersedes clear by claiming the collection DELETE route. Only one
of each conflicting pair can be exposed at a time. Other verbs coexist
without conflict because they operate on distinct paths or HTTP
methods.
Phase Lifecycle โ๏ธ
Tigrbl operations execute through a fixed sequence of phases. Hook chains can attach handlers at any phase to customize behavior or enforce policy.
| Phase | Description |
|---|---|
PRE_TX_BEGIN โณ |
Pre-transaction checks before a database session is used. |
START_TX ๐ฆ |
Open a new transaction when one is not already active. |
PRE_HANDLER ๐งน |
Validate the request and prepare resources for the handler. |
HANDLER โถ๏ธ |
Execute the core operation logic within the transaction. |
POST_HANDLER ๐ง |
Post-processing while still inside the transaction. |
PRE_COMMIT โ
|
Final verification before committing; writes are frozen. |
END_TX ๐งพ |
Commit and close the transaction. |
POST_COMMIT ๐ |
Steps that run after commit but before the response is returned. |
POST_RESPONSE ๐ฎ |
Fire-and-forget work after the response has been sent. |
ON_ERROR ๐ |
Fallback error handler when no phase-specific chain matches. |
ON_PRE_TX_BEGIN_ERROR ๐งฏ |
Handle errors raised during PRE_TX_BEGIN. |
ON_START_TX_ERROR ๐งฏ |
Handle errors raised during START_TX. |
ON_PRE_HANDLER_ERROR ๐งฏ |
Handle errors raised during PRE_HANDLER. |
ON_HANDLER_ERROR ๐งฏ |
Handle errors raised during HANDLER. |
ON_POST_HANDLER_ERROR ๐งฏ |
Handle errors raised during POST_HANDLER. |
ON_PRE_COMMIT_ERROR ๐งฏ |
Handle errors raised during PRE_COMMIT. |
ON_END_TX_ERROR ๐งฏ |
Handle errors raised during END_TX. |
ON_POST_COMMIT_ERROR ๐งฏ |
Handle errors raised during POST_COMMIT. |
ON_POST_RESPONSE_ERROR ๐งฏ |
Handle errors raised during POST_RESPONSE. |
ON_ROLLBACK โฉ๏ธ |
Run when the transaction rolls back to perform cleanup. |
Happy-path flow
PRE\_TX\_BEGIN
|
START\_TX
|
PRE\_HANDLER
|
HANDLER
|
POST\_HANDLER
|
PRE\_COMMIT
|
END\_TX
|
POST\_COMMIT
|
POST\_RESPONSE
If a phase raises an exception, control transfers to the matching
ON_<PHASE>_ERROR chain or falls back to ON_ERROR, with ON_ROLLBACK
executing when the transaction is rolled back.
Request โ Response Flow Examples ๐
REST example
Client
|
v
HTTP Request
|
v
ASGI Router
|
v
Tigrbl Runtime
|
v
Operation Handler
|
v
HTTP Response
RPC example
Client
|
v
JSON-RPC Request
|
v
RPC Dispatcher
|
v
Tigrbl Runtime
|
v
Operation Handler
|
v
JSON-RPC Response
Hooks ๐ช
Hooks allow you to plug custom logic into any phase of a verb. Use the
hook_ctx decorator to declare context-only hooks:
from tigrbl import Base, hook_ctx
class Item(Base):
__tablename__ = "items"
@hook_ctx(ops="create", phase="PRE_HANDLER")
async def validate(cls, ctx):
if ctx["request"].payload.get("name") == "bad":
raise ValueError("invalid name")
The function runs during the PRE_HANDLER phase of create. The
ctx mapping provides request and response objects, a database session,
and values from earlier hooks.
Hooks can also be registered imperatively:
async def audit(ctx):
...
class Item(Base):
__tigrbl_hooks__ = {"delete": {"POST_COMMIT": [audit]}}
Running apps expose a /system/hookz route that lists all registered
hooks. ๐
Step Types ๐งฑ
Tigrbl orders work into labeled steps that control how phases run:
- secdeps ๐ โ security dependencies executed before other checks. Downstream applications declare these to enforce auth or policy.
- deps ๐งฉ โ general dependencies resolved ahead of phase handlers. Downstream code provides these to inject request context or resources.
- sys ๐๏ธ โ system steps bundled with Tigrbl that drive core behavior. Maintainers own these and downstream packages should not modify them.
- atoms โ๏ธ โ built-in runtime units such as schema collectors or wire validators. These are maintained by the core team.
- hooks ๐ช โ extension points that downstream packages register to customize phase behavior.
Only secdeps, deps, and hooks are expected to be configured downstream;
sys and atom steps are maintained by the Tigrbl maintainers.
Kernelz Labeling ๐
Running apps expose a /system/kernelz diagnostics endpoint that returns the
kernel's phase plan for each model and operation. Every entry is prefixed by
its phase and a descriptive label, for example:
PRE_TX:secdep:myapp.auth.require_user
HANDLER:hook:wire:myapp.handlers.audit@HANDLER
END_TX:hook:sys:txn:commit@END_TX
POST_HANDLER:atom:wire:dump@POST_HANDLER
The token after the phase identifies the step type:
secdepanddepโ security and general dependencies asPRE_TX:secdep:<callable>andPRE_TX:dep:<callable>.hook:sysโ built-in system hooks shipped with Tigrbl.hook:wireโ default label for user hooks including module/function name + phase.atom:{domain}:{subject}โ runtime atoms, e.g.atom:wire:dump.
These labels allow downstream services to inspect execution order and debug how work is scheduled. ๐งญ
Configuration Overview โ๏ธ
Operation Config Precedence ๐งฎ
When merging configuration for a given operation, Tigrbl layers settings in increasing order of precedence:
- defaults
- app config
- API config
- table config
- column
.cfgentries - operation spec
- per-request overrides
Later entries override earlier ones, so request overrides win over all other
sources. This can be summarized as
overrides > opspec > colspecs > tabspec > apispec > appspec > defaults.
Schema Config Precedence ๐งฌ
Tigrbl merges schema configuration from several scopes. Later layers override earlier ones, with the precedence order:
- defaults (lowest)
- app configuration
- API configuration
- table configuration
- column-level
cfgvalues - op-specific
cfg - per-request overrides (highest)
This hierarchy ensures that the most specific settings always win. ๐ฅ
Table-Level ๐งพ
__tigrbl_request_extras__โ verb-scoped virtual request fields.__tigrbl_response_extras__โ verb-scoped virtual response fields.__tigrbl_register_hooks__โ hook registration entry point.__tigrbl_nested_paths__โ nested REST path segments.__tigrbl_allow_anon__โ verbs permitted without auth.__tigrbl_owner_policy__/__tigrbl_tenant_policy__โ server vs client field injection.__tigrbl_verb_aliases__&__tigrbl_verb_alias_policy__โ custom verb names.
Routing ๐งญ
__tigrbl_nested_paths__for hierarchical routing.__tigrbl_verb_aliases__for custom verbs.__tigrbl_verb_alias_policy__to scope alias application.
Persistence ๐พ
- Mixins such as
Upsertable,Bootstrappable,GUIDPk,Timestamped. - Policies
__tigrbl_owner_policy__and__tigrbl_tenant_policy__. transactionaldecorator for atomic RPC + REST endpoints.
Security ๐
- Pluggable
AuthNProviderinterface. __tigrbl_allow_anon__to permit anonymous access.
Default Precedence ๐ง
When assembling values for persistence, defaults are resolved in this order:
- Client-supplied value
- API
default_factory - ORM default
- Database
server_default - HTTP 422 if the field is required and still missing
Database Guards ๐ก๏ธ
Tigrbl executes each phase under database guards that temporarily replace
commit and flush on the SQLAlchemy session. Guards prevent writes or
commits outside their allowed phases and only permit commits when Tigrbl
owns the transaction. See the
runtime documentation for the full
matrix of phase policies.
The START_TX phase opens a transaction and disables session.flush,
allowing validation and hooks to run before any statements hit the
database. Once the transaction exists, PRE_HANDLER, HANDLER, and
POST_HANDLER phases permit flushes so pending writes reach the database
without committing. The workflow concludes in END_TX, which performs a
final flush and commits the transaction when the runtime owns it. โ
Response and Template Specs ๐
Customize outbound responses with ResponseSpec and TemplateSpec. These dataclasses
control headers, status codes, and optional template rendering. See
tigrbl/v3/response/README.md for field descriptions and examples.
Dependencies ๐ฆ
- SQLAlchemy for ORM integration.
- Pydantic for schema generation.
- ASGI-native routing and dependency injection.
Best Design Practices โ
The following practices are the canonical, production-ready patterns for building on Tigrbl. Each rule is explained and demonstrated with approved usage. These are not optionalโadhering to them keeps the runtime predictable, preserves hook lifecycle guarantees, and ensures schema consistency across REST and RPC surfaces.
1) Never import SQLAlchemy directly or bypass Tigrbl APIs
Why: Direct imports bypass Tigrbl's compatibility layer and make it harder to evolve internal dependencies. Use the Tigrbl exports so your code stays aligned with the frameworkโs versioned ASGI API.
โ Preferred:
from tigrbl import Base, TigrblApp, TigrblApi
from tigrbl.types import Integer, String, Mapped
from tigrbl.types import Depends, HTTPException, Request
๐ซ Avoid:
from sqlalchemy import Integer, String
from some_framework import Depends
2) Do not coerce UUIDs manually
Why: Tigrbl schemas and types already normalize UUIDs. Manual coercion creates inconsistent behavior across engines and breaks schema-level validation.
โ Preferred:
from tigrbl.types import PgUUID, uuid4, Mapped
class Item(Table):
__tablename__ = "items"
id: Mapped[PgUUID] = acol(primary_key=True, default=uuid4)
๐ซ Avoid:
from uuid import UUID
item_id = UUID(str(payload["id"]))
3) Use engine specs for persistence, not ad-hoc engines
Why: Engine specs make persistence declarative, testable, and compatible with engine resolution across app, API, table, and op scopes.
โ Preferred:
from tigrbl.engine.shortcuts import engine_spec
from tigrbl.engine.decorators import engine_ctx
spec = engine_spec(kind="postgres", async_=True, host="db", name="app_db")
@engine_ctx(spec)
class App:
...
๐ซ Avoid:
from sqlalchemy.ext.asyncio import create_async_engine
engine = create_async_engine("postgresql+asyncpg://...")
4) Never call DB session methods directly
Why: Direct calls bypass the hook lifecycle and the database guards.
Use model handlers or app.<Model>.handlers.<op> so hooks, policies, and
schema enforcement run consistently.
โ Preferred:
result = await Item.handlers.create(payload, ctx=request_ctx)
# or from a Tigrbl app instance:
result = await app.Item.handlers.create(payload, ctx=request_ctx)
๐ซ Avoid:
db.add(item)
await db.execute(statement)
5) Always use encapsulated payloads as inputs and outputs
Why: Tigrbl expects request/response envelopes to preserve metadata, support policy enforcement, and keep REST/RPC in lockstep.
โ Preferred:
from tigrbl import get_schema
CreateIn = get_schema(Item, "create", "in")
CreateOut = get_schema(Item, "create", "out")
payload = CreateIn(name="Widget")
result = await Item.handlers.create(payload, ctx=request_ctx)
response = CreateOut(result=result)
๐ซ Avoid:
payload = {"name": "Widget"}
result = await Item.handlers.create(payload)
6) Encapsulation must use get_schema(...)
Why: get_schema guarantees the envelope is aligned to the configured
schema and respects schema overrides, request extras, and response extras.
โ Preferred:
ListIn = get_schema(Item, "list", "in")
ListOut = get_schema(Item, "list", "out")
๐ซ Avoid:
from pydantic import BaseModel
class ListIn(BaseModel):
payload: dict
7) Table must be the first inherited class for all models
Why: Tigrbl inspects base classes for lifecycle and configuration.
Putting Table first preserves deterministic MRO behavior.
โ Preferred:
from tigrbl.orm.tables import Table
from tigrbl.orm.mixins import Timestamped
class Item(Table, Timestamped):
__tablename__ = "items"
๐ซ Avoid:
class Item(Timestamped, Table):
__tablename__ = "items"
8) Never call db.flush() or db.commit()
Why: The hook lifecycle owns transactional boundaries. Manual flush or commit short-circuits phase guards and can corrupt the request lifecycle.
โ Preferred:
@hook_ctx(ops="create", phase="HANDLER")
async def handler(ctx):
await Item.handlers.create(ctx["request"].payload, ctx=ctx)
๐ซ Avoid:
db.flush()
db.commit()
9) Use ops for new REST/RPC methodsโnever add ad-hoc framework routes
Why: Ops keep routing, schemas, hooks, and policies unified. Custom custom framework routes bypass these guarantees.
โ Preferred:
from tigrbl import op_ctx
@op_ctx(name="rotate_keys", method="POST", path="/keys/rotate")
async def rotate_keys(payload, *, ctx):
return await Key.handlers.rotate(payload, ctx=ctx)
๐ซ Avoid:
from some_framework import APIRouter
router = APIRouter()
@router.post("/keys/rotate")
async def rotate_keys(payload):
...
10) Use context decorators where appropriate
Why: Context decorators (engine_ctx, schema_ctx, op_ctx,
hook_ctx) provide explicit, declarative binding of behavior and are
resolved deterministically by the runtime.
โ Preferred:
from tigrbl import hook_ctx, op_ctx, schema_ctx
from tigrbl.engine.decorators import engine_ctx
@engine_ctx(kind="sqlite", mode="memory")
class Item(Table):
__tablename__ = "items"
@schema_ctx(ops="create", cfg={"exclude": {"id"}})
class ItemCreateSchema:
model = Item
@op_ctx(name="export", method="GET", path="/items/export")
async def export_items(payload, *, ctx):
return await Item.handlers.list(payload, ctx=ctx)
@hook_ctx(ops="create", phase="PRE_HANDLER")
async def validate(ctx):
...
Engine & Provider examples ๐ ๏ธ
from tigrbl.engine.shortcuts import engine_spec, prov
from tigrbl.engine._engine import Engine, Provider
# Build an EngineSpec from a DSN string
spec = engine_spec("sqlite://:memory:")
# Or from keyword arguments
spec_pg = engine_spec(kind="postgres", async_=True, host="db", name="app_db")
# Lazy Provider from the spec
provider = prov(spec) # same as Provider(spec)
with provider.session() as session:
session.execute("SELECT 1")
# Engine faรงade wrapping a Provider
eng = Engine(spec_pg)
async with eng.asession() as session:
await session.execute("SELECT 1")
# Direct Provider construction is also supported
provider_pg = Provider(spec_pg)
Attaching engine contexts ๐
engine_ctx binds database configuration to different layers. It accepts a
DSN string, a mapping, an EngineSpec, a Provider, or an Engine. The
resolver chooses the most specific binding in the order
op > table > api > app.
Engine precedence ๐ฅ
When engine contexts are declared at multiple scopes, Tigrbl resolves them with strict precedence:
- Op level โ bindings attached directly to an operation take highest priority.
- Table/Model level โ definitions on a model or table override API and app defaults.
- API level โ bindings on the API class apply when no model-specific context exists.
- App level โ the default engine supplied to the application is used last.
This ordering ensures that the most specific engine context always wins.
Declarative bindings ๐
from types import SimpleNamespace
from tigrbl.engine.shortcuts import prov, engine
app = SimpleNamespace(db=prov(kind="sqlite", mode="memory"))
alt = SimpleNamespace(db=engine(kind="sqlite", mode="memory"))
class API:
db = {"kind": "sqlite", "memory": True}
class Item:
__tablename__ = "items"
table_config = {"db": {"kind": "sqlite", "memory": True}}
async def create(payload, *, db=None):
...
create.__tigrbl_engine_ctx__ = {
"kind": "postgres",
"async": True,
"host": "db",
"name": "op_db",
}
Decorative bindings ๐๏ธ
from tigrbl.engine.decorators import engine_ctx
from tigrbl.engine.shortcuts import prov, engine
@engine_ctx(prov(kind="sqlite", mode="memory"))
class App:
pass
@engine_ctx(engine(kind="sqlite", mode="memory"))
class DecoratedAPI:
pass
@engine_ctx(kind="sqlite", mode="memory")
class DecoratedItem:
__tablename__ = "items"
@engine_ctx(kind="postgres", async_=True, host="db", name="op_db")
async def decorated_create(payload, *, db=None):
...
Swarmauri class + Tigrbl lifecycle integration ๐งฌ
If you need to run concrete Swarmauri classes inside Tigrbl's runtime, see:
The bridge examples cover two integration styles:
-
Factory + schema-rich envelope (
swarmauri_tigrbl_bridge.py)- Swarmauri Pydantic JSON workflows (
model_validate_json,model_dump_json,model_json_schema) withHumanMessage. - A Swarmauri
Factoryinvocation duringPRE_HANDLERviahook_ctx. - Tigrbl default verbs (
create,get,list,update,delete) plus a custom op. engine_ctxat model and operation scope.- Generated OpenAPI and OpenRPC documents mounted from the same model bindings.
- Swarmauri Pydantic JSON workflows (
-
Smoother direct-model flow (
swarmauri_tigrbl_bridge_smooth.py)- Uses hooks + default
createpersistence to normalize Swarmauri payloads. - Adds a
Conversationtable with a persisted one-to-many relationship to messages. - Avoids extra
json_schemafields in request/response payload contracts. - Returns
HumanMessage.model_validate_json(...)directly from a custom op. - Uses the concrete model classes themselves to derive input/output schema docs.
- Uses hooks + default
Glossary ๐
- Tables
- Schemas
- Schema Overlays (Request Extras)
- Phases
- Phase Lifecycle
- Request
- Request Ctx
- Default Flush
- Core
- Core_Raw
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tigrbl-0.3.7.dev2.tar.gz.
File metadata
- Download URL: tigrbl-0.3.7.dev2.tar.gz
- Upload date:
- Size: 250.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f80ffe0c41337e54b2626864af0647678934928ce6241b2c2907400cd7b349f8
|
|
| MD5 |
142dc9aa1169550e442bde8d8571f710
|
|
| BLAKE2b-256 |
8a01cab9df8e29794e642d5d4ff9c81715fc2f4272e0e129cd769e916fba2a2e
|
File details
Details for the file tigrbl-0.3.7.dev2-py3-none-any.whl.
File metadata
- Download URL: tigrbl-0.3.7.dev2-py3-none-any.whl
- Upload date:
- Size: 376.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c8f4ebc49163ccbf6606269c004f99c272e3ee8009785201aab7735c1a5ab93c
|
|
| MD5 |
712514d4c7d2903ee332ddbaeaa02ec7
|
|
| BLAKE2b-256 |
c94d4b62881d59ec7c91e2f0ec6867c2561f234d4a614f2d37a74a2becc157da
|