tigrbl 0.0.1.dev1__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tigrbl/README.md +94 -0
- tigrbl/__init__.py +139 -14
- tigrbl/api/__init__.py +6 -0
- tigrbl/api/_api.py +97 -0
- tigrbl/api/api_spec.py +30 -0
- tigrbl/api/mro_collect.py +43 -0
- tigrbl/api/shortcuts.py +56 -0
- tigrbl/api/tigrbl_api.py +291 -0
- tigrbl/app/__init__.py +0 -0
- tigrbl/app/_app.py +86 -0
- tigrbl/app/_model_registry.py +41 -0
- tigrbl/app/app_spec.py +42 -0
- tigrbl/app/mro_collect.py +67 -0
- tigrbl/app/shortcuts.py +65 -0
- tigrbl/app/tigrbl_app.py +319 -0
- tigrbl/bindings/__init__.py +73 -0
- tigrbl/bindings/api/__init__.py +12 -0
- tigrbl/bindings/api/common.py +109 -0
- tigrbl/bindings/api/include.py +256 -0
- tigrbl/bindings/api/resource_proxy.py +149 -0
- tigrbl/bindings/api/rpc.py +111 -0
- tigrbl/bindings/columns.py +49 -0
- tigrbl/bindings/handlers/__init__.py +11 -0
- tigrbl/bindings/handlers/builder.py +119 -0
- tigrbl/bindings/handlers/ctx.py +74 -0
- tigrbl/bindings/handlers/identifiers.py +228 -0
- tigrbl/bindings/handlers/namespaces.py +51 -0
- tigrbl/bindings/handlers/steps.py +276 -0
- tigrbl/bindings/hooks.py +311 -0
- tigrbl/bindings/model.py +194 -0
- tigrbl/bindings/model_helpers.py +139 -0
- tigrbl/bindings/model_registry.py +77 -0
- tigrbl/bindings/rest/__init__.py +7 -0
- tigrbl/bindings/rest/attach.py +34 -0
- tigrbl/bindings/rest/collection.py +286 -0
- tigrbl/bindings/rest/common.py +120 -0
- tigrbl/bindings/rest/fastapi.py +76 -0
- tigrbl/bindings/rest/helpers.py +119 -0
- tigrbl/bindings/rest/io.py +317 -0
- tigrbl/bindings/rest/io_headers.py +49 -0
- tigrbl/bindings/rest/member.py +386 -0
- tigrbl/bindings/rest/router.py +296 -0
- tigrbl/bindings/rest/routing.py +153 -0
- tigrbl/bindings/rpc.py +364 -0
- tigrbl/bindings/schemas/__init__.py +11 -0
- tigrbl/bindings/schemas/builder.py +348 -0
- tigrbl/bindings/schemas/defaults.py +260 -0
- tigrbl/bindings/schemas/utils.py +193 -0
- tigrbl/column/README.md +62 -0
- tigrbl/column/__init__.py +72 -0
- tigrbl/column/_column.py +96 -0
- tigrbl/column/column_spec.py +40 -0
- tigrbl/column/field_spec.py +31 -0
- tigrbl/column/infer/__init__.py +25 -0
- tigrbl/column/infer/core.py +92 -0
- tigrbl/column/infer/jsonhints.py +44 -0
- tigrbl/column/infer/planning.py +133 -0
- tigrbl/column/infer/types.py +102 -0
- tigrbl/column/infer/utils.py +59 -0
- tigrbl/column/io_spec.py +136 -0
- tigrbl/column/mro_collect.py +59 -0
- tigrbl/column/shortcuts.py +89 -0
- tigrbl/column/storage_spec.py +65 -0
- tigrbl/config/__init__.py +19 -0
- tigrbl/config/constants.py +224 -0
- tigrbl/config/defaults.py +29 -0
- tigrbl/config/resolver.py +295 -0
- tigrbl/core/__init__.py +47 -0
- tigrbl/core/crud/__init__.py +36 -0
- tigrbl/core/crud/bulk.py +168 -0
- tigrbl/core/crud/helpers/__init__.py +76 -0
- tigrbl/core/crud/helpers/db.py +92 -0
- tigrbl/core/crud/helpers/enum.py +86 -0
- tigrbl/core/crud/helpers/filters.py +162 -0
- tigrbl/core/crud/helpers/model.py +123 -0
- tigrbl/core/crud/helpers/normalize.py +99 -0
- tigrbl/core/crud/ops.py +235 -0
- tigrbl/ddl/__init__.py +344 -0
- tigrbl/decorators.py +17 -0
- tigrbl/deps/__init__.py +20 -0
- tigrbl/deps/fastapi.py +45 -0
- tigrbl/deps/favicon.svg +4 -0
- tigrbl/deps/jinja.py +27 -0
- tigrbl/deps/pydantic.py +10 -0
- tigrbl/deps/sqlalchemy.py +94 -0
- tigrbl/deps/starlette.py +36 -0
- tigrbl/engine/__init__.py +45 -0
- tigrbl/engine/_engine.py +144 -0
- tigrbl/engine/bind.py +33 -0
- tigrbl/engine/builders.py +236 -0
- tigrbl/engine/capabilities.py +29 -0
- tigrbl/engine/collect.py +111 -0
- tigrbl/engine/decorators.py +110 -0
- tigrbl/engine/docs/PLUGINS.md +49 -0
- tigrbl/engine/engine_spec.py +355 -0
- tigrbl/engine/plugins.py +52 -0
- tigrbl/engine/registry.py +36 -0
- tigrbl/engine/resolver.py +224 -0
- tigrbl/engine/shortcuts.py +216 -0
- tigrbl/hook/__init__.py +21 -0
- tigrbl/hook/_hook.py +22 -0
- tigrbl/hook/decorators.py +28 -0
- tigrbl/hook/hook_spec.py +24 -0
- tigrbl/hook/mro_collect.py +98 -0
- tigrbl/hook/shortcuts.py +44 -0
- tigrbl/hook/types.py +76 -0
- tigrbl/op/__init__.py +50 -0
- tigrbl/op/_op.py +31 -0
- tigrbl/op/canonical.py +31 -0
- tigrbl/op/collect.py +11 -0
- tigrbl/op/decorators.py +238 -0
- tigrbl/op/model_registry.py +301 -0
- tigrbl/op/mro_collect.py +99 -0
- tigrbl/op/resolver.py +216 -0
- tigrbl/op/types.py +136 -0
- tigrbl/orm/__init__.py +1 -0
- tigrbl/orm/mixins/_RowBound.py +83 -0
- tigrbl/orm/mixins/__init__.py +95 -0
- tigrbl/orm/mixins/bootstrappable.py +113 -0
- tigrbl/orm/mixins/bound.py +47 -0
- tigrbl/orm/mixins/edges.py +40 -0
- tigrbl/orm/mixins/fields.py +165 -0
- tigrbl/orm/mixins/hierarchy.py +54 -0
- tigrbl/orm/mixins/key_digest.py +44 -0
- tigrbl/orm/mixins/lifecycle.py +115 -0
- tigrbl/orm/mixins/locks.py +51 -0
- tigrbl/orm/mixins/markers.py +16 -0
- tigrbl/orm/mixins/operations.py +57 -0
- tigrbl/orm/mixins/ownable.py +337 -0
- tigrbl/orm/mixins/principals.py +98 -0
- tigrbl/orm/mixins/tenant_bound.py +301 -0
- tigrbl/orm/mixins/upsertable.py +118 -0
- tigrbl/orm/mixins/utils.py +49 -0
- tigrbl/orm/tables/__init__.py +72 -0
- tigrbl/orm/tables/_base.py +8 -0
- tigrbl/orm/tables/audit.py +56 -0
- tigrbl/orm/tables/client.py +25 -0
- tigrbl/orm/tables/group.py +29 -0
- tigrbl/orm/tables/org.py +30 -0
- tigrbl/orm/tables/rbac.py +76 -0
- tigrbl/orm/tables/status.py +106 -0
- tigrbl/orm/tables/tenant.py +22 -0
- tigrbl/orm/tables/user.py +39 -0
- tigrbl/response/README.md +34 -0
- tigrbl/response/__init__.py +33 -0
- tigrbl/response/bind.py +12 -0
- tigrbl/response/decorators.py +37 -0
- tigrbl/response/resolver.py +83 -0
- tigrbl/response/shortcuts.py +171 -0
- tigrbl/response/types.py +49 -0
- tigrbl/rest/__init__.py +27 -0
- tigrbl/runtime/README.md +129 -0
- tigrbl/runtime/__init__.py +20 -0
- tigrbl/runtime/atoms/__init__.py +102 -0
- tigrbl/runtime/atoms/emit/__init__.py +42 -0
- tigrbl/runtime/atoms/emit/paired_post.py +158 -0
- tigrbl/runtime/atoms/emit/paired_pre.py +106 -0
- tigrbl/runtime/atoms/emit/readtime_alias.py +120 -0
- tigrbl/runtime/atoms/out/__init__.py +38 -0
- tigrbl/runtime/atoms/out/masking.py +135 -0
- tigrbl/runtime/atoms/refresh/__init__.py +38 -0
- tigrbl/runtime/atoms/refresh/demand.py +130 -0
- tigrbl/runtime/atoms/resolve/__init__.py +40 -0
- tigrbl/runtime/atoms/resolve/assemble.py +167 -0
- tigrbl/runtime/atoms/resolve/paired_gen.py +147 -0
- tigrbl/runtime/atoms/response/__init__.py +19 -0
- tigrbl/runtime/atoms/response/headers_from_payload.py +57 -0
- tigrbl/runtime/atoms/response/negotiate.py +30 -0
- tigrbl/runtime/atoms/response/negotiation.py +43 -0
- tigrbl/runtime/atoms/response/render.py +36 -0
- tigrbl/runtime/atoms/response/renderer.py +116 -0
- tigrbl/runtime/atoms/response/template.py +44 -0
- tigrbl/runtime/atoms/response/templates.py +88 -0
- tigrbl/runtime/atoms/schema/__init__.py +40 -0
- tigrbl/runtime/atoms/schema/collect_in.py +21 -0
- tigrbl/runtime/atoms/schema/collect_out.py +21 -0
- tigrbl/runtime/atoms/storage/__init__.py +38 -0
- tigrbl/runtime/atoms/storage/to_stored.py +167 -0
- tigrbl/runtime/atoms/wire/__init__.py +45 -0
- tigrbl/runtime/atoms/wire/build_in.py +166 -0
- tigrbl/runtime/atoms/wire/build_out.py +87 -0
- tigrbl/runtime/atoms/wire/dump.py +206 -0
- tigrbl/runtime/atoms/wire/validate_in.py +227 -0
- tigrbl/runtime/context.py +206 -0
- tigrbl/runtime/errors/__init__.py +61 -0
- tigrbl/runtime/errors/converters.py +214 -0
- tigrbl/runtime/errors/exceptions.py +124 -0
- tigrbl/runtime/errors/mappings.py +71 -0
- tigrbl/runtime/errors/utils.py +150 -0
- tigrbl/runtime/events.py +209 -0
- tigrbl/runtime/executor/__init__.py +6 -0
- tigrbl/runtime/executor/guards.py +132 -0
- tigrbl/runtime/executor/helpers.py +88 -0
- tigrbl/runtime/executor/invoke.py +150 -0
- tigrbl/runtime/executor/types.py +84 -0
- tigrbl/runtime/kernel.py +644 -0
- tigrbl/runtime/labels.py +353 -0
- tigrbl/runtime/opview.py +89 -0
- tigrbl/runtime/ordering.py +256 -0
- tigrbl/runtime/system.py +279 -0
- tigrbl/runtime/trace.py +330 -0
- tigrbl/schema/__init__.py +38 -0
- tigrbl/schema/_schema.py +27 -0
- tigrbl/schema/builder/__init__.py +17 -0
- tigrbl/schema/builder/build_schema.py +209 -0
- tigrbl/schema/builder/cache.py +24 -0
- tigrbl/schema/builder/compat.py +16 -0
- tigrbl/schema/builder/extras.py +85 -0
- tigrbl/schema/builder/helpers.py +51 -0
- tigrbl/schema/builder/list_params.py +117 -0
- tigrbl/schema/builder/strip_parent_fields.py +70 -0
- tigrbl/schema/collect.py +79 -0
- tigrbl/schema/decorators.py +68 -0
- tigrbl/schema/get_schema.py +86 -0
- tigrbl/schema/schema_spec.py +20 -0
- tigrbl/schema/shortcuts.py +42 -0
- tigrbl/schema/types.py +34 -0
- tigrbl/schema/utils.py +143 -0
- tigrbl/session/README.md +14 -0
- tigrbl/session/__init__.py +28 -0
- tigrbl/session/abc.py +76 -0
- tigrbl/session/base.py +151 -0
- tigrbl/session/decorators.py +43 -0
- tigrbl/session/default.py +118 -0
- tigrbl/session/shortcuts.py +50 -0
- tigrbl/session/spec.py +112 -0
- tigrbl/shortcuts.py +22 -0
- tigrbl/specs.py +44 -0
- tigrbl/system/__init__.py +13 -0
- tigrbl/system/diagnostics/__init__.py +24 -0
- tigrbl/system/diagnostics/compat.py +31 -0
- tigrbl/system/diagnostics/healthz.py +41 -0
- tigrbl/system/diagnostics/hookz.py +51 -0
- tigrbl/system/diagnostics/kernelz.py +20 -0
- tigrbl/system/diagnostics/methodz.py +43 -0
- tigrbl/system/diagnostics/router.py +73 -0
- tigrbl/system/diagnostics/utils.py +43 -0
- tigrbl/system/uvicorn.py +60 -0
- tigrbl/table/__init__.py +9 -0
- tigrbl/table/_base.py +260 -0
- tigrbl/table/_table.py +54 -0
- tigrbl/table/mro_collect.py +69 -0
- tigrbl/table/shortcuts.py +57 -0
- tigrbl/table/table_spec.py +28 -0
- tigrbl/transport/__init__.py +74 -0
- tigrbl/transport/jsonrpc/__init__.py +19 -0
- tigrbl/transport/jsonrpc/dispatcher.py +352 -0
- tigrbl/transport/jsonrpc/helpers.py +115 -0
- tigrbl/transport/jsonrpc/models.py +41 -0
- tigrbl/transport/rest/__init__.py +25 -0
- tigrbl/transport/rest/aggregator.py +132 -0
- tigrbl/types/__init__.py +170 -0
- tigrbl/types/allow_anon_provider.py +19 -0
- tigrbl/types/authn_abc.py +30 -0
- tigrbl/types/nested_path_provider.py +22 -0
- tigrbl/types/op.py +35 -0
- tigrbl/types/op_config_provider.py +17 -0
- tigrbl/types/op_verb_alias_provider.py +33 -0
- tigrbl/types/request_extras_provider.py +22 -0
- tigrbl/types/response_extras_provider.py +22 -0
- tigrbl/types/table_config_provider.py +13 -0
- tigrbl/types/uuid.py +55 -0
- tigrbl-0.3.0.dist-info/METADATA +516 -0
- tigrbl-0.3.0.dist-info/RECORD +266 -0
- {tigrbl-0.0.1.dev1.dist-info → tigrbl-0.3.0.dist-info}/WHEEL +1 -1
- tigrbl-0.3.0.dist-info/licenses/LICENSE +201 -0
- tigrbl/ExampleAgent.py +0 -1
- tigrbl-0.0.1.dev1.dist-info/METADATA +0 -18
- tigrbl-0.0.1.dev1.dist-info/RECORD +0 -5
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# tigrbl/v3/schema/decorators.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Dict, Optional
|
|
7
|
+
|
|
8
|
+
from ..config.constants import TIGRBL_SCHEMA_DECLS_ATTR
|
|
9
|
+
|
|
10
|
+
logging.getLogger("uvicorn").setLevel(logging.DEBUG)
|
|
11
|
+
logger = logging.getLogger("uvicorn")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class _SchemaDecl:
|
|
16
|
+
alias: str # name under model.schemas.<alias>
|
|
17
|
+
kind: str # "in" | "out"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _register_schema_decl(
|
|
21
|
+
target_model: type, alias: str, kind: str, schema_cls: type
|
|
22
|
+
) -> None:
|
|
23
|
+
"""Store schema declarations on the model for later binding."""
|
|
24
|
+
logger.debug(
|
|
25
|
+
"Registering schema decl for %s alias=%s kind=%s",
|
|
26
|
+
target_model.__name__,
|
|
27
|
+
alias,
|
|
28
|
+
kind,
|
|
29
|
+
)
|
|
30
|
+
if kind not in ("in", "out"):
|
|
31
|
+
logger.debug("Invalid kind '%s' for schema decl", kind)
|
|
32
|
+
raise ValueError("schema_ctx(kind=...) must be 'in' or 'out'")
|
|
33
|
+
mapping: Dict[str, Dict[str, type]] = (
|
|
34
|
+
getattr(target_model, TIGRBL_SCHEMA_DECLS_ATTR, None) or {}
|
|
35
|
+
)
|
|
36
|
+
bucket = dict(mapping.get(alias, {}))
|
|
37
|
+
bucket[kind] = schema_cls
|
|
38
|
+
mapping[alias] = bucket
|
|
39
|
+
setattr(target_model, TIGRBL_SCHEMA_DECLS_ATTR, mapping)
|
|
40
|
+
logger.debug("Registered schema %s for alias '%s'", schema_cls, alias)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def schema_ctx(*, alias: str, kind: str = "out", for_: Optional[type] = None):
|
|
44
|
+
"""Register a named schema for a model."""
|
|
45
|
+
|
|
46
|
+
def deco(schema_cls: type):
|
|
47
|
+
if not isinstance(schema_cls, type):
|
|
48
|
+
logger.debug("schema_ctx applied to non-class %r", schema_cls)
|
|
49
|
+
raise TypeError("@schema_ctx must decorate a class")
|
|
50
|
+
if for_ is not None:
|
|
51
|
+
logger.debug(
|
|
52
|
+
"Registering schema %s for model %s via decorator",
|
|
53
|
+
schema_cls,
|
|
54
|
+
for_.__name__,
|
|
55
|
+
)
|
|
56
|
+
_register_schema_decl(for_, alias, kind, schema_cls)
|
|
57
|
+
setattr(
|
|
58
|
+
schema_cls, "__tigrbl_schema_decl__", _SchemaDecl(alias=alias, kind=kind)
|
|
59
|
+
)
|
|
60
|
+
logger.debug(
|
|
61
|
+
"Attached schema decl to %s: alias=%s kind=%s", schema_cls, alias, kind
|
|
62
|
+
)
|
|
63
|
+
return schema_cls
|
|
64
|
+
|
|
65
|
+
return deco
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
__all__ = ["schema_ctx"]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Literal, Optional, Type
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
logging.getLogger("uvicorn").setLevel(logging.DEBUG)
|
|
9
|
+
logger = logging.getLogger("uvicorn")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_schema(
|
|
13
|
+
orm_cls: type,
|
|
14
|
+
op: str,
|
|
15
|
+
*,
|
|
16
|
+
kind: Literal["in", "out"] = "out",
|
|
17
|
+
) -> Optional[Type[BaseModel]]:
|
|
18
|
+
"""Return the bound schema for ``orm_cls``.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
orm_cls:
|
|
23
|
+
ORM model that has been bound via :func:`tigrbl.bindings.build_schemas`.
|
|
24
|
+
op:
|
|
25
|
+
Operation alias whose schema should be returned.
|
|
26
|
+
kind:
|
|
27
|
+
Either ``"in"`` for request schemas or ``"out"`` for response schemas.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
Optional[Type[BaseModel]]
|
|
32
|
+
The requested schema class if available, otherwise ``None`` when the
|
|
33
|
+
operation uses raw payloads.
|
|
34
|
+
|
|
35
|
+
Raises
|
|
36
|
+
------
|
|
37
|
+
KeyError
|
|
38
|
+
If the model has not been bound or the operation/kind is unknown.
|
|
39
|
+
"""
|
|
40
|
+
logger.debug(
|
|
41
|
+
"Resolving schema for model=%s op=%s kind=%s", orm_cls.__name__, op, kind
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
ns = getattr(orm_cls, "schemas", None)
|
|
45
|
+
if ns is None:
|
|
46
|
+
logger.debug("Model %s has no schemas namespace", orm_cls.__name__)
|
|
47
|
+
raise KeyError(
|
|
48
|
+
f"{orm_cls.__name__} has no bound schemas; did you include the model?",
|
|
49
|
+
)
|
|
50
|
+
logger.debug("Found schemas namespace for model %s", orm_cls.__name__)
|
|
51
|
+
|
|
52
|
+
alias_ns = getattr(ns, op, None)
|
|
53
|
+
if alias_ns is None:
|
|
54
|
+
logger.debug("Unknown operation '%s' for model %s", op, orm_cls.__name__)
|
|
55
|
+
raise KeyError(f"Unknown op '{op}' for {orm_cls.__name__}")
|
|
56
|
+
logger.debug("Found operation '%s' for model %s", op, orm_cls.__name__)
|
|
57
|
+
|
|
58
|
+
kind = kind.lower()
|
|
59
|
+
if kind not in {"in", "out"}:
|
|
60
|
+
logger.debug("Invalid schema kind '%s' requested", kind)
|
|
61
|
+
raise ValueError("kind must be 'in' or 'out'")
|
|
62
|
+
logger.debug("Using schema kind '%s'", kind)
|
|
63
|
+
|
|
64
|
+
attr = "in_" if kind == "in" else "out"
|
|
65
|
+
if not hasattr(alias_ns, attr):
|
|
66
|
+
logger.debug(
|
|
67
|
+
"Schema kind '%s' not found for op '%s' on %s", kind, op, orm_cls.__name__
|
|
68
|
+
)
|
|
69
|
+
raise KeyError(
|
|
70
|
+
f"Schema kind '{kind}' not found for op '{op}' on {orm_cls.__name__}",
|
|
71
|
+
)
|
|
72
|
+
logger.debug(
|
|
73
|
+
"Found schema attribute '%s' for op '%s' on %s", attr, op, orm_cls.__name__
|
|
74
|
+
)
|
|
75
|
+
schema = getattr(alias_ns, attr)
|
|
76
|
+
logger.debug(
|
|
77
|
+
"Resolved schema %s for model %s op=%s kind=%s",
|
|
78
|
+
schema,
|
|
79
|
+
orm_cls.__name__,
|
|
80
|
+
op,
|
|
81
|
+
kind,
|
|
82
|
+
)
|
|
83
|
+
return schema
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
__all__ = ["get_schema"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# tigrbl/v3/schema/schema_spec.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from .types import SchemaArg, SchemaKind
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class SchemaSpec:
|
|
12
|
+
"""Declarative description of a schema for a model."""
|
|
13
|
+
|
|
14
|
+
alias: str
|
|
15
|
+
kind: SchemaKind = "out"
|
|
16
|
+
for_: Optional[type] = None
|
|
17
|
+
schema: SchemaArg | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = ["SchemaSpec"]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# tigrbl/v3/schema/shortcuts.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Optional, Type
|
|
5
|
+
|
|
6
|
+
try: # pragma: no cover
|
|
7
|
+
from pydantic import BaseModel # type: ignore
|
|
8
|
+
except Exception: # pragma: no cover
|
|
9
|
+
|
|
10
|
+
class BaseModel: # minimal stub for typing only
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
from ._schema import Schema
|
|
15
|
+
from .schema_spec import SchemaSpec
|
|
16
|
+
from .types import SchemaArg, SchemaKind
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def schema_spec(
|
|
20
|
+
alias: str,
|
|
21
|
+
*,
|
|
22
|
+
kind: SchemaKind = "out",
|
|
23
|
+
for_: Optional[type] = None,
|
|
24
|
+
schema: SchemaArg | None = None,
|
|
25
|
+
) -> SchemaSpec:
|
|
26
|
+
"""Factory for :class:`SchemaSpec`."""
|
|
27
|
+
|
|
28
|
+
return SchemaSpec(alias=alias, kind=kind, for_=for_, schema=schema)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def schema(
|
|
32
|
+
model: Type[BaseModel],
|
|
33
|
+
*,
|
|
34
|
+
kind: SchemaKind = "out",
|
|
35
|
+
alias: str | None = None,
|
|
36
|
+
) -> Schema:
|
|
37
|
+
"""Factory for :class:`Schema`."""
|
|
38
|
+
|
|
39
|
+
return Schema(model=model, kind=kind, alias=alias)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
__all__ = ["schema_spec", "schema"]
|
tigrbl/schema/types.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# tigrbl/v3/schema/types.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Callable, Type, Union, Literal
|
|
6
|
+
|
|
7
|
+
try: # pragma: no cover
|
|
8
|
+
from pydantic import BaseModel # type: ignore
|
|
9
|
+
except Exception: # pragma: no cover
|
|
10
|
+
|
|
11
|
+
class BaseModel: # minimal stub for typing only
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
SchemaKind = Literal["in", "out"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True, slots=True)
|
|
19
|
+
class SchemaRef:
|
|
20
|
+
"""Lazy reference to ``model.schemas.<alias>.(in_|out)``."""
|
|
21
|
+
|
|
22
|
+
alias: str
|
|
23
|
+
kind: SchemaKind = "in"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
SchemaArg = Union[
|
|
27
|
+
Type[BaseModel], # direct Pydantic model
|
|
28
|
+
SchemaRef, # cross-op reference
|
|
29
|
+
str, # "alias.in" | "alias.out"
|
|
30
|
+
Callable[[type], Type[BaseModel]], # lambda cls: cls.schemas.create.in_
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
__all__ = ["SchemaKind", "SchemaRef", "SchemaArg"]
|
tigrbl/schema/utils.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Dict, List, Type
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field, RootModel, create_model
|
|
7
|
+
|
|
8
|
+
logging.getLogger("uvicorn").setLevel(logging.DEBUG)
|
|
9
|
+
logger = logging.getLogger("uvicorn")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def namely_model(model: Type[BaseModel], *, name: str, doc: str) -> Type[BaseModel]:
|
|
13
|
+
"""Assign a unique name and docstring to a Pydantic model class."""
|
|
14
|
+
logger.debug("Renaming model %s to %s", model.__name__, name)
|
|
15
|
+
model.__name__ = name
|
|
16
|
+
model.__qualname__ = name
|
|
17
|
+
model.__doc__ = doc
|
|
18
|
+
|
|
19
|
+
# Rebuild the model so Pydantic updates internal references (e.g., for OpenAPI titles).
|
|
20
|
+
|
|
21
|
+
model.model_rebuild(force=True)
|
|
22
|
+
logger.debug("Model %s rebuilt", name)
|
|
23
|
+
return model
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _camel(s: str) -> str:
|
|
27
|
+
rv = "".join(p.capitalize() or "_" for p in s.split("_"))
|
|
28
|
+
logger.debug("Camel-cased '%s' → '%s'", s, rv)
|
|
29
|
+
return rv
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _extract_example(schema: Type[BaseModel]) -> Dict[str, Any]:
|
|
33
|
+
"""Build a simple example object from field examples if available."""
|
|
34
|
+
try:
|
|
35
|
+
js = schema.model_json_schema()
|
|
36
|
+
except Exception as exc:
|
|
37
|
+
logger.debug("Failed to build JSON schema for %s: %s", schema, exc)
|
|
38
|
+
return {}
|
|
39
|
+
out: Dict[str, Any] = {}
|
|
40
|
+
for name, prop in (js.get("properties") or {}).items():
|
|
41
|
+
examples = prop.get("examples")
|
|
42
|
+
if examples:
|
|
43
|
+
out[name] = examples[0]
|
|
44
|
+
logger.debug("Extracted example for %s: %s", schema, out)
|
|
45
|
+
return out
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _make_bulk_rows_model(
|
|
49
|
+
model: type, verb: str, item_schema: Type[BaseModel]
|
|
50
|
+
) -> Type[BaseModel]:
|
|
51
|
+
"""Build a root model representing ``List[item_schema]``."""
|
|
52
|
+
name = f"{model.__name__}{_camel(verb)}Request"
|
|
53
|
+
example = _extract_example(item_schema)
|
|
54
|
+
examples = [[example]] if example else []
|
|
55
|
+
|
|
56
|
+
class _BulkModel(RootModel[List[item_schema]]): # type: ignore[misc]
|
|
57
|
+
model_config = ConfigDict(json_schema_extra={"examples": examples})
|
|
58
|
+
|
|
59
|
+
logger.debug("Built bulk rows model %s with examples=%s", name, examples)
|
|
60
|
+
return namely_model(
|
|
61
|
+
_BulkModel,
|
|
62
|
+
name=name,
|
|
63
|
+
doc=f"{verb} request schema for {model.__name__}",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _make_bulk_rows_response_model(
|
|
68
|
+
model: type, verb: str, item_schema: Type[BaseModel]
|
|
69
|
+
) -> Type[BaseModel]:
|
|
70
|
+
"""Build a root model representing ``List[item_schema]`` for responses."""
|
|
71
|
+
name = f"{model.__name__}{_camel(verb)}Response"
|
|
72
|
+
example = _extract_example(item_schema)
|
|
73
|
+
examples = [[example]] if example else []
|
|
74
|
+
|
|
75
|
+
class _BulkModel(RootModel[List[item_schema]]): # type: ignore[misc]
|
|
76
|
+
model_config = ConfigDict(json_schema_extra={"examples": examples})
|
|
77
|
+
|
|
78
|
+
logger.debug("Built bulk rows response model %s with examples=%s", name, examples)
|
|
79
|
+
return namely_model(
|
|
80
|
+
_BulkModel,
|
|
81
|
+
name=name,
|
|
82
|
+
doc=f"{verb} response schema for {model.__name__}",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _make_bulk_ids_model(
|
|
87
|
+
model: type, verb: str, pk_type: type | Any
|
|
88
|
+
) -> Type[BaseModel]:
|
|
89
|
+
"""Build a wrapper schema with an ``ids: List[pk_type]`` field."""
|
|
90
|
+
name = f"{model.__name__}{_camel(verb)}Request"
|
|
91
|
+
schema = create_model( # type: ignore[call-arg]
|
|
92
|
+
name,
|
|
93
|
+
ids=(List[pk_type], Field(...)), # type: ignore[name-defined]
|
|
94
|
+
)
|
|
95
|
+
logger.debug("Built bulk ids model %s", name)
|
|
96
|
+
return namely_model(
|
|
97
|
+
schema,
|
|
98
|
+
name=name,
|
|
99
|
+
doc=f"{verb} request schema for {model.__name__}",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _make_deleted_response_model(model: type, verb: str) -> Type[BaseModel]:
|
|
104
|
+
"""Build a response schema with a ``deleted`` count."""
|
|
105
|
+
name = f"{model.__name__}{_camel(verb)}Response"
|
|
106
|
+
schema = create_model( # type: ignore[call-arg]
|
|
107
|
+
name,
|
|
108
|
+
deleted=(int, Field(..., examples=[0])),
|
|
109
|
+
__config__=ConfigDict(json_schema_extra={"examples": [{"deleted": 0}]}),
|
|
110
|
+
)
|
|
111
|
+
logger.debug("Built deleted response model %s", name)
|
|
112
|
+
return namely_model(
|
|
113
|
+
schema,
|
|
114
|
+
name=name,
|
|
115
|
+
doc=f"{verb} response schema for {model.__name__}",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _make_pk_model(
|
|
120
|
+
model: type, verb: str, pk_name: str, pk_type: type | Any
|
|
121
|
+
) -> Type[BaseModel]:
|
|
122
|
+
"""Build a wrapper schema with a single primary-key field."""
|
|
123
|
+
name = f"{model.__name__}{_camel(verb)}Request"
|
|
124
|
+
schema = create_model( # type: ignore[call-arg]
|
|
125
|
+
name,
|
|
126
|
+
**{pk_name: (pk_type, Field(...))}, # type: ignore[name-defined]
|
|
127
|
+
)
|
|
128
|
+
logger.debug("Built pk model %s for field %s", name, pk_name)
|
|
129
|
+
return namely_model(
|
|
130
|
+
schema,
|
|
131
|
+
name=name,
|
|
132
|
+
doc=f"{verb} request schema for {model.__name__}",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
__all__ = [
|
|
137
|
+
"namely_model",
|
|
138
|
+
"_make_bulk_rows_model",
|
|
139
|
+
"_make_bulk_rows_response_model",
|
|
140
|
+
"_make_bulk_ids_model",
|
|
141
|
+
"_make_deleted_response_model",
|
|
142
|
+
"_make_pk_model",
|
|
143
|
+
]
|
tigrbl/session/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
# tigrbl.session
|
|
3
|
+
|
|
4
|
+
`tigrbl.session` provides the transaction-aware session contract and helpers for Tigrbl:
|
|
5
|
+
|
|
6
|
+
- `SessionABC`: required interface (native transactions).
|
|
7
|
+
- `SessionSpec`: per-session policy (isolation, read-only, timeouts, retries, etc.).
|
|
8
|
+
- `TigrblSessionBase`: abstract base with guardrails (read-only enforcement, queued add()).
|
|
9
|
+
- `DefaultSession`: delegating wrapper for native driver sessions.
|
|
10
|
+
- `session_ctx` / `read_only_session`: decorators to attach policy at app/api/model/op scopes.
|
|
11
|
+
- `session_spec` / `tx_*` / `readonly`: shortcuts to build policy objects.
|
|
12
|
+
- `wrap_sessionmaker`: helper to adapt provider session factories to Tigrbl sessions.
|
|
13
|
+
|
|
14
|
+
This module is backend-agnostic and does not import any database libraries.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from .abc import SessionABC
|
|
2
|
+
from .spec import SessionSpec
|
|
3
|
+
from .base import TigrblSessionBase
|
|
4
|
+
from .default import DefaultSession
|
|
5
|
+
from .decorators import session_ctx, read_only_session
|
|
6
|
+
from .shortcuts import (
|
|
7
|
+
session_spec,
|
|
8
|
+
tx_read_committed,
|
|
9
|
+
tx_repeatable_read,
|
|
10
|
+
tx_serializable,
|
|
11
|
+
readonly,
|
|
12
|
+
wrap_sessionmaker,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"SessionABC",
|
|
17
|
+
"SessionSpec",
|
|
18
|
+
"TigrblSessionBase",
|
|
19
|
+
"DefaultSession",
|
|
20
|
+
"session_ctx",
|
|
21
|
+
"read_only_session",
|
|
22
|
+
"session_spec",
|
|
23
|
+
"tx_read_committed",
|
|
24
|
+
"tx_repeatable_read",
|
|
25
|
+
"tx_serializable",
|
|
26
|
+
"readonly",
|
|
27
|
+
"wrap_sessionmaker",
|
|
28
|
+
]
|
tigrbl/session/abc.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SessionABC(ABC):
|
|
8
|
+
"""
|
|
9
|
+
Authoritative Tigrbl session interface.
|
|
10
|
+
|
|
11
|
+
All concrete sessions MUST be natively transactional and implement the
|
|
12
|
+
methods below. This ABC is intentionally minimal and backend-agnostic.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# ---- Transactions ----
|
|
16
|
+
@abstractmethod
|
|
17
|
+
async def begin(self) -> None:
|
|
18
|
+
"""Open a native transaction for this session."""
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
async def commit(self) -> None:
|
|
22
|
+
"""Commit the current transaction."""
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
async def rollback(self) -> None:
|
|
26
|
+
"""Rollback the current transaction."""
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def in_transaction(self) -> bool:
|
|
30
|
+
"""Return True iff a transaction is currently open."""
|
|
31
|
+
|
|
32
|
+
# ---- CRUD surface ----
|
|
33
|
+
@abstractmethod
|
|
34
|
+
async def get(self, model: type, ident: Any) -> Any | None:
|
|
35
|
+
"""Fetch one instance by primary key (model, ident)."""
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def add(self, obj: Any) -> None:
|
|
39
|
+
"""Stage a new/dirty object for persistence."""
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
async def delete(self, obj: Any) -> None:
|
|
43
|
+
"""Stage an object for deletion."""
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
async def flush(self) -> None:
|
|
47
|
+
"""Flush staged changes to the underlying store (still in TX)."""
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
async def refresh(self, obj: Any) -> None:
|
|
51
|
+
"""Refresh the object from the store (respecting the current TX view)."""
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
async def execute(self, stmt: Any) -> Any:
|
|
55
|
+
"""
|
|
56
|
+
Execute a backend-native statement.
|
|
57
|
+
|
|
58
|
+
The result (if any) SHOULD provide a minimal facade compatible with:
|
|
59
|
+
- .scalars().all()
|
|
60
|
+
- .scalar_one()
|
|
61
|
+
to ease integration with higher-level helpers.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
# ---- Lifecycle / async marker ----
|
|
65
|
+
@abstractmethod
|
|
66
|
+
async def close(self) -> None:
|
|
67
|
+
"""Release underlying resources (connections, cursors, etc.)."""
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
async def run_sync(self, fn: Callable[[Any], Any]) -> Any:
|
|
71
|
+
"""
|
|
72
|
+
Execute a callback against the underlying native handle.
|
|
73
|
+
|
|
74
|
+
Presence of this method also acts as the "async session" marker for code
|
|
75
|
+
paths that need to distinguish sync-vs-async sessions.
|
|
76
|
+
"""
|
tigrbl/session/base.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import inspect
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, Callable, List, Optional
|
|
7
|
+
|
|
8
|
+
from .abc import SessionABC
|
|
9
|
+
from .spec import SessionSpec
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class TigrblSessionBase(SessionABC):
|
|
14
|
+
"""
|
|
15
|
+
Common session behavior:
|
|
16
|
+
- Tracks SessionSpec
|
|
17
|
+
- Tracks transaction state (_open) and write intent (_dirty)
|
|
18
|
+
- Queues accidentally-async add() work and resolves on flush/commit
|
|
19
|
+
- Enforces read-only both on write calls and at commit
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
_spec: Optional[SessionSpec] = None
|
|
23
|
+
|
|
24
|
+
_open: bool = field(default=False, init=False)
|
|
25
|
+
_dirty: bool = field(default=False, init=False)
|
|
26
|
+
_pending: List[asyncio.Task] = field(default_factory=list, init=False)
|
|
27
|
+
|
|
28
|
+
# ---- utilities ----
|
|
29
|
+
def apply_spec(self, spec: SessionSpec | None) -> None:
|
|
30
|
+
self._spec = spec
|
|
31
|
+
|
|
32
|
+
async def run_sync(self, fn: Callable[[Any], Any]) -> Any:
|
|
33
|
+
"""
|
|
34
|
+
Default async marker: run the callback against *this* session.
|
|
35
|
+
Subclasses may override to pass the native handle.
|
|
36
|
+
"""
|
|
37
|
+
rv = fn(self)
|
|
38
|
+
if inspect.isawaitable(rv):
|
|
39
|
+
return await rv
|
|
40
|
+
return rv
|
|
41
|
+
|
|
42
|
+
# ---- TX template methods ----
|
|
43
|
+
async def begin(self) -> None:
|
|
44
|
+
await self._tx_begin_impl()
|
|
45
|
+
self._open = True
|
|
46
|
+
|
|
47
|
+
async def commit(self) -> None:
|
|
48
|
+
# late guard
|
|
49
|
+
if self._spec and self._spec.read_only and self._dirty:
|
|
50
|
+
raise RuntimeError("read-only session: writes detected before commit")
|
|
51
|
+
await self.flush()
|
|
52
|
+
await self._tx_commit_impl()
|
|
53
|
+
self._open = False
|
|
54
|
+
self._dirty = False
|
|
55
|
+
|
|
56
|
+
async def rollback(self) -> None:
|
|
57
|
+
# cancel queued add() tasks
|
|
58
|
+
for t in self._pending:
|
|
59
|
+
try:
|
|
60
|
+
t.cancel()
|
|
61
|
+
except Exception:
|
|
62
|
+
pass
|
|
63
|
+
self._pending.clear()
|
|
64
|
+
await self._tx_rollback_impl()
|
|
65
|
+
self._open = False
|
|
66
|
+
self._dirty = False
|
|
67
|
+
|
|
68
|
+
def in_transaction(self) -> bool:
|
|
69
|
+
return bool(self._open)
|
|
70
|
+
|
|
71
|
+
# ---- CRUD surface (template) ----
|
|
72
|
+
def add(self, obj: Any) -> None:
|
|
73
|
+
if self._spec and self._spec.read_only:
|
|
74
|
+
raise RuntimeError("write attempted in read-only session (add)")
|
|
75
|
+
self._dirty = True
|
|
76
|
+
rv = self._add_impl(obj)
|
|
77
|
+
if inspect.isawaitable(rv):
|
|
78
|
+
try:
|
|
79
|
+
loop = asyncio.get_running_loop()
|
|
80
|
+
except RuntimeError:
|
|
81
|
+
asyncio.run(rv)
|
|
82
|
+
else:
|
|
83
|
+
self._pending.append(loop.create_task(rv))
|
|
84
|
+
|
|
85
|
+
async def delete(self, obj: Any) -> None:
|
|
86
|
+
if self._spec and self._spec.read_only:
|
|
87
|
+
raise RuntimeError("write attempted in read-only session (delete)")
|
|
88
|
+
self._dirty = True
|
|
89
|
+
await self._delete_impl(obj)
|
|
90
|
+
|
|
91
|
+
async def flush(self) -> None:
|
|
92
|
+
if self._pending:
|
|
93
|
+
done, _ = await asyncio.wait(
|
|
94
|
+
self._pending, return_when=asyncio.ALL_COMPLETED
|
|
95
|
+
)
|
|
96
|
+
self._pending = []
|
|
97
|
+
# surface any exception
|
|
98
|
+
for t in done:
|
|
99
|
+
_ = t.result()
|
|
100
|
+
await self._flush_impl()
|
|
101
|
+
|
|
102
|
+
async def refresh(self, obj: Any) -> None:
|
|
103
|
+
await self._refresh_impl(obj)
|
|
104
|
+
|
|
105
|
+
async def get(self, model: type, ident: Any) -> Any | None:
|
|
106
|
+
return await self._get_impl(model, ident)
|
|
107
|
+
|
|
108
|
+
async def execute(self, stmt: Any) -> Any:
|
|
109
|
+
return await self._execute_impl(stmt)
|
|
110
|
+
|
|
111
|
+
async def close(self) -> None:
|
|
112
|
+
for t in self._pending:
|
|
113
|
+
try:
|
|
114
|
+
t.cancel()
|
|
115
|
+
except Exception:
|
|
116
|
+
pass
|
|
117
|
+
self._pending = []
|
|
118
|
+
await self._close_impl()
|
|
119
|
+
|
|
120
|
+
# ---- abstract primitives ----
|
|
121
|
+
async def _tx_begin_impl(self) -> None: # pragma: no cover - abstract hook
|
|
122
|
+
raise NotImplementedError
|
|
123
|
+
|
|
124
|
+
async def _tx_commit_impl(self) -> None: # pragma: no cover - abstract hook
|
|
125
|
+
raise NotImplementedError
|
|
126
|
+
|
|
127
|
+
async def _tx_rollback_impl(self) -> None: # pragma: no cover - abstract hook
|
|
128
|
+
raise NotImplementedError
|
|
129
|
+
|
|
130
|
+
def _add_impl(self, obj: Any) -> Any: # pragma: no cover - abstract hook
|
|
131
|
+
raise NotImplementedError
|
|
132
|
+
|
|
133
|
+
async def _delete_impl(self, obj: Any) -> None: # pragma: no cover - abstract hook
|
|
134
|
+
raise NotImplementedError
|
|
135
|
+
|
|
136
|
+
async def _flush_impl(self) -> None: # pragma: no cover - abstract hook
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
async def _refresh_impl(self, obj: Any) -> None: # pragma: no cover - abstract hook
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
async def _get_impl(
|
|
143
|
+
self, model: type, ident: Any
|
|
144
|
+
) -> Any | None: # pragma: no cover
|
|
145
|
+
raise NotImplementedError
|
|
146
|
+
|
|
147
|
+
async def _execute_impl(self, stmt: Any) -> Any: # pragma: no cover - abstract hook
|
|
148
|
+
raise NotImplementedError
|
|
149
|
+
|
|
150
|
+
async def _close_impl(self) -> None: # pragma: no cover - abstract hook
|
|
151
|
+
return
|