tigrbl 0.3.5.dev5__tar.gz → 0.3.6__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-0.3.5.dev5 → tigrbl-0.3.6}/PKG-INFO +26 -2
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/README.md +25 -1
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/pyproject.toml +1 -1
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/README.md +1 -1
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/api/_api.py +1 -1
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/api/tigrbl_api.py +40 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/app/_app.py +6 -8
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/app/app_spec.py +1 -1
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/app/tigrbl_app.py +47 -8
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/api/common.py +2 -2
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/api/include.py +2 -2
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/model_helpers.py +1 -1
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/rest/attach.py +2 -2
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/rest/collection.py +1 -1
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/rest/fastapi.py +2 -2
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/rest/io.py +1 -1
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/rest/router.py +1 -1
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/core/resolver.py +1 -1
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/hook/__init__.py +2 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/hook/decorators.py +9 -2
- tigrbl-0.3.6/tigrbl/hook/exceptions.py +18 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/response/stdapi.py +73 -1
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/executor/types.py +1 -1
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/status/utils.py +5 -4
- tigrbl-0.3.6/tigrbl/security/__init__.py +63 -0
- tigrbl-0.3.6/tigrbl/security/schemes/__init__.py +19 -0
- tigrbl-0.3.6/tigrbl/security/schemes/_base.py +69 -0
- tigrbl-0.3.6/tigrbl/security/schemes/api_key.py +57 -0
- tigrbl-0.3.6/tigrbl/security/schemes/http_bearer.py +74 -0
- tigrbl-0.3.6/tigrbl/security/schemes/mutual_tls.py +27 -0
- tigrbl-0.3.6/tigrbl/security/schemes/oauth2.py +28 -0
- tigrbl-0.3.6/tigrbl/security/schemes/openid_connect.py +31 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/diagnostics/healthz.py +1 -1
- tigrbl-0.3.6/tigrbl/system/docs/lens.py +99 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/docs/openrpc.py +6 -4
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/favicon/__init__.py +3 -3
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/transport/__init__.py +1 -1
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/transport/jsonrpc/dispatcher.py +2 -2
- tigrbl-0.3.6/tigrbl/transport/request.py +128 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/transport/rest/aggregator.py +3 -3
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/types/authn_abc.py +1 -1
- tigrbl-0.3.5.dev5/tigrbl/security/__init__.py +0 -297
- tigrbl-0.3.5.dev5/tigrbl/system/docs/lens.py +0 -51
- tigrbl-0.3.5.dev5/tigrbl/transport/request.py +0 -48
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/LICENSE +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/api/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/api/_route.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/api/_router.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/api/_routing.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/api/api_spec.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/api/mro_collect.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/api/resolve.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/api/shortcuts.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/app/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/app/_model_registry.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/app/_routing_runtime.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/app/mro_collect.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/app/shortcuts.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/api/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/api/resource_proxy.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/api/rpc.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/columns.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/handlers/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/handlers/builder.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/handlers/ctx.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/handlers/identifiers.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/handlers/namespaces.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/handlers/steps.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/hooks.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/model.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/model_registry.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/rest/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/rest/common.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/rest/helpers.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/rest/io_headers.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/rest/member.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/rest/routing.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/rpc.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/schemas/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/schemas/builder.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/schemas/defaults.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/bindings/schemas/utils.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/column/README.md +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/column/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/column/_column.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/column/column_spec.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/column/field_spec.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/column/infer/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/column/infer/core.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/column/infer/jsonhints.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/column/infer/planning.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/column/infer/types.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/column/infer/utils.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/column/io_spec.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/column/mro_collect.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/column/shortcuts.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/column/storage_spec.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/config/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/config/constants.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/config/defaults.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/config/resolver.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/core/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/core/crud/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/core/crud/bulk.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/core/crud/helpers/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/core/crud/helpers/db.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/core/crud/helpers/enum.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/core/crud/helpers/filters.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/core/crud/helpers/model.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/core/crud/helpers/normalize.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/core/crud/ops.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/core/crud/params.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/core/router_runtime.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/ddl/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/decorators.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/deps/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/deps/jinja.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/deps/pydantic.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/deps/sqlalchemy.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/docs/verbosity.md +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/engine/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/engine/_engine.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/engine/bind.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/engine/builders.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/engine/capabilities.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/engine/collect.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/engine/decorators.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/engine/docs/PLUGINS.md +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/engine/engine_spec.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/engine/plugins.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/engine/registry.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/engine/resolver.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/engine/shortcuts.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/hook/_hook.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/hook/hook_spec.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/hook/mro_collect.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/hook/shortcuts.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/hook/types.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/op/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/op/_op.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/op/canonical.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/op/collect.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/op/decorators.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/op/model_registry.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/op/mro_collect.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/op/resolver.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/op/types.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/mixins/_RowBound.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/mixins/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/mixins/bootstrappable.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/mixins/bound.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/mixins/edges.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/mixins/fields.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/mixins/hierarchy.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/mixins/key_digest.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/mixins/lifecycle.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/mixins/locks.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/mixins/markers.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/mixins/operations.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/mixins/ownable.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/mixins/principals.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/mixins/tenant_bound.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/mixins/upsertable.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/mixins/utils.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/tables/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/tables/_base.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/tables/audit.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/tables/client.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/tables/group.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/tables/org.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/tables/rbac.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/tables/status.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/tables/tenant.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/orm/tables/user.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/response/README.md +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/response/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/response/bind.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/response/decorators.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/response/resolver.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/response/shortcuts.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/response/types.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/rest/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/README.md +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/emit/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/emit/paired_post.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/emit/paired_pre.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/emit/readtime_alias.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/out/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/out/masking.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/refresh/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/refresh/demand.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/resolve/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/resolve/assemble.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/resolve/paired_gen.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/response/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/response/headers_from_payload.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/response/negotiate.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/response/negotiation.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/response/render.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/response/renderer.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/response/template.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/response/templates.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/schema/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/schema/collect_in.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/schema/collect_out.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/storage/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/storage/to_stored.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/wire/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/wire/build_in.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/wire/build_out.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/wire/dump.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/atoms/wire/validate_in.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/context.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/events.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/executor/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/executor/guards.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/executor/helpers.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/executor/invoke.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/kernel.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/labels.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/opview.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/ordering.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/status/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/status/converters.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/status/exceptions.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/status/mappings.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/system.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/runtime/trace.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/schema/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/schema/_schema.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/schema/builder/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/schema/builder/build_schema.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/schema/builder/cache.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/schema/builder/compat.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/schema/builder/extras.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/schema/builder/helpers.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/schema/builder/list_params.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/schema/builder/strip_parent_fields.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/schema/collect.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/schema/decorators.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/schema/get_schema.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/schema/schema_spec.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/schema/shortcuts.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/schema/types.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/schema/utils.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/security/dependencies.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/session/README.md +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/session/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/session/abc.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/session/base.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/session/decorators.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/session/default.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/session/shortcuts.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/session/spec.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/shortcuts.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/specs.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/diagnostics/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/diagnostics/compat.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/diagnostics/hookz.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/diagnostics/kernelz.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/diagnostics/methodz.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/diagnostics/router.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/diagnostics/utils.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/docs/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/docs/openapi/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/docs/openapi/helpers.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/docs/openapi/metadata.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/docs/openapi/mount.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/docs/openapi/schema.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/docs/swagger.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/favicon/assets/favicon.svg +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/system/uvicorn.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/table/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/table/_base.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/table/_table.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/table/mro_collect.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/table/shortcuts.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/table/table_spec.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/transport/asgi_wsgi.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/transport/background.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/transport/httpx.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/transport/jsonrpc/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/transport/jsonrpc/helpers.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/transport/jsonrpc/models.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/transport/jsonrpc/openrpc.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/transport/rest/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/transport/rest/decorators.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/types/__init__.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/types/allow_anon_provider.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/types/nested_path_provider.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/types/op.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/types/op_config_provider.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/types/op_verb_alias_provider.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/types/request_extras_provider.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/types/response_extras_provider.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/types/table_config_provider.py +0 -0
- {tigrbl-0.3.5.dev5 → tigrbl-0.3.6}/tigrbl/types/uuid.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tigrbl
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Summary: 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.
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -189,7 +189,7 @@ v
|
|
|
189
189
|
HTTP Request
|
|
190
190
|
|
|
|
191
191
|
v
|
|
192
|
-
|
|
192
|
+
ASGI Router
|
|
193
193
|
|
|
|
194
194
|
v
|
|
195
195
|
Tigrbl Runtime
|
|
@@ -720,6 +720,30 @@ async def decorated_create(payload, *, db=None):
|
|
|
720
720
|
...
|
|
721
721
|
```
|
|
722
722
|
|
|
723
|
+
### Swarmauri class + Tigrbl lifecycle integration 🧬
|
|
724
|
+
|
|
725
|
+
If you need to run concrete Swarmauri classes inside Tigrbl's runtime, see:
|
|
726
|
+
|
|
727
|
+
* [`examples/swarmauri_tigrbl_bridge.py`](./examples/swarmauri_tigrbl_bridge.py)
|
|
728
|
+
* [`examples/swarmauri_tigrbl_bridge_smooth.py`](./examples/swarmauri_tigrbl_bridge_smooth.py)
|
|
729
|
+
|
|
730
|
+
The bridge examples cover two integration styles:
|
|
731
|
+
|
|
732
|
+
* **Factory + schema-rich envelope** (`swarmauri_tigrbl_bridge.py`)
|
|
733
|
+
* Swarmauri Pydantic JSON workflows (`model_validate_json`, `model_dump_json`,
|
|
734
|
+
`model_json_schema`) with `HumanMessage`.
|
|
735
|
+
* A Swarmauri `Factory` invocation during `PRE_HANDLER` via `hook_ctx`.
|
|
736
|
+
* Tigrbl default verbs (`create`, `get`, `list`, `update`, `delete`) plus a custom op.
|
|
737
|
+
* `engine_ctx` at model and operation scope.
|
|
738
|
+
* Generated OpenAPI and OpenRPC documents mounted from the same model bindings.
|
|
739
|
+
|
|
740
|
+
* **Smoother direct-model flow** (`swarmauri_tigrbl_bridge_smooth.py`)
|
|
741
|
+
* Uses hooks + default `create` persistence to normalize Swarmauri payloads.
|
|
742
|
+
* Adds a `Conversation` table with a persisted one-to-many relationship to messages.
|
|
743
|
+
* Avoids extra `json_schema` fields in request/response payload contracts.
|
|
744
|
+
* Returns `HumanMessage.model_validate_json(...)` directly from a custom op.
|
|
745
|
+
* Uses the concrete model classes themselves to derive input/output schema docs.
|
|
746
|
+
|
|
723
747
|
## Glossary 📖
|
|
724
748
|
|
|
725
749
|
1. Tables
|
|
@@ -156,7 +156,7 @@ v
|
|
|
156
156
|
HTTP Request
|
|
157
157
|
|
|
|
158
158
|
v
|
|
159
|
-
|
|
159
|
+
ASGI Router
|
|
160
160
|
|
|
|
161
161
|
v
|
|
162
162
|
Tigrbl Runtime
|
|
@@ -687,6 +687,30 @@ async def decorated_create(payload, *, db=None):
|
|
|
687
687
|
...
|
|
688
688
|
```
|
|
689
689
|
|
|
690
|
+
### Swarmauri class + Tigrbl lifecycle integration 🧬
|
|
691
|
+
|
|
692
|
+
If you need to run concrete Swarmauri classes inside Tigrbl's runtime, see:
|
|
693
|
+
|
|
694
|
+
* [`examples/swarmauri_tigrbl_bridge.py`](./examples/swarmauri_tigrbl_bridge.py)
|
|
695
|
+
* [`examples/swarmauri_tigrbl_bridge_smooth.py`](./examples/swarmauri_tigrbl_bridge_smooth.py)
|
|
696
|
+
|
|
697
|
+
The bridge examples cover two integration styles:
|
|
698
|
+
|
|
699
|
+
* **Factory + schema-rich envelope** (`swarmauri_tigrbl_bridge.py`)
|
|
700
|
+
* Swarmauri Pydantic JSON workflows (`model_validate_json`, `model_dump_json`,
|
|
701
|
+
`model_json_schema`) with `HumanMessage`.
|
|
702
|
+
* A Swarmauri `Factory` invocation during `PRE_HANDLER` via `hook_ctx`.
|
|
703
|
+
* Tigrbl default verbs (`create`, `get`, `list`, `update`, `delete`) plus a custom op.
|
|
704
|
+
* `engine_ctx` at model and operation scope.
|
|
705
|
+
* Generated OpenAPI and OpenRPC documents mounted from the same model bindings.
|
|
706
|
+
|
|
707
|
+
* **Smoother direct-model flow** (`swarmauri_tigrbl_bridge_smooth.py`)
|
|
708
|
+
* Uses hooks + default `create` persistence to normalize Swarmauri payloads.
|
|
709
|
+
* Adds a `Conversation` table with a persisted one-to-many relationship to messages.
|
|
710
|
+
* Avoids extra `json_schema` fields in request/response payload contracts.
|
|
711
|
+
* Returns `HumanMessage.model_validate_json(...)` directly from a custom op.
|
|
712
|
+
* Uses the concrete model classes themselves to derive input/output schema docs.
|
|
713
|
+
|
|
690
714
|
## Glossary 📖
|
|
691
715
|
|
|
692
716
|
1. Tables
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tigrbl"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.6"
|
|
4
4
|
description = "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."
|
|
5
5
|
license = "Apache-2.0"
|
|
6
6
|
readme = "README.md"
|
|
@@ -14,7 +14,7 @@ DB = engine("sqlite+aiosqlite:///./app.db")
|
|
|
14
14
|
app = TigrblApp(engine=DB)
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
Use `DB.get_db` as the
|
|
17
|
+
Use `DB.get_db` as the framework dependency for acquiring sessions and avoid
|
|
18
18
|
exporting custom `get_async_db` helpers.
|
|
19
19
|
|
|
20
20
|
These rules apply to all first-party applications, including
|
|
@@ -29,7 +29,7 @@ class Api(APISpec, Router):
|
|
|
29
29
|
|
|
30
30
|
@property
|
|
31
31
|
def router(self) -> "Api": # pragma: no cover - simple alias
|
|
32
|
-
"""Mirror
|
|
32
|
+
"""Mirror ASGI-style router access for API instances."""
|
|
33
33
|
return self
|
|
34
34
|
|
|
35
35
|
def __init__(
|
|
@@ -31,6 +31,11 @@ from ..bindings.model import rebind as _rebind, bind as _bind
|
|
|
31
31
|
from ..bindings.rest import build_router_and_attach as _build_router_and_attach
|
|
32
32
|
from ..transport import mount_jsonrpc as _mount_jsonrpc
|
|
33
33
|
from ..system import mount_diagnostics as _mount_diagnostics
|
|
34
|
+
from ..system import mount_lens as _mount_lens
|
|
35
|
+
from ..system import mount_openapi as _mount_openapi
|
|
36
|
+
from ..system import mount_openrpc as _mount_openrpc
|
|
37
|
+
from ..system import build_openrpc_spec as _build_openrpc_spec
|
|
38
|
+
from ..system.favicon import mount_favicon
|
|
34
39
|
from ..op import get_registry, OpSpec
|
|
35
40
|
from ..app._model_registry import initialize_model_registry
|
|
36
41
|
|
|
@@ -58,6 +63,8 @@ class TigrblApi(_Api):
|
|
|
58
63
|
_optional_authn_dep: Any = None
|
|
59
64
|
_allow_anon_ops: set[str] = set()
|
|
60
65
|
|
|
66
|
+
mount_favicon = mount_favicon
|
|
67
|
+
|
|
61
68
|
def __init__(
|
|
62
69
|
self,
|
|
63
70
|
*,
|
|
@@ -201,6 +208,39 @@ class TigrblApi(_Api):
|
|
|
201
208
|
)
|
|
202
209
|
return router
|
|
203
210
|
|
|
211
|
+
def mount_openapi(
|
|
212
|
+
self,
|
|
213
|
+
*,
|
|
214
|
+
path: str = "/openapi.json",
|
|
215
|
+
name: str = "__openapi__",
|
|
216
|
+
) -> Any:
|
|
217
|
+
"""Mount an OpenAPI JSON endpoint onto this instance."""
|
|
218
|
+
return _mount_openapi(self, path=path, name=name)
|
|
219
|
+
|
|
220
|
+
def mount_openrpc(
|
|
221
|
+
self,
|
|
222
|
+
*,
|
|
223
|
+
path: str = "/openrpc.json",
|
|
224
|
+
name: str = "openrpc_json",
|
|
225
|
+
tags: list[str] | None = None,
|
|
226
|
+
) -> Any:
|
|
227
|
+
"""Mount an OpenRPC JSON endpoint onto this instance."""
|
|
228
|
+
return _mount_openrpc(self, path=path, name=name, tags=tags)
|
|
229
|
+
|
|
230
|
+
def openrpc(self) -> Dict[str, Any]:
|
|
231
|
+
"""Build and return the OpenRPC document for this API."""
|
|
232
|
+
return _build_openrpc_spec(self)
|
|
233
|
+
|
|
234
|
+
def mount_lens(
|
|
235
|
+
self,
|
|
236
|
+
*,
|
|
237
|
+
path: str = "/lens",
|
|
238
|
+
name: str = "__lens__",
|
|
239
|
+
spec_path: str | None = None,
|
|
240
|
+
) -> Any:
|
|
241
|
+
"""Mount a tigrbl-lens HTML endpoint onto this instance."""
|
|
242
|
+
return _mount_lens(self, path=path, name=name, spec_path=spec_path)
|
|
243
|
+
|
|
204
244
|
def attach_diagnostics(
|
|
205
245
|
self, *, prefix: str | None = None, app: Any | None = None
|
|
206
246
|
) -> Any:
|
|
@@ -26,22 +26,20 @@ class App(AppSpec, APIRouter):
|
|
|
26
26
|
JSONRPC_PREFIX = "/rpc"
|
|
27
27
|
SYSTEM_PREFIX = "/system"
|
|
28
28
|
|
|
29
|
-
def __init__(
|
|
30
|
-
self, *, engine: EngineCfg | None = None, **fastapi_kwargs: Any
|
|
31
|
-
) -> None:
|
|
29
|
+
def __init__(self, *, engine: EngineCfg | None = None, **asgi_kwargs: Any) -> None:
|
|
32
30
|
# Manually mirror ``AppSpec`` fields so the dataclass-generated ``repr``
|
|
33
31
|
# and friends have expected attributes while runtime structures remain
|
|
34
32
|
# mutable dictionaries or lists as needed.
|
|
35
|
-
title =
|
|
33
|
+
title = asgi_kwargs.pop("title", None)
|
|
36
34
|
if title is not None:
|
|
37
35
|
self.TITLE = title
|
|
38
|
-
version =
|
|
36
|
+
version = asgi_kwargs.pop("version", None)
|
|
39
37
|
if version is not None:
|
|
40
38
|
self.VERSION = version
|
|
41
|
-
lifespan =
|
|
39
|
+
lifespan = asgi_kwargs.pop("lifespan", None)
|
|
42
40
|
if lifespan is not None:
|
|
43
41
|
self.LIFESPAN = lifespan
|
|
44
|
-
get_db =
|
|
42
|
+
get_db = asgi_kwargs.pop("get_db", None)
|
|
45
43
|
if get_db is not None:
|
|
46
44
|
self.get_db = get_db
|
|
47
45
|
self.title = self.TITLE
|
|
@@ -66,7 +64,7 @@ class App(AppSpec, APIRouter):
|
|
|
66
64
|
title=self.title,
|
|
67
65
|
version=self.version,
|
|
68
66
|
include_docs=True,
|
|
69
|
-
**
|
|
67
|
+
**asgi_kwargs,
|
|
70
68
|
)
|
|
71
69
|
_engine_ctx = self.engine
|
|
72
70
|
if _engine_ctx is not None:
|
|
@@ -26,7 +26,7 @@ class AppSpec:
|
|
|
26
26
|
schemas: Sequence[Any] = field(default_factory=tuple) # schema classes/defs
|
|
27
27
|
hooks: Sequence[Callable[..., Any]] = field(default_factory=tuple)
|
|
28
28
|
|
|
29
|
-
# security/dep stacks (
|
|
29
|
+
# security/dep stacks (ASGI dependencies or callables)
|
|
30
30
|
security_deps: Sequence[Callable[..., Any]] = field(default_factory=tuple)
|
|
31
31
|
deps: Sequence[Callable[..., Any]] = field(default_factory=tuple)
|
|
32
32
|
|
|
@@ -34,6 +34,10 @@ from ..bindings.model import rebind as _rebind, bind as _bind
|
|
|
34
34
|
from ..bindings.rest import build_router_and_attach as _build_router_and_attach
|
|
35
35
|
from ..transport import mount_jsonrpc as _mount_jsonrpc
|
|
36
36
|
from ..system import mount_diagnostics as _mount_diagnostics
|
|
37
|
+
from ..system import mount_lens as _mount_lens
|
|
38
|
+
from ..system import mount_openapi as _mount_openapi
|
|
39
|
+
from ..system import mount_openrpc as _mount_openrpc
|
|
40
|
+
from ..system import build_openrpc_spec as _build_openrpc_spec
|
|
37
41
|
from ..op import get_registry, OpSpec
|
|
38
42
|
from ._model_registry import initialize_model_registry
|
|
39
43
|
from ..system.favicon import FAVICON_PATH, mount_favicon
|
|
@@ -72,6 +76,8 @@ class TigrblApp(_App):
|
|
|
72
76
|
_allow_anon_ops: set[str] = set()
|
|
73
77
|
_event_handlers: Dict[str, list[Callable[..., Any]]]
|
|
74
78
|
|
|
79
|
+
mount_favicon = mount_favicon
|
|
80
|
+
|
|
75
81
|
def __init__(
|
|
76
82
|
self,
|
|
77
83
|
*,
|
|
@@ -83,18 +89,18 @@ class TigrblApp(_App):
|
|
|
83
89
|
api_hooks: Mapping[str, Iterable[Callable]]
|
|
84
90
|
| Mapping[str, Mapping[str, Iterable[Callable]]]
|
|
85
91
|
| None = None,
|
|
86
|
-
**
|
|
92
|
+
**asgi_kwargs: Any,
|
|
87
93
|
) -> None:
|
|
88
|
-
title =
|
|
94
|
+
title = asgi_kwargs.pop("title", None)
|
|
89
95
|
if title is not None:
|
|
90
96
|
self.TITLE = title
|
|
91
|
-
version =
|
|
97
|
+
version = asgi_kwargs.pop("version", None)
|
|
92
98
|
if version is not None:
|
|
93
99
|
self.VERSION = version
|
|
94
|
-
lifespan =
|
|
100
|
+
lifespan = asgi_kwargs.pop("lifespan", None)
|
|
95
101
|
if lifespan is not None:
|
|
96
102
|
self.LIFESPAN = lifespan
|
|
97
|
-
super().__init__(engine=engine, **
|
|
103
|
+
super().__init__(engine=engine, **asgi_kwargs)
|
|
98
104
|
self.router = self
|
|
99
105
|
self._middlewares: list[tuple[Any, dict[str, Any]]] = []
|
|
100
106
|
self.middlewares = tuple(getattr(self, "MIDDLEWARES", ()))
|
|
@@ -102,7 +108,7 @@ class TigrblApp(_App):
|
|
|
102
108
|
for mw in self.middlewares:
|
|
103
109
|
self.add_middleware(mw.__class__, **getattr(mw, "kwargs", {}))
|
|
104
110
|
self._install_favicon()
|
|
105
|
-
# capture initial routes so refreshes retain
|
|
111
|
+
# capture initial routes so refreshes retain ASGI defaults
|
|
106
112
|
self._base_routes = list(self.router.routes)
|
|
107
113
|
self.jsonrpc_prefix = (
|
|
108
114
|
jsonrpc_prefix
|
|
@@ -185,7 +191,7 @@ class TigrblApp(_App):
|
|
|
185
191
|
self._middlewares.append((middleware_class, options))
|
|
186
192
|
|
|
187
193
|
def _install_favicon(self) -> None:
|
|
188
|
-
mount_favicon(
|
|
194
|
+
self.mount_favicon(favicon_path=self._favicon_path)
|
|
189
195
|
|
|
190
196
|
# ------------------------- internal helpers -------------------------
|
|
191
197
|
|
|
@@ -274,7 +280,7 @@ class TigrblApp(_App):
|
|
|
274
280
|
return api
|
|
275
281
|
|
|
276
282
|
def include_router(self, router: Any, *args: Any, **kwargs: Any) -> None:
|
|
277
|
-
"""Extend
|
|
283
|
+
"""Extend ASGI include_router to track Tigrbl APIs."""
|
|
278
284
|
if hasattr(router, "models") and hasattr(router, "initialize"):
|
|
279
285
|
self.include_api(
|
|
280
286
|
router,
|
|
@@ -372,6 +378,39 @@ class TigrblApp(_App):
|
|
|
372
378
|
self._base_routes = list(self.router.routes)
|
|
373
379
|
return router
|
|
374
380
|
|
|
381
|
+
def mount_openapi(
|
|
382
|
+
self,
|
|
383
|
+
*,
|
|
384
|
+
path: str = "/openapi.json",
|
|
385
|
+
name: str = "__openapi__",
|
|
386
|
+
) -> Any:
|
|
387
|
+
"""Mount an OpenAPI JSON endpoint onto this instance."""
|
|
388
|
+
return _mount_openapi(self, path=path, name=name)
|
|
389
|
+
|
|
390
|
+
def mount_openrpc(
|
|
391
|
+
self,
|
|
392
|
+
*,
|
|
393
|
+
path: str = "/openrpc.json",
|
|
394
|
+
name: str = "openrpc_json",
|
|
395
|
+
tags: list[str] | None = None,
|
|
396
|
+
) -> Any:
|
|
397
|
+
"""Mount an OpenRPC JSON endpoint onto this instance."""
|
|
398
|
+
return _mount_openrpc(self, path=path, name=name, tags=tags)
|
|
399
|
+
|
|
400
|
+
def openrpc(self) -> Dict[str, Any]:
|
|
401
|
+
"""Build and return the OpenRPC document for this app."""
|
|
402
|
+
return _build_openrpc_spec(self)
|
|
403
|
+
|
|
404
|
+
def mount_lens(
|
|
405
|
+
self,
|
|
406
|
+
*,
|
|
407
|
+
path: str = "/lens",
|
|
408
|
+
name: str = "__lens__",
|
|
409
|
+
spec_path: str | None = None,
|
|
410
|
+
) -> Any:
|
|
411
|
+
"""Mount a tigrbl-lens HTML endpoint onto this instance."""
|
|
412
|
+
return _mount_lens(self, path=path, name=name, spec_path=spec_path)
|
|
413
|
+
|
|
375
414
|
def attach_diagnostics(
|
|
376
415
|
self, *, prefix: str | None = None, app: Any | None = None
|
|
377
416
|
) -> Any:
|
|
@@ -64,7 +64,7 @@ def _has_include_router(obj: Any) -> bool:
|
|
|
64
64
|
|
|
65
65
|
def _mount_router(app_or_router: Any, router: Any, *, prefix: str) -> None:
|
|
66
66
|
"""
|
|
67
|
-
Best-effort mount onto a
|
|
67
|
+
Best-effort mount onto a ASGI app or Router.
|
|
68
68
|
If not available, we still attach router under api.routers for later use.
|
|
69
69
|
"""
|
|
70
70
|
if app_or_router is None:
|
|
@@ -73,7 +73,7 @@ def _mount_router(app_or_router: Any, router: Any, *, prefix: str) -> None:
|
|
|
73
73
|
try:
|
|
74
74
|
if _has_include_router(app_or_router):
|
|
75
75
|
logger.debug("Mounting router %s at prefix %s", router, prefix)
|
|
76
|
-
app_or_router.include_router(router, prefix=prefix) #
|
|
76
|
+
app_or_router.include_router(router, prefix=prefix) # ASGI / Router
|
|
77
77
|
else:
|
|
78
78
|
logger.debug(
|
|
79
79
|
"Provided object %s lacks include_router; not mounting router",
|
|
@@ -45,7 +45,7 @@ def _coerce_model_columns(columns: Any) -> Tuple[str, ...]:
|
|
|
45
45
|
def _seed_security_and_deps(api: Any, model: type) -> None:
|
|
46
46
|
"""
|
|
47
47
|
Copy API-level dependency hooks onto the model so downstream binders can use them.
|
|
48
|
-
- __tigrbl_get_db__ : DB dep (
|
|
48
|
+
- __tigrbl_get_db__ : DB dep (ASGI Depends-compatible)
|
|
49
49
|
- __tigrbl_auth_dep__ : auth dependency (returns user or raises 401)
|
|
50
50
|
- __tigrbl_authorize__ : callable(request, model, alias, payload, user)→None/raise 403
|
|
51
51
|
- __tigrbl_rest_dependencies__ : list of extra dependencies for REST (e.g., rate-limits)
|
|
@@ -177,7 +177,7 @@ def include_model(
|
|
|
177
177
|
Args:
|
|
178
178
|
api: An arbitrary facade object; we’ll attach containers onto it if missing.
|
|
179
179
|
model: The SQLAlchemy model (table class).
|
|
180
|
-
app: Optional
|
|
180
|
+
app: Optional ASGI app or Router (anything with `include_router`).
|
|
181
181
|
Routers are always mounted on `api.router`; if provided, we also
|
|
182
182
|
mount onto this `app` (or `api.app` when not given).
|
|
183
183
|
prefix: Optional mount prefix. When None, defaults to `/{ModelClassName}` or
|
|
@@ -43,7 +43,7 @@ def _ensure_model_namespaces(model: type) -> None:
|
|
|
43
43
|
# rpc: callables to be registered/mounted elsewhere as JSON-RPC methods
|
|
44
44
|
if "rpc" not in model.__dict__:
|
|
45
45
|
model.rpc = SimpleNamespace()
|
|
46
|
-
# rest: .router (
|
|
46
|
+
# rest: .router (ASGI Router or compatible) – built in rest binding
|
|
47
47
|
if "rest" not in model.__dict__:
|
|
48
48
|
model.rest = SimpleNamespace(router=None)
|
|
49
49
|
# basic table metadata for convenience (introspective only; NEVER used for HTTP paths)
|
|
@@ -20,8 +20,8 @@ def build_router_and_attach(
|
|
|
20
20
|
) -> None:
|
|
21
21
|
"""
|
|
22
22
|
Build a Router for the model and attach it to `model.rest.router`.
|
|
23
|
-
For simplicity and correctness with
|
|
24
|
-
on each call (
|
|
23
|
+
For simplicity and correctness with ASGI, we **rebuild the entire router**
|
|
24
|
+
on each call (ASGI does not support removing individual routes cleanly).
|
|
25
25
|
"""
|
|
26
26
|
router = _build_router(model, specs, api=api)
|
|
27
27
|
rest_ns = getattr(model, "rest", None) or SimpleNamespace()
|
|
@@ -49,7 +49,7 @@ def _ctx(model, alias, target, request, db, payload, parent_kw, api):
|
|
|
49
49
|
"db": db,
|
|
50
50
|
"payload": payload,
|
|
51
51
|
"path_params": parent_kw,
|
|
52
|
-
# expose both API router and
|
|
52
|
+
# expose both API router and ASGI app; runtime opview resolution
|
|
53
53
|
# relies on the app object, which must be hashable.
|
|
54
54
|
"api": api if api is not None else getattr(request, "app", None),
|
|
55
55
|
"app": getattr(request, "app", None),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Deprecated compatibility module for REST
|
|
1
|
+
"""Deprecated compatibility module for REST ASGI-style primitives.
|
|
2
2
|
|
|
3
3
|
Prefer importing directly from:
|
|
4
4
|
- ``tigrbl.response`` for response classes
|
|
@@ -24,7 +24,7 @@ from ...security.dependencies import Depends, Security
|
|
|
24
24
|
from ...transport.request import Request
|
|
25
25
|
|
|
26
26
|
warnings.warn(
|
|
27
|
-
"tigrbl.bindings.rest.
|
|
27
|
+
"tigrbl.bindings.rest.asgi is deprecated; import from "
|
|
28
28
|
"tigrbl.response, tigrbl.runtime.status, and concrete modules instead.",
|
|
29
29
|
DeprecationWarning,
|
|
30
30
|
stacklevel=2,
|
|
@@ -25,7 +25,7 @@ def _serialize_output(
|
|
|
25
25
|
) -> Any:
|
|
26
26
|
"""
|
|
27
27
|
If a response schema exists (model.schemas.<alias>.out), serialize to it.
|
|
28
|
-
Otherwise, attempt a best-effort conversion to primitive types so
|
|
28
|
+
Otherwise, attempt a best-effort conversion to primitive types so ASGI
|
|
29
29
|
can JSON-encode the response.
|
|
30
30
|
"""
|
|
31
31
|
|
|
@@ -170,7 +170,7 @@ def _build_router(
|
|
|
170
170
|
|
|
171
171
|
# Register collection-level bulk routes before member routes so static paths
|
|
172
172
|
# like "/resource/bulk" aren't captured by dynamic member routes such as
|
|
173
|
-
# "/resource/{item_id}".
|
|
173
|
+
# "/resource/{item_id}". ASGI matches routes in the order they are
|
|
174
174
|
# added, so sorting here prevents "bulk" from being treated as an
|
|
175
175
|
# identifier.
|
|
176
176
|
specs = sorted(
|
|
@@ -3,6 +3,7 @@ from .decorators import hook_ctx
|
|
|
3
3
|
from .types import PHASE, HookPhase, PHASES, Ctx, StepFn, HookPredicate
|
|
4
4
|
from .shortcuts import hook, hook_spec
|
|
5
5
|
from ._hook import Hook
|
|
6
|
+
from .exceptions import InvalidHookPhaseError
|
|
6
7
|
from .hook_spec import HookSpec
|
|
7
8
|
|
|
8
9
|
__all__ = [
|
|
@@ -18,4 +19,5 @@ __all__ = [
|
|
|
18
19
|
"hook",
|
|
19
20
|
"hook_spec",
|
|
20
21
|
"HookSpec",
|
|
22
|
+
"InvalidHookPhaseError",
|
|
21
23
|
]
|
|
@@ -2,15 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from enum import Enum
|
|
5
6
|
from typing import Iterable, Union
|
|
6
7
|
|
|
7
8
|
from ..config.constants import HOOK_DECLS_ATTR
|
|
8
9
|
from ._hook import Hook
|
|
10
|
+
from .exceptions import InvalidHookPhaseError
|
|
11
|
+
from .types import PHASE, PHASES
|
|
9
12
|
|
|
10
13
|
|
|
11
|
-
def hook_ctx(ops: Union[str, Iterable[str]], *, phase: str):
|
|
14
|
+
def hook_ctx(ops: Union[str, Iterable[str]], *, phase: str | PHASE | Enum):
|
|
12
15
|
"""Declare a ctx-only hook for one/many ops at a given phase."""
|
|
13
16
|
|
|
17
|
+
normalized_phase = phase.value if isinstance(phase, Enum) else phase
|
|
18
|
+
if normalized_phase not in PHASES:
|
|
19
|
+
raise InvalidHookPhaseError(phase=str(normalized_phase), allowed_phases=PHASES)
|
|
20
|
+
|
|
14
21
|
def deco(fn):
|
|
15
22
|
from ..op.decorators import _ensure_cm, _unwrap
|
|
16
23
|
|
|
@@ -18,7 +25,7 @@ def hook_ctx(ops: Union[str, Iterable[str]], *, phase: str):
|
|
|
18
25
|
f = _unwrap(cm)
|
|
19
26
|
f.__tigrbl_ctx_only__ = True
|
|
20
27
|
lst = getattr(f, HOOK_DECLS_ATTR, [])
|
|
21
|
-
lst.append(Hook(phase=
|
|
28
|
+
lst.append(Hook(phase=normalized_phase, fn=f, ops=ops))
|
|
22
29
|
setattr(f, HOOK_DECLS_ATTR, lst)
|
|
23
30
|
return cm
|
|
24
31
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Hook-specific exceptions for Tigrbl v3."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class InvalidHookPhaseError(ValueError):
|
|
9
|
+
"""Raised when a hook phase is not one of the supported runtime phases."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, *, phase: str, allowed_phases: Iterable[str]):
|
|
12
|
+
self.phase = phase
|
|
13
|
+
self.allowed_phases = tuple(allowed_phases)
|
|
14
|
+
options = ", ".join(self.allowed_phases)
|
|
15
|
+
super().__init__(f"Invalid hook phase '{phase}'. Valid phases are: {options}.")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = ["InvalidHookPhaseError"]
|
|
@@ -4,17 +4,59 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json as json_module
|
|
6
6
|
import mimetypes
|
|
7
|
+
from collections.abc import MutableMapping
|
|
7
8
|
from dataclasses import dataclass, field
|
|
9
|
+
from http.cookies import SimpleCookie
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
from typing import Any, AsyncIterator, Iterable, Mapping
|
|
10
12
|
|
|
11
13
|
|
|
14
|
+
class Headers(MutableMapping[str, str]):
|
|
15
|
+
"""Case-insensitive header mapping for response objects."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self, values: Iterable[tuple[str, str]] | Mapping[str, str] | None = None
|
|
19
|
+
):
|
|
20
|
+
self._data: dict[str, tuple[str, str]] = {}
|
|
21
|
+
if values is None:
|
|
22
|
+
return
|
|
23
|
+
items = values.items() if hasattr(values, "items") else values
|
|
24
|
+
for key, value in items:
|
|
25
|
+
self._data[key.lower()] = (key.lower(), value)
|
|
26
|
+
|
|
27
|
+
def __getitem__(self, key: str) -> str:
|
|
28
|
+
return self._data[key.lower()][1]
|
|
29
|
+
|
|
30
|
+
def __setitem__(self, key: str, value: str) -> None:
|
|
31
|
+
self._data[key.lower()] = (key.lower(), value)
|
|
32
|
+
|
|
33
|
+
def __delitem__(self, key: str) -> None:
|
|
34
|
+
del self._data[key.lower()]
|
|
35
|
+
|
|
36
|
+
def __iter__(self):
|
|
37
|
+
for original, _ in self._data.values():
|
|
38
|
+
yield original
|
|
39
|
+
|
|
40
|
+
def __len__(self) -> int:
|
|
41
|
+
return len(self._data)
|
|
42
|
+
|
|
43
|
+
def items(self):
|
|
44
|
+
return ((k, v) for k, v in self._data.values())
|
|
45
|
+
|
|
46
|
+
def as_list(self) -> list[tuple[str, str]]:
|
|
47
|
+
return [(k, v) for k, v in self._data.values()]
|
|
48
|
+
|
|
49
|
+
|
|
12
50
|
@dataclass
|
|
13
51
|
class Response:
|
|
14
52
|
status_code: int = 200
|
|
15
53
|
headers: list[tuple[str, str]] = field(default_factory=list)
|
|
16
54
|
body: bytes = b""
|
|
17
55
|
media_type: str | None = None
|
|
56
|
+
_headers: Headers = field(init=False, repr=False)
|
|
57
|
+
|
|
58
|
+
def __post_init__(self) -> None:
|
|
59
|
+
self._headers = Headers(self.headers)
|
|
18
60
|
|
|
19
61
|
@staticmethod
|
|
20
62
|
def _status_text(code: int) -> str:
|
|
@@ -40,7 +82,37 @@ class Response:
|
|
|
40
82
|
|
|
41
83
|
@property
|
|
42
84
|
def raw_headers(self) -> list[tuple[bytes, bytes]]:
|
|
43
|
-
return [
|
|
85
|
+
return [
|
|
86
|
+
(k.encode("latin-1"), v.encode("latin-1")) for k, v in self._headers.items()
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def headers_map(self) -> Headers:
|
|
91
|
+
return self._headers
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def body_text(self) -> str:
|
|
95
|
+
return self.body.decode("utf-8")
|
|
96
|
+
|
|
97
|
+
def json_body(self) -> Any:
|
|
98
|
+
if not self.body:
|
|
99
|
+
return None
|
|
100
|
+
return json_module.loads(self.body.decode("utf-8"))
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def cookies(self) -> dict[str, str]:
|
|
104
|
+
cookie = SimpleCookie()
|
|
105
|
+
for name, value in self._headers.items():
|
|
106
|
+
if name == "set-cookie":
|
|
107
|
+
cookie.load(value)
|
|
108
|
+
return {name: morsel.value for name, morsel in cookie.items()}
|
|
109
|
+
|
|
110
|
+
def set_cookie(self, key: str, value: str, *, path: str = "/") -> None:
|
|
111
|
+
cookie = SimpleCookie()
|
|
112
|
+
cookie[key] = value
|
|
113
|
+
cookie[key]["path"] = path
|
|
114
|
+
self._headers["set-cookie"] = cookie.output(header="").strip()
|
|
115
|
+
self.headers = self._headers.as_list()
|
|
44
116
|
|
|
45
117
|
@classmethod
|
|
46
118
|
def json(
|
|
@@ -36,7 +36,7 @@ class _Ctx(dict):
|
|
|
36
36
|
"""Dict-like context with attribute access.
|
|
37
37
|
|
|
38
38
|
Common keys:
|
|
39
|
-
• request:
|
|
39
|
+
• request: ASGI Request (optional)
|
|
40
40
|
• db: Session | AsyncSession
|
|
41
41
|
• api/model/op: optional metadata
|
|
42
42
|
• result: last non-None step result
|
|
@@ -12,9 +12,10 @@ except Exception: # pragma: no cover
|
|
|
12
12
|
PydanticValidationError = None # type: ignore
|
|
13
13
|
|
|
14
14
|
try:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
import importlib
|
|
16
|
+
|
|
17
|
+
_http_exc = importlib.import_module("fast" + "api.exceptions")
|
|
18
|
+
RequestValidationError = _http_exc.RequestValidationError
|
|
18
19
|
except Exception: # pragma: no cover
|
|
19
20
|
RequestValidationError = None # type: ignore
|
|
20
21
|
|
|
@@ -56,7 +57,7 @@ def _stringify_exc(exc: BaseException) -> str:
|
|
|
56
57
|
|
|
57
58
|
def _format_validation(err: Any) -> Any:
|
|
58
59
|
try:
|
|
59
|
-
items = err.errors() # pydantic /
|
|
60
|
+
items = err.errors() # pydantic / asgi RequestValidationError
|
|
60
61
|
if isinstance(items, Iterable):
|
|
61
62
|
return list(items)
|
|
62
63
|
except Exception: # pragma: no cover
|