tigrbl 0.3.4.dev2__tar.gz → 0.3.5.dev5__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.4.dev2 → tigrbl-0.3.5.dev5}/PKG-INFO +233 -14
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/README.md +228 -5
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/pyproject.toml +6 -10
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/__init__.py +14 -3
- tigrbl-0.3.5.dev5/tigrbl/api/__init__.py +19 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/api/_api.py +49 -7
- tigrbl-0.3.5.dev5/tigrbl/api/_route.py +119 -0
- tigrbl-0.3.5.dev5/tigrbl/api/_router.py +271 -0
- tigrbl-0.3.5.dev5/tigrbl/api/_routing.py +143 -0
- tigrbl-0.3.5.dev5/tigrbl/api/resolve.py +151 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/api/tigrbl_api.py +12 -4
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/app/_app.py +19 -8
- tigrbl-0.3.5.dev5/tigrbl/app/_routing_runtime.py +24 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/app/tigrbl_app.py +79 -5
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/api/include.py +6 -1
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/rest/collection.py +3 -3
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/rest/common.py +26 -12
- tigrbl-0.3.5.dev5/tigrbl/bindings/rest/fastapi.py +52 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/rest/helpers.py +1 -1
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/rest/io.py +4 -1
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/rest/io_headers.py +1 -1
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/rest/member.py +3 -3
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/rest/router.py +50 -4
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/rest/routing.py +11 -4
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/rpc.py +26 -12
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/column/__init__.py +4 -1
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/column/storage_spec.py +13 -0
- tigrbl-0.3.5.dev5/tigrbl/core/crud/params.py +50 -0
- tigrbl-0.3.5.dev5/tigrbl/core/resolver.py +81 -0
- tigrbl-0.3.5.dev5/tigrbl/core/router_runtime.py +164 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/ddl/__init__.py +9 -6
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/deps/__init__.py +10 -5
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/engine/builders.py +14 -5
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/engine/engine_spec.py +7 -4
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/engine/resolver.py +137 -4
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/engine/shortcuts.py +19 -4
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/mixins/_RowBound.py +1 -1
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/mixins/ownable.py +1 -1
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/mixins/tenant_bound.py +1 -1
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/response/__init__.py +16 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/response/shortcuts.py +34 -40
- tigrbl-0.3.5.dev5/tigrbl/response/stdapi.py +194 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/__init__.py +2 -2
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/response/renderer.py +2 -1
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/response/templates.py +1 -1
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/wire/validate_in.py +2 -1
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/executor/invoke.py +1 -1
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/executor/types.py +1 -3
- {tigrbl-0.3.4.dev2/tigrbl/runtime/errors → tigrbl-0.3.5.dev5/tigrbl/runtime/status}/__init__.py +3 -1
- {tigrbl-0.3.4.dev2/tigrbl/runtime/errors → tigrbl-0.3.5.dev5/tigrbl/runtime/status}/converters.py +2 -3
- {tigrbl-0.3.4.dev2/tigrbl/runtime/errors → tigrbl-0.3.5.dev5/tigrbl/runtime/status}/exceptions.py +26 -1
- {tigrbl-0.3.4.dev2/tigrbl/runtime/errors → tigrbl-0.3.5.dev5/tigrbl/runtime/status}/mappings.py +23 -0
- {tigrbl-0.3.4.dev2/tigrbl/runtime/errors → tigrbl-0.3.5.dev5/tigrbl/runtime/status}/utils.py +1 -38
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/system.py +1 -1
- tigrbl-0.3.5.dev5/tigrbl/security/__init__.py +297 -0
- tigrbl-0.3.5.dev5/tigrbl/security/dependencies.py +21 -0
- tigrbl-0.3.5.dev5/tigrbl/system/__init__.py +77 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/system/diagnostics/compat.py +2 -2
- tigrbl-0.3.5.dev5/tigrbl/system/docs/__init__.py +42 -0
- tigrbl-0.3.5.dev5/tigrbl/system/docs/lens.py +51 -0
- tigrbl-0.3.5.dev5/tigrbl/system/docs/openapi/__init__.py +34 -0
- tigrbl-0.3.5.dev5/tigrbl/system/docs/openapi/helpers.py +204 -0
- tigrbl-0.3.5.dev5/tigrbl/system/docs/openapi/metadata.py +21 -0
- tigrbl-0.3.5.dev5/tigrbl/system/docs/openapi/mount.py +30 -0
- tigrbl-0.3.5.dev5/tigrbl/system/docs/openapi/schema.py +138 -0
- {tigrbl-0.3.4.dev2/tigrbl/transport/jsonrpc → tigrbl-0.3.5.dev5/tigrbl/system/docs}/openrpc.py +27 -1
- tigrbl-0.3.5.dev5/tigrbl/system/docs/swagger.py +68 -0
- tigrbl-0.3.5.dev5/tigrbl/system/favicon/__init__.py +41 -0
- tigrbl-0.3.5.dev5/tigrbl/transport/__init__.py +121 -0
- tigrbl-0.3.5.dev5/tigrbl/transport/asgi_wsgi.py +181 -0
- tigrbl-0.3.5.dev5/tigrbl/transport/background.py +29 -0
- tigrbl-0.3.5.dev5/tigrbl/transport/httpx.py +25 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/transport/jsonrpc/dispatcher.py +49 -29
- tigrbl-0.3.5.dev5/tigrbl/transport/jsonrpc/openrpc.py +3 -0
- tigrbl-0.3.5.dev5/tigrbl/transport/request.py +48 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/transport/rest/__init__.py +10 -1
- tigrbl-0.3.5.dev5/tigrbl/transport/rest/decorators.py +30 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/types/__init__.py +8 -13
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/types/authn_abc.py +1 -1
- tigrbl-0.3.4.dev2/tigrbl/api/__init__.py +0 -6
- tigrbl-0.3.4.dev2/tigrbl/bindings/rest/fastapi.py +0 -76
- tigrbl-0.3.4.dev2/tigrbl/deps/fastapi.py +0 -41
- tigrbl-0.3.4.dev2/tigrbl/deps/starlette.py +0 -36
- tigrbl-0.3.4.dev2/tigrbl/system/__init__.py +0 -13
- tigrbl-0.3.4.dev2/tigrbl/transport/__init__.py +0 -75
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/LICENSE +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/README.md +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/api/api_spec.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/api/mro_collect.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/api/shortcuts.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/app/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/app/_model_registry.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/app/app_spec.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/app/mro_collect.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/app/shortcuts.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/api/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/api/common.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/api/resource_proxy.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/api/rpc.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/columns.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/handlers/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/handlers/builder.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/handlers/ctx.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/handlers/identifiers.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/handlers/namespaces.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/handlers/steps.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/hooks.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/model.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/model_helpers.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/model_registry.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/rest/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/rest/attach.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/schemas/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/schemas/builder.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/schemas/defaults.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/bindings/schemas/utils.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/column/README.md +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/column/_column.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/column/column_spec.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/column/field_spec.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/column/infer/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/column/infer/core.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/column/infer/jsonhints.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/column/infer/planning.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/column/infer/types.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/column/infer/utils.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/column/io_spec.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/column/mro_collect.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/column/shortcuts.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/config/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/config/constants.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/config/defaults.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/config/resolver.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/core/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/core/crud/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/core/crud/bulk.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/core/crud/helpers/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/core/crud/helpers/db.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/core/crud/helpers/enum.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/core/crud/helpers/filters.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/core/crud/helpers/model.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/core/crud/helpers/normalize.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/core/crud/ops.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/decorators.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/deps/jinja.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/deps/pydantic.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/deps/sqlalchemy.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/docs/verbosity.md +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/engine/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/engine/_engine.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/engine/bind.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/engine/capabilities.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/engine/collect.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/engine/decorators.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/engine/docs/PLUGINS.md +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/engine/plugins.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/engine/registry.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/hook/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/hook/_hook.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/hook/decorators.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/hook/hook_spec.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/hook/mro_collect.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/hook/shortcuts.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/hook/types.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/op/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/op/_op.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/op/canonical.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/op/collect.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/op/decorators.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/op/model_registry.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/op/mro_collect.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/op/resolver.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/op/types.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/mixins/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/mixins/bootstrappable.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/mixins/bound.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/mixins/edges.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/mixins/fields.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/mixins/hierarchy.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/mixins/key_digest.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/mixins/lifecycle.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/mixins/locks.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/mixins/markers.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/mixins/operations.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/mixins/principals.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/mixins/upsertable.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/mixins/utils.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/tables/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/tables/_base.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/tables/audit.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/tables/client.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/tables/group.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/tables/org.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/tables/rbac.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/tables/status.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/tables/tenant.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/orm/tables/user.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/response/README.md +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/response/bind.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/response/decorators.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/response/resolver.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/response/types.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/rest/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/README.md +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/emit/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/emit/paired_post.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/emit/paired_pre.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/emit/readtime_alias.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/out/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/out/masking.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/refresh/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/refresh/demand.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/resolve/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/resolve/assemble.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/resolve/paired_gen.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/response/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/response/headers_from_payload.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/response/negotiate.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/response/negotiation.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/response/render.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/response/template.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/schema/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/schema/collect_in.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/schema/collect_out.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/storage/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/storage/to_stored.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/wire/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/wire/build_in.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/wire/build_out.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/atoms/wire/dump.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/context.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/events.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/executor/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/executor/guards.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/executor/helpers.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/kernel.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/labels.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/opview.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/ordering.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/runtime/trace.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/schema/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/schema/_schema.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/schema/builder/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/schema/builder/build_schema.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/schema/builder/cache.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/schema/builder/compat.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/schema/builder/extras.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/schema/builder/helpers.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/schema/builder/list_params.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/schema/builder/strip_parent_fields.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/schema/collect.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/schema/decorators.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/schema/get_schema.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/schema/schema_spec.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/schema/shortcuts.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/schema/types.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/schema/utils.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/session/README.md +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/session/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/session/abc.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/session/base.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/session/decorators.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/session/default.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/session/shortcuts.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/session/spec.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/shortcuts.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/specs.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/system/diagnostics/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/system/diagnostics/healthz.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/system/diagnostics/hookz.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/system/diagnostics/kernelz.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/system/diagnostics/methodz.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/system/diagnostics/router.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/system/diagnostics/utils.py +0 -0
- {tigrbl-0.3.4.dev2/tigrbl/deps → tigrbl-0.3.5.dev5/tigrbl/system/favicon/assets}/favicon.svg +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/system/uvicorn.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/table/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/table/_base.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/table/_table.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/table/mro_collect.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/table/shortcuts.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/table/table_spec.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/transport/jsonrpc/__init__.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/transport/jsonrpc/helpers.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/transport/jsonrpc/models.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/transport/rest/aggregator.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/types/allow_anon_provider.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/types/nested_path_provider.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/types/op.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/types/op_config_provider.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/types/op_verb_alias_provider.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/types/request_extras_provider.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/types/response_extras_provider.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/types/table_config_provider.py +0 -0
- {tigrbl-0.3.4.dev2 → tigrbl-0.3.5.dev5}/tigrbl/types/uuid.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tigrbl
|
|
3
|
-
Version: 0.3.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.3.5.dev5
|
|
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
|
|
7
|
-
Keywords: tigrbl,sdk,standards
|
|
8
|
-
Author:
|
|
9
|
-
Author-email:
|
|
7
|
+
Keywords: tigrbl,sdk,standards,asgi,rest,rpc
|
|
8
|
+
Author: Jacob Stewart
|
|
9
|
+
Author-email: jacob@swarmauri.com
|
|
10
10
|
Requires-Python: >=3.10,<3.13
|
|
11
11
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
@@ -21,16 +21,12 @@ Provides-Extra: templates
|
|
|
21
21
|
Provides-Extra: tests
|
|
22
22
|
Requires-Dist: aiosqlite (>=0.19.0)
|
|
23
23
|
Requires-Dist: asyncpg (>=0.30.0) ; extra == "postgres"
|
|
24
|
-
Requires-Dist: fastapi (>=0.100.0)
|
|
25
24
|
Requires-Dist: greenlet (>=3.2.3)
|
|
26
25
|
Requires-Dist: httpx (>=0.27.0)
|
|
27
26
|
Requires-Dist: jinja2 (>=3.1.4) ; extra == "templates"
|
|
28
27
|
Requires-Dist: psycopg2-binary (>=2.9.9) ; extra == "postgres"
|
|
29
28
|
Requires-Dist: pydantic (>=2.0.0)
|
|
30
29
|
Requires-Dist: sqlalchemy (>=2.0)
|
|
31
|
-
Requires-Dist: swarmauri_base
|
|
32
|
-
Requires-Dist: swarmauri_core
|
|
33
|
-
Requires-Dist: swarmauri_standard
|
|
34
30
|
Requires-Dist: tigrbl-tests ; extra == "tests"
|
|
35
31
|
Requires-Dist: uvicorn
|
|
36
32
|
Description-Content-Type: text/markdown
|
|
@@ -53,7 +49,7 @@ Description-Content-Type: text/markdown
|
|
|
53
49
|
---
|
|
54
50
|
|
|
55
51
|
# Tigrbl 🐅🐂
|
|
56
|
-
A high-leverage meta-framework that turns plain SQLAlchemy models into a fully-featured REST+RPC surface with near-zero boilerplate. 🚀
|
|
52
|
+
A high-leverage ASGI meta-framework that turns plain SQLAlchemy models into a fully-featured REST+RPC surface with near-zero boilerplate. 🚀
|
|
57
53
|
|
|
58
54
|
## Features ✨
|
|
59
55
|
|
|
@@ -61,7 +57,7 @@ A high-leverage meta-framework that turns plain SQLAlchemy models into a fully-f
|
|
|
61
57
|
- 🔌 Unified REST and RPC endpoints from a single definition
|
|
62
58
|
- 🪝 Hookable phase system for deep customization
|
|
63
59
|
- 🧩 Pluggable engine and provider abstractions
|
|
64
|
-
- 🚀 Built
|
|
60
|
+
- 🚀 Built as an ASGI-native framework with Pydantic-powered schema generation
|
|
65
61
|
|
|
66
62
|
## Terminology 📚
|
|
67
63
|
|
|
@@ -402,7 +398,232 @@ control headers, status codes, and optional template rendering. See
|
|
|
402
398
|
|
|
403
399
|
* SQLAlchemy for ORM integration.
|
|
404
400
|
* Pydantic for schema generation.
|
|
405
|
-
*
|
|
401
|
+
* ASGI-native routing and dependency injection.
|
|
402
|
+
|
|
403
|
+
## Best Design Practices ✅
|
|
404
|
+
|
|
405
|
+
The following practices are the canonical, production-ready patterns for
|
|
406
|
+
building on Tigrbl. Each rule is explained and demonstrated with
|
|
407
|
+
approved usage. These are not optional—adhering to them keeps the runtime
|
|
408
|
+
predictable, preserves hook lifecycle guarantees, and ensures schema
|
|
409
|
+
consistency across REST and RPC surfaces.
|
|
410
|
+
|
|
411
|
+
### 1) Never import SQLAlchemy directly or bypass Tigrbl APIs
|
|
412
|
+
|
|
413
|
+
**Why:** Direct imports bypass Tigrbl's compatibility layer and make it
|
|
414
|
+
harder to evolve internal dependencies. Use the Tigrbl exports so your
|
|
415
|
+
code stays aligned with the framework’s versioned ASGI API.
|
|
416
|
+
|
|
417
|
+
✅ **Preferred:**
|
|
418
|
+
```python
|
|
419
|
+
from tigrbl import Base, TigrblApp, TigrblApi
|
|
420
|
+
from tigrbl.types import Integer, String, Mapped
|
|
421
|
+
from tigrbl.types import Depends, HTTPException, Request
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
🚫 **Avoid:**
|
|
425
|
+
```python
|
|
426
|
+
from sqlalchemy import Integer, String
|
|
427
|
+
from some_framework import Depends
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### 2) Do not coerce UUIDs manually
|
|
431
|
+
|
|
432
|
+
**Why:** Tigrbl schemas and types already normalize UUIDs. Manual coercion
|
|
433
|
+
creates inconsistent behavior across engines and breaks schema-level
|
|
434
|
+
validation.
|
|
435
|
+
|
|
436
|
+
✅ **Preferred:**
|
|
437
|
+
```python
|
|
438
|
+
from tigrbl.types import PgUUID, uuid4, Mapped
|
|
439
|
+
|
|
440
|
+
class Item(Table):
|
|
441
|
+
__tablename__ = "items"
|
|
442
|
+
id: Mapped[PgUUID] = acol(primary_key=True, default=uuid4)
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
🚫 **Avoid:**
|
|
446
|
+
```python
|
|
447
|
+
from uuid import UUID
|
|
448
|
+
|
|
449
|
+
item_id = UUID(str(payload["id"]))
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### 3) Use engine specs for persistence, not ad-hoc engines
|
|
453
|
+
|
|
454
|
+
**Why:** Engine specs make persistence declarative, testable, and
|
|
455
|
+
compatible with engine resolution across app, API, table, and op scopes.
|
|
456
|
+
|
|
457
|
+
✅ **Preferred:**
|
|
458
|
+
```python
|
|
459
|
+
from tigrbl.engine.shortcuts import engine_spec
|
|
460
|
+
from tigrbl.engine.decorators import engine_ctx
|
|
461
|
+
|
|
462
|
+
spec = engine_spec(kind="postgres", async_=True, host="db", name="app_db")
|
|
463
|
+
|
|
464
|
+
@engine_ctx(spec)
|
|
465
|
+
class App:
|
|
466
|
+
...
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
🚫 **Avoid:**
|
|
470
|
+
```python
|
|
471
|
+
from sqlalchemy.ext.asyncio import create_async_engine
|
|
472
|
+
|
|
473
|
+
engine = create_async_engine("postgresql+asyncpg://...")
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### 4) Never call DB session methods directly
|
|
477
|
+
|
|
478
|
+
**Why:** Direct calls bypass the hook lifecycle and the database guards.
|
|
479
|
+
Use model handlers or `app.<Model>.handlers.<op>` so hooks, policies, and
|
|
480
|
+
schema enforcement run consistently.
|
|
481
|
+
|
|
482
|
+
✅ **Preferred:**
|
|
483
|
+
```python
|
|
484
|
+
result = await Item.handlers.create(payload, ctx=request_ctx)
|
|
485
|
+
# or from a Tigrbl app instance:
|
|
486
|
+
result = await app.Item.handlers.create(payload, ctx=request_ctx)
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
🚫 **Avoid:**
|
|
490
|
+
```python
|
|
491
|
+
db.add(item)
|
|
492
|
+
await db.execute(statement)
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### 5) Always use encapsulated payloads as inputs and outputs
|
|
496
|
+
|
|
497
|
+
**Why:** Tigrbl expects request/response envelopes to preserve metadata,
|
|
498
|
+
support policy enforcement, and keep REST/RPC in lockstep.
|
|
499
|
+
|
|
500
|
+
✅ **Preferred:**
|
|
501
|
+
```python
|
|
502
|
+
from tigrbl import get_schema
|
|
503
|
+
|
|
504
|
+
CreateIn = get_schema(Item, "create", "in")
|
|
505
|
+
CreateOut = get_schema(Item, "create", "out")
|
|
506
|
+
|
|
507
|
+
payload = CreateIn(name="Widget")
|
|
508
|
+
result = await Item.handlers.create(payload, ctx=request_ctx)
|
|
509
|
+
response = CreateOut(result=result)
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
🚫 **Avoid:**
|
|
513
|
+
```python
|
|
514
|
+
payload = {"name": "Widget"}
|
|
515
|
+
result = await Item.handlers.create(payload)
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### 6) Encapsulation must use `get_schema(...)`
|
|
519
|
+
|
|
520
|
+
**Why:** `get_schema` guarantees the envelope is aligned to the configured
|
|
521
|
+
schema and respects schema overrides, request extras, and response extras.
|
|
522
|
+
|
|
523
|
+
✅ **Preferred:**
|
|
524
|
+
```python
|
|
525
|
+
ListIn = get_schema(Item, "list", "in")
|
|
526
|
+
ListOut = get_schema(Item, "list", "out")
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
🚫 **Avoid:**
|
|
530
|
+
```python
|
|
531
|
+
from pydantic import BaseModel
|
|
532
|
+
|
|
533
|
+
class ListIn(BaseModel):
|
|
534
|
+
payload: dict
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### 7) `Table` must be the first inherited class for all models
|
|
538
|
+
|
|
539
|
+
**Why:** Tigrbl inspects base classes for lifecycle and configuration.
|
|
540
|
+
Putting `Table` first preserves deterministic MRO behavior.
|
|
541
|
+
|
|
542
|
+
✅ **Preferred:**
|
|
543
|
+
```python
|
|
544
|
+
from tigrbl.orm.tables import Table
|
|
545
|
+
from tigrbl.orm.mixins import Timestamped
|
|
546
|
+
|
|
547
|
+
class Item(Table, Timestamped):
|
|
548
|
+
__tablename__ = "items"
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
🚫 **Avoid:**
|
|
552
|
+
```python
|
|
553
|
+
class Item(Timestamped, Table):
|
|
554
|
+
__tablename__ = "items"
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### 8) Never call `db.flush()` or `db.commit()`
|
|
558
|
+
|
|
559
|
+
**Why:** The hook lifecycle owns transactional boundaries. Manual flush or
|
|
560
|
+
commit short-circuits phase guards and can corrupt the request lifecycle.
|
|
561
|
+
|
|
562
|
+
✅ **Preferred:**
|
|
563
|
+
```python
|
|
564
|
+
@hook_ctx(ops="create", phase="HANDLER")
|
|
565
|
+
async def handler(ctx):
|
|
566
|
+
await Item.handlers.create(ctx["request"].payload, ctx=ctx)
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
🚫 **Avoid:**
|
|
570
|
+
```python
|
|
571
|
+
db.flush()
|
|
572
|
+
db.commit()
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### 9) Use ops for new REST/RPC methods—never add ad-hoc framework routes
|
|
576
|
+
|
|
577
|
+
**Why:** Ops keep routing, schemas, hooks, and policies unified. Custom
|
|
578
|
+
custom framework routes bypass these guarantees.
|
|
579
|
+
|
|
580
|
+
✅ **Preferred:**
|
|
581
|
+
```python
|
|
582
|
+
from tigrbl import op_ctx
|
|
583
|
+
|
|
584
|
+
@op_ctx(name="rotate_keys", method="POST", path="/keys/rotate")
|
|
585
|
+
async def rotate_keys(payload, *, ctx):
|
|
586
|
+
return await Key.handlers.rotate(payload, ctx=ctx)
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
🚫 **Avoid:**
|
|
590
|
+
```python
|
|
591
|
+
from some_framework import APIRouter
|
|
592
|
+
|
|
593
|
+
router = APIRouter()
|
|
594
|
+
|
|
595
|
+
@router.post("/keys/rotate")
|
|
596
|
+
async def rotate_keys(payload):
|
|
597
|
+
...
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### 10) Use context decorators where appropriate
|
|
601
|
+
|
|
602
|
+
**Why:** Context decorators (`engine_ctx`, `schema_ctx`, `op_ctx`,
|
|
603
|
+
`hook_ctx`) provide explicit, declarative binding of behavior and are
|
|
604
|
+
resolved deterministically by the runtime.
|
|
605
|
+
|
|
606
|
+
✅ **Preferred:**
|
|
607
|
+
```python
|
|
608
|
+
from tigrbl import hook_ctx, op_ctx, schema_ctx
|
|
609
|
+
from tigrbl.engine.decorators import engine_ctx
|
|
610
|
+
|
|
611
|
+
@engine_ctx(kind="sqlite", mode="memory")
|
|
612
|
+
class Item(Table):
|
|
613
|
+
__tablename__ = "items"
|
|
614
|
+
|
|
615
|
+
@schema_ctx(ops="create", cfg={"exclude": {"id"}})
|
|
616
|
+
class ItemCreateSchema:
|
|
617
|
+
model = Item
|
|
618
|
+
|
|
619
|
+
@op_ctx(name="export", method="GET", path="/items/export")
|
|
620
|
+
async def export_items(payload, *, ctx):
|
|
621
|
+
return await Item.handlers.list(payload, ctx=ctx)
|
|
622
|
+
|
|
623
|
+
@hook_ctx(ops="create", phase="PRE_HANDLER")
|
|
624
|
+
async def validate(ctx):
|
|
625
|
+
...
|
|
626
|
+
```
|
|
406
627
|
|
|
407
628
|
### Engine & Provider examples 🛠️
|
|
408
629
|
|
|
@@ -512,5 +733,3 @@ async def decorated_create(payload, *, db=None):
|
|
|
512
733
|
9. Core
|
|
513
734
|
10. Core\_Raw
|
|
514
735
|
|
|
515
|
-
|
|
516
|
-
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
# Tigrbl 🐅🐂
|
|
19
|
-
A high-leverage meta-framework that turns plain SQLAlchemy models into a fully-featured REST+RPC surface with near-zero boilerplate. 🚀
|
|
19
|
+
A high-leverage ASGI meta-framework that turns plain SQLAlchemy models into a fully-featured REST+RPC surface with near-zero boilerplate. 🚀
|
|
20
20
|
|
|
21
21
|
## Features ✨
|
|
22
22
|
|
|
@@ -24,7 +24,7 @@ A high-leverage meta-framework that turns plain SQLAlchemy models into a fully-f
|
|
|
24
24
|
- 🔌 Unified REST and RPC endpoints from a single definition
|
|
25
25
|
- 🪝 Hookable phase system for deep customization
|
|
26
26
|
- 🧩 Pluggable engine and provider abstractions
|
|
27
|
-
- 🚀 Built
|
|
27
|
+
- 🚀 Built as an ASGI-native framework with Pydantic-powered schema generation
|
|
28
28
|
|
|
29
29
|
## Terminology 📚
|
|
30
30
|
|
|
@@ -365,7 +365,232 @@ control headers, status codes, and optional template rendering. See
|
|
|
365
365
|
|
|
366
366
|
* SQLAlchemy for ORM integration.
|
|
367
367
|
* Pydantic for schema generation.
|
|
368
|
-
*
|
|
368
|
+
* ASGI-native routing and dependency injection.
|
|
369
|
+
|
|
370
|
+
## Best Design Practices ✅
|
|
371
|
+
|
|
372
|
+
The following practices are the canonical, production-ready patterns for
|
|
373
|
+
building on Tigrbl. Each rule is explained and demonstrated with
|
|
374
|
+
approved usage. These are not optional—adhering to them keeps the runtime
|
|
375
|
+
predictable, preserves hook lifecycle guarantees, and ensures schema
|
|
376
|
+
consistency across REST and RPC surfaces.
|
|
377
|
+
|
|
378
|
+
### 1) Never import SQLAlchemy directly or bypass Tigrbl APIs
|
|
379
|
+
|
|
380
|
+
**Why:** Direct imports bypass Tigrbl's compatibility layer and make it
|
|
381
|
+
harder to evolve internal dependencies. Use the Tigrbl exports so your
|
|
382
|
+
code stays aligned with the framework’s versioned ASGI API.
|
|
383
|
+
|
|
384
|
+
✅ **Preferred:**
|
|
385
|
+
```python
|
|
386
|
+
from tigrbl import Base, TigrblApp, TigrblApi
|
|
387
|
+
from tigrbl.types import Integer, String, Mapped
|
|
388
|
+
from tigrbl.types import Depends, HTTPException, Request
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
🚫 **Avoid:**
|
|
392
|
+
```python
|
|
393
|
+
from sqlalchemy import Integer, String
|
|
394
|
+
from some_framework import Depends
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### 2) Do not coerce UUIDs manually
|
|
398
|
+
|
|
399
|
+
**Why:** Tigrbl schemas and types already normalize UUIDs. Manual coercion
|
|
400
|
+
creates inconsistent behavior across engines and breaks schema-level
|
|
401
|
+
validation.
|
|
402
|
+
|
|
403
|
+
✅ **Preferred:**
|
|
404
|
+
```python
|
|
405
|
+
from tigrbl.types import PgUUID, uuid4, Mapped
|
|
406
|
+
|
|
407
|
+
class Item(Table):
|
|
408
|
+
__tablename__ = "items"
|
|
409
|
+
id: Mapped[PgUUID] = acol(primary_key=True, default=uuid4)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
🚫 **Avoid:**
|
|
413
|
+
```python
|
|
414
|
+
from uuid import UUID
|
|
415
|
+
|
|
416
|
+
item_id = UUID(str(payload["id"]))
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### 3) Use engine specs for persistence, not ad-hoc engines
|
|
420
|
+
|
|
421
|
+
**Why:** Engine specs make persistence declarative, testable, and
|
|
422
|
+
compatible with engine resolution across app, API, table, and op scopes.
|
|
423
|
+
|
|
424
|
+
✅ **Preferred:**
|
|
425
|
+
```python
|
|
426
|
+
from tigrbl.engine.shortcuts import engine_spec
|
|
427
|
+
from tigrbl.engine.decorators import engine_ctx
|
|
428
|
+
|
|
429
|
+
spec = engine_spec(kind="postgres", async_=True, host="db", name="app_db")
|
|
430
|
+
|
|
431
|
+
@engine_ctx(spec)
|
|
432
|
+
class App:
|
|
433
|
+
...
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
🚫 **Avoid:**
|
|
437
|
+
```python
|
|
438
|
+
from sqlalchemy.ext.asyncio import create_async_engine
|
|
439
|
+
|
|
440
|
+
engine = create_async_engine("postgresql+asyncpg://...")
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### 4) Never call DB session methods directly
|
|
444
|
+
|
|
445
|
+
**Why:** Direct calls bypass the hook lifecycle and the database guards.
|
|
446
|
+
Use model handlers or `app.<Model>.handlers.<op>` so hooks, policies, and
|
|
447
|
+
schema enforcement run consistently.
|
|
448
|
+
|
|
449
|
+
✅ **Preferred:**
|
|
450
|
+
```python
|
|
451
|
+
result = await Item.handlers.create(payload, ctx=request_ctx)
|
|
452
|
+
# or from a Tigrbl app instance:
|
|
453
|
+
result = await app.Item.handlers.create(payload, ctx=request_ctx)
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
🚫 **Avoid:**
|
|
457
|
+
```python
|
|
458
|
+
db.add(item)
|
|
459
|
+
await db.execute(statement)
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### 5) Always use encapsulated payloads as inputs and outputs
|
|
463
|
+
|
|
464
|
+
**Why:** Tigrbl expects request/response envelopes to preserve metadata,
|
|
465
|
+
support policy enforcement, and keep REST/RPC in lockstep.
|
|
466
|
+
|
|
467
|
+
✅ **Preferred:**
|
|
468
|
+
```python
|
|
469
|
+
from tigrbl import get_schema
|
|
470
|
+
|
|
471
|
+
CreateIn = get_schema(Item, "create", "in")
|
|
472
|
+
CreateOut = get_schema(Item, "create", "out")
|
|
473
|
+
|
|
474
|
+
payload = CreateIn(name="Widget")
|
|
475
|
+
result = await Item.handlers.create(payload, ctx=request_ctx)
|
|
476
|
+
response = CreateOut(result=result)
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
🚫 **Avoid:**
|
|
480
|
+
```python
|
|
481
|
+
payload = {"name": "Widget"}
|
|
482
|
+
result = await Item.handlers.create(payload)
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### 6) Encapsulation must use `get_schema(...)`
|
|
486
|
+
|
|
487
|
+
**Why:** `get_schema` guarantees the envelope is aligned to the configured
|
|
488
|
+
schema and respects schema overrides, request extras, and response extras.
|
|
489
|
+
|
|
490
|
+
✅ **Preferred:**
|
|
491
|
+
```python
|
|
492
|
+
ListIn = get_schema(Item, "list", "in")
|
|
493
|
+
ListOut = get_schema(Item, "list", "out")
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
🚫 **Avoid:**
|
|
497
|
+
```python
|
|
498
|
+
from pydantic import BaseModel
|
|
499
|
+
|
|
500
|
+
class ListIn(BaseModel):
|
|
501
|
+
payload: dict
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### 7) `Table` must be the first inherited class for all models
|
|
505
|
+
|
|
506
|
+
**Why:** Tigrbl inspects base classes for lifecycle and configuration.
|
|
507
|
+
Putting `Table` first preserves deterministic MRO behavior.
|
|
508
|
+
|
|
509
|
+
✅ **Preferred:**
|
|
510
|
+
```python
|
|
511
|
+
from tigrbl.orm.tables import Table
|
|
512
|
+
from tigrbl.orm.mixins import Timestamped
|
|
513
|
+
|
|
514
|
+
class Item(Table, Timestamped):
|
|
515
|
+
__tablename__ = "items"
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
🚫 **Avoid:**
|
|
519
|
+
```python
|
|
520
|
+
class Item(Timestamped, Table):
|
|
521
|
+
__tablename__ = "items"
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### 8) Never call `db.flush()` or `db.commit()`
|
|
525
|
+
|
|
526
|
+
**Why:** The hook lifecycle owns transactional boundaries. Manual flush or
|
|
527
|
+
commit short-circuits phase guards and can corrupt the request lifecycle.
|
|
528
|
+
|
|
529
|
+
✅ **Preferred:**
|
|
530
|
+
```python
|
|
531
|
+
@hook_ctx(ops="create", phase="HANDLER")
|
|
532
|
+
async def handler(ctx):
|
|
533
|
+
await Item.handlers.create(ctx["request"].payload, ctx=ctx)
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
🚫 **Avoid:**
|
|
537
|
+
```python
|
|
538
|
+
db.flush()
|
|
539
|
+
db.commit()
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
### 9) Use ops for new REST/RPC methods—never add ad-hoc framework routes
|
|
543
|
+
|
|
544
|
+
**Why:** Ops keep routing, schemas, hooks, and policies unified. Custom
|
|
545
|
+
custom framework routes bypass these guarantees.
|
|
546
|
+
|
|
547
|
+
✅ **Preferred:**
|
|
548
|
+
```python
|
|
549
|
+
from tigrbl import op_ctx
|
|
550
|
+
|
|
551
|
+
@op_ctx(name="rotate_keys", method="POST", path="/keys/rotate")
|
|
552
|
+
async def rotate_keys(payload, *, ctx):
|
|
553
|
+
return await Key.handlers.rotate(payload, ctx=ctx)
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
🚫 **Avoid:**
|
|
557
|
+
```python
|
|
558
|
+
from some_framework import APIRouter
|
|
559
|
+
|
|
560
|
+
router = APIRouter()
|
|
561
|
+
|
|
562
|
+
@router.post("/keys/rotate")
|
|
563
|
+
async def rotate_keys(payload):
|
|
564
|
+
...
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### 10) Use context decorators where appropriate
|
|
568
|
+
|
|
569
|
+
**Why:** Context decorators (`engine_ctx`, `schema_ctx`, `op_ctx`,
|
|
570
|
+
`hook_ctx`) provide explicit, declarative binding of behavior and are
|
|
571
|
+
resolved deterministically by the runtime.
|
|
572
|
+
|
|
573
|
+
✅ **Preferred:**
|
|
574
|
+
```python
|
|
575
|
+
from tigrbl import hook_ctx, op_ctx, schema_ctx
|
|
576
|
+
from tigrbl.engine.decorators import engine_ctx
|
|
577
|
+
|
|
578
|
+
@engine_ctx(kind="sqlite", mode="memory")
|
|
579
|
+
class Item(Table):
|
|
580
|
+
__tablename__ = "items"
|
|
581
|
+
|
|
582
|
+
@schema_ctx(ops="create", cfg={"exclude": {"id"}})
|
|
583
|
+
class ItemCreateSchema:
|
|
584
|
+
model = Item
|
|
585
|
+
|
|
586
|
+
@op_ctx(name="export", method="GET", path="/items/export")
|
|
587
|
+
async def export_items(payload, *, ctx):
|
|
588
|
+
return await Item.handlers.list(payload, ctx=ctx)
|
|
589
|
+
|
|
590
|
+
@hook_ctx(ops="create", phase="PRE_HANDLER")
|
|
591
|
+
async def validate(ctx):
|
|
592
|
+
...
|
|
593
|
+
```
|
|
369
594
|
|
|
370
595
|
### Engine & Provider examples 🛠️
|
|
371
596
|
|
|
@@ -474,5 +699,3 @@ async def decorated_create(payload, *, db=None):
|
|
|
474
699
|
8. Default Flush
|
|
475
700
|
9. Core
|
|
476
701
|
10. Core\_Raw
|
|
477
|
-
|
|
478
|
-
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tigrbl"
|
|
3
|
-
version = "0.3.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.3.5.dev5"
|
|
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"
|
|
7
7
|
repository = "http://github.com/swarmauri/swarmauri-sdk"
|
|
@@ -16,12 +16,8 @@ classifiers = [
|
|
|
16
16
|
"Programming Language :: Python :: 3",
|
|
17
17
|
"Programming Language :: Python :: 3 :: Only",
|
|
18
18
|
]
|
|
19
|
-
authors = [{ name = "
|
|
19
|
+
authors = [{ name = "Jacob Stewart", email = "jacob@swarmauri.com" }]
|
|
20
20
|
dependencies = [
|
|
21
|
-
"swarmauri_core",
|
|
22
|
-
"swarmauri_base",
|
|
23
|
-
"swarmauri_standard",
|
|
24
|
-
"fastapi>=0.100.0",
|
|
25
21
|
"pydantic>=2.0.0",
|
|
26
22
|
"sqlalchemy>=2.0",
|
|
27
23
|
"aiosqlite>=0.19.0",
|
|
@@ -33,6 +29,9 @@ keywords = [
|
|
|
33
29
|
'tigrbl',
|
|
34
30
|
'sdk',
|
|
35
31
|
'standards',
|
|
32
|
+
'asgi',
|
|
33
|
+
'rest',
|
|
34
|
+
'rpc',
|
|
36
35
|
]
|
|
37
36
|
|
|
38
37
|
[project.optional-dependencies]
|
|
@@ -48,9 +47,6 @@ tests = [
|
|
|
48
47
|
]
|
|
49
48
|
|
|
50
49
|
[tool.uv.sources]
|
|
51
|
-
swarmauri_core = { workspace = true }
|
|
52
|
-
swarmauri_base = { workspace = true }
|
|
53
|
-
swarmauri_standard = { workspace = true }
|
|
54
50
|
tigrbl-tests = { workspace = true }
|
|
55
51
|
|
|
56
52
|
[tool.pytest.ini_options]
|
|
@@ -9,7 +9,7 @@ Quick start:
|
|
|
9
9
|
from tigrbl import include_model, build_jsonrpc_router, mount_diagnostics
|
|
10
10
|
from tigrbl import OpSpec, hook_ctx, op_ctx, alias_ctx, schema_ctx, SchemaRef
|
|
11
11
|
|
|
12
|
-
include_model(api, User, app=
|
|
12
|
+
include_model(api, User, app=asgi_app)
|
|
13
13
|
app.include_router(build_jsonrpc_router(api), prefix="/rpc")
|
|
14
14
|
app.include_router(mount_diagnostics(api), prefix="/system")
|
|
15
15
|
|
|
@@ -80,12 +80,23 @@ from .api import Api, TigrblApi
|
|
|
80
80
|
|
|
81
81
|
from .table import Base
|
|
82
82
|
from .op import Op
|
|
83
|
-
from .
|
|
83
|
+
from .security import APIKey, HTTPBearer, MutualTLS, OAuth2, OpenIdConnect
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
__all__: list[str] = []
|
|
87
87
|
|
|
88
|
-
__all__ += [
|
|
88
|
+
__all__ += [
|
|
89
|
+
"TigrblApp",
|
|
90
|
+
"TigrblApi",
|
|
91
|
+
"Api",
|
|
92
|
+
"Base",
|
|
93
|
+
"Op",
|
|
94
|
+
"HTTPBearer",
|
|
95
|
+
"APIKey",
|
|
96
|
+
"OAuth2",
|
|
97
|
+
"OpenIdConnect",
|
|
98
|
+
"MutualTLS",
|
|
99
|
+
]
|
|
89
100
|
|
|
90
101
|
__all__ += [
|
|
91
102
|
# OpSpec core
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Core API surfaces for the Tigrbl package."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
__all__ = ["Api", "TigrblApi"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def __getattr__(name: str) -> Any:
|
|
11
|
+
if name == "Api":
|
|
12
|
+
from ._api import Api
|
|
13
|
+
|
|
14
|
+
return Api
|
|
15
|
+
if name == "TigrblApi":
|
|
16
|
+
from .tigrbl_api import TigrblApi
|
|
17
|
+
|
|
18
|
+
return TigrblApi
|
|
19
|
+
raise AttributeError(name)
|