tigrbl 0.0.1.dev1__py3-none-any.whl → 0.3.0.dev3__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 +72 -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 +286 -0
- tigrbl/app/__init__.py +0 -0
- tigrbl/app/_app.py +61 -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 +314 -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 +265 -0
- tigrbl/bindings/rest/common.py +116 -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/member.py +367 -0
- tigrbl/bindings/rest/router.py +292 -0
- tigrbl/bindings/rest/routing.py +133 -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 +133 -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 +26 -0
- tigrbl/engine/_engine.py +130 -0
- tigrbl/engine/bind.py +33 -0
- tigrbl/engine/builders.py +236 -0
- tigrbl/engine/collect.py +111 -0
- tigrbl/engine/decorators.py +108 -0
- tigrbl/engine/engine_spec.py +261 -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 +111 -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 +144 -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 +17 -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 +628 -0
- tigrbl/runtime/labels.py +353 -0
- tigrbl/runtime/opview.py +87 -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 +55 -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/shortcuts.py +22 -0
- tigrbl/specs.py +44 -0
- tigrbl/system/__init__.py +12 -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/table/__init__.py +9 -0
- tigrbl/table/_base.py +237 -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 +174 -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-0.3.0.dev3.dist-info/LICENSE +201 -0
- tigrbl-0.3.0.dev3.dist-info/METADATA +501 -0
- tigrbl-0.3.0.dev3.dist-info/RECORD +249 -0
- tigrbl/ExampleAgent.py +0 -1
- tigrbl-0.0.1.dev1.dist-info/METADATA +0 -18
- tigrbl-0.0.1.dev1.dist-info/RECORD +0 -5
- {tigrbl-0.0.1.dev1.dist-info → tigrbl-0.3.0.dev3.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# tigrbl/v3/config/constants.py
|
|
2
|
+
"""
|
|
3
|
+
Tigrbl v3 – Configuration constants
|
|
4
|
+
|
|
5
|
+
Centralizes “well-known” names and defaults used across v3. These are
|
|
6
|
+
pure-Python constants; there is no runtime coupling to any web framework.
|
|
7
|
+
|
|
8
|
+
Highlights
|
|
9
|
+
----------
|
|
10
|
+
• Verbs/targets are *derived* from the v3 OpSpec canon so they always stay in sync.
|
|
11
|
+
• Default HTTP method mapping for REST bindings lives here (used by bindings.rest).
|
|
12
|
+
• Model config keys document the names we look for on SQLAlchemy classes.
|
|
13
|
+
|
|
14
|
+
Nothing in this module performs I/O.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import Mapping, Tuple
|
|
20
|
+
import re
|
|
21
|
+
|
|
22
|
+
# NOTE: importing CANON from ``ops.types`` introduces a circular dependency
|
|
23
|
+
# because that module transitively imports this one via ``hook``. To keep the
|
|
24
|
+
# constant values in sync without triggering the circular import at import time,
|
|
25
|
+
# we inline the canonical verb tuple here. This tuple **must** match
|
|
26
|
+
# ``tigrbl.op.types.CANON``.
|
|
27
|
+
CANON: Tuple[str, ...] = (
|
|
28
|
+
"create",
|
|
29
|
+
"read",
|
|
30
|
+
"update",
|
|
31
|
+
"replace",
|
|
32
|
+
"merge",
|
|
33
|
+
"delete",
|
|
34
|
+
"list",
|
|
35
|
+
"clear",
|
|
36
|
+
"bulk_create",
|
|
37
|
+
"bulk_update",
|
|
38
|
+
"bulk_replace",
|
|
39
|
+
"bulk_merge",
|
|
40
|
+
"bulk_delete",
|
|
41
|
+
"custom",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ───────────────────────────────────────────────────────────────────────────────
|
|
46
|
+
# Verbs / targets
|
|
47
|
+
# ───────────────────────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
# Source of truth: `CANON` keys (strings like "create", "read", "bulk_update", ...)
|
|
50
|
+
ALL_VERBS: frozenset[str] = frozenset(
|
|
51
|
+
str(k) for k in (CANON.keys() if hasattr(CANON, "keys") else CANON)
|
|
52
|
+
) # type: ignore[arg-type]
|
|
53
|
+
|
|
54
|
+
# Bulk targets are those prefixed with "bulk_"
|
|
55
|
+
BULK_VERBS: frozenset[str] = frozenset(v for v in ALL_VERBS if v.startswith("bulk_"))
|
|
56
|
+
|
|
57
|
+
# Routable canonical (non-bulk) verbs; excludes the synthetic "custom"
|
|
58
|
+
ROUTING_VERBS: frozenset[str] = frozenset(
|
|
59
|
+
v for v in ALL_VERBS if not v.startswith("bulk_") and v != "custom"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ───────────────────────────────────────────────────────────────────────────────
|
|
64
|
+
# Default HTTP methods per target (bindings.rest uses this unless overridden)
|
|
65
|
+
# ───────────────────────────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
DEFAULT_HTTP_METHODS: Mapping[str, Tuple[str, ...]] = {
|
|
68
|
+
"create": ("POST",),
|
|
69
|
+
"read": ("GET",),
|
|
70
|
+
"update": ("PATCH",),
|
|
71
|
+
"replace": ("PUT",),
|
|
72
|
+
"merge": ("PATCH",),
|
|
73
|
+
"delete": ("DELETE",),
|
|
74
|
+
"list": ("GET",),
|
|
75
|
+
"clear": ("DELETE",),
|
|
76
|
+
"bulk_create": ("POST",),
|
|
77
|
+
"bulk_update": ("PATCH",),
|
|
78
|
+
"bulk_replace": ("PUT",),
|
|
79
|
+
"bulk_merge": ("PATCH",),
|
|
80
|
+
"bulk_delete": ("DELETE",),
|
|
81
|
+
"custom": ("POST",),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ───────────────────────────────────────────────────────────────────────────────
|
|
86
|
+
# Model-level configuration attributes (looked up on the SQLAlchemy class)
|
|
87
|
+
# ───────────────────────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
# Well-known attribute names injected or inspected on ORM models
|
|
90
|
+
TIGRBL_REQUEST_EXTRAS_ATTR = (
|
|
91
|
+
"__tigrbl_request_extras__" # request-only virtual fields per verb
|
|
92
|
+
)
|
|
93
|
+
TIGRBL_RESPONSE_EXTRAS_ATTR = (
|
|
94
|
+
"__tigrbl_response_extras__" # response-only virtual fields per verb
|
|
95
|
+
)
|
|
96
|
+
TIGRBL_OPS_ATTR = "__tigrbl_ops__" # declarative OpSpec list from decorators
|
|
97
|
+
TIGRBL_VERB_ALIASES_ATTR = "__tigrbl_verb_aliases__" # optional verb alias map
|
|
98
|
+
TIGRBL_VERB_ALIAS_POLICY_ATTR = "__tigrbl_verb_alias_policy__" # alias policy override
|
|
99
|
+
TIGRBL_NESTED_PATHS_ATTR = "__tigrbl_nested_paths__" # nested path callback (optional)
|
|
100
|
+
TIGRBL_API_HOOKS_ATTR = "__tigrbl_api_hooks__" # API-level hooks map
|
|
101
|
+
TIGRBL_HOOKS_ATTR = "__tigrbl_hooks__" # model-level hooks map
|
|
102
|
+
TIGRBL_REGISTRY_LISTENER_ATTR = "__tigrbl_registry_listener__" # ops registry listener
|
|
103
|
+
TIGRBL_GET_DB_ATTR = "__tigrbl_get_db__" # DB dependency
|
|
104
|
+
TIGRBL_AUTH_DEP_ATTR = "__tigrbl_auth_dep__" # auth dependency
|
|
105
|
+
TIGRBL_AUTHORIZE_ATTR = "__tigrbl_authorize__" # authorization callable
|
|
106
|
+
TIGRBL_REST_DEPENDENCIES_ATTR = "__tigrbl_rest_dependencies__" # extra REST deps
|
|
107
|
+
TIGRBL_RPC_DEPENDENCIES_ATTR = "__tigrbl_rpc_dependencies__" # extra RPC deps
|
|
108
|
+
TIGRBL_OWNER_POLICY_ATTR = "__tigrbl_owner_policy__" # ownership policy override
|
|
109
|
+
TIGRBL_TENANT_POLICY_ATTR = "__tigrbl_tenant_policy__" # tenancy policy override
|
|
110
|
+
TIGRBL_ALLOW_ANON_ATTR = "__tigrbl_allow_anon__" # verbs callable without auth
|
|
111
|
+
TIGRBL_DEFAULTS_MODE_ATTR = "__tigrbl_defaults_mode__" # canonical verb wiring policy
|
|
112
|
+
TIGRBL_DEFAULTS_INCLUDE_ATTR = "__tigrbl_defaults_include__" # verbs to force include
|
|
113
|
+
TIGRBL_DEFAULTS_EXCLUDE_ATTR = "__tigrbl_defaults_exclude__" # verbs to force exclude
|
|
114
|
+
TIGRBL_SCHEMA_DECLS_ATTR = "__tigrbl_schema_decls__" # declared schemas map
|
|
115
|
+
|
|
116
|
+
# Aggregate of recognized model-level config attributes
|
|
117
|
+
MODEL_LEVEL_CFGS: frozenset[str] = frozenset(
|
|
118
|
+
{
|
|
119
|
+
TIGRBL_REQUEST_EXTRAS_ATTR,
|
|
120
|
+
TIGRBL_RESPONSE_EXTRAS_ATTR,
|
|
121
|
+
TIGRBL_OPS_ATTR,
|
|
122
|
+
TIGRBL_VERB_ALIASES_ATTR,
|
|
123
|
+
TIGRBL_VERB_ALIAS_POLICY_ATTR,
|
|
124
|
+
TIGRBL_NESTED_PATHS_ATTR,
|
|
125
|
+
TIGRBL_API_HOOKS_ATTR,
|
|
126
|
+
TIGRBL_HOOKS_ATTR,
|
|
127
|
+
TIGRBL_REGISTRY_LISTENER_ATTR,
|
|
128
|
+
TIGRBL_GET_DB_ATTR,
|
|
129
|
+
TIGRBL_AUTH_DEP_ATTR,
|
|
130
|
+
TIGRBL_AUTHORIZE_ATTR,
|
|
131
|
+
TIGRBL_REST_DEPENDENCIES_ATTR,
|
|
132
|
+
TIGRBL_RPC_DEPENDENCIES_ATTR,
|
|
133
|
+
TIGRBL_OWNER_POLICY_ATTR,
|
|
134
|
+
TIGRBL_TENANT_POLICY_ATTR,
|
|
135
|
+
TIGRBL_ALLOW_ANON_ATTR,
|
|
136
|
+
TIGRBL_DEFAULTS_MODE_ATTR,
|
|
137
|
+
TIGRBL_DEFAULTS_INCLUDE_ATTR,
|
|
138
|
+
TIGRBL_DEFAULTS_EXCLUDE_ATTR,
|
|
139
|
+
TIGRBL_SCHEMA_DECLS_ATTR,
|
|
140
|
+
"__resource__", # resource name override for REST
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Other internal attribute names
|
|
145
|
+
TIGRBL_CUSTOM_OP_ATTR = "__tigrbl_custom_op__" # marker for custom op
|
|
146
|
+
HOOK_DECLS_ATTR = "__tigrbl_hook_decls__" # per-function hook declarations
|
|
147
|
+
|
|
148
|
+
# ───────────────────────────────────────────────────────────────────────────────
|
|
149
|
+
# ‼ Everything is natively transactional now
|
|
150
|
+
# ‼ We will not support transactionals as a custom type or obj moving forward.
|
|
151
|
+
# ‼ Support is not guaranteed.
|
|
152
|
+
# ───────────────────────────────────────────────────────────────────────────────
|
|
153
|
+
TIGRBL_TX_MODELS_ATTR = "__tigrbl_tx_models__" # transactional model cache
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# ───────────────────────────────────────────────────────────────────────────────
|
|
157
|
+
# Common context keys used throughout runtime/bindings (non-normative)
|
|
158
|
+
# ───────────────────────────────────────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
# These string constants are *conventional* keys you may put into ctx, not
|
|
161
|
+
# enforced by Tigrbl. They’re listed here for discoverability.
|
|
162
|
+
CTX_REQUEST_KEY = "request"
|
|
163
|
+
CTX_DB_KEY = "db"
|
|
164
|
+
CTX_PAYLOAD_KEY = "payload"
|
|
165
|
+
CTX_PATH_PARAMS_KEY = "path_params"
|
|
166
|
+
CTX_ENV_KEY = "env"
|
|
167
|
+
CTX_RPC_ID_KEY = "rpc_id" # used by the JSON-RPC dispatcher
|
|
168
|
+
CTX_SKIP_PERSIST_FLAG = "__tigrbl_skip_persist__" # set by ephemeral ops
|
|
169
|
+
|
|
170
|
+
# Optional auth/multitenancy keys that middlewares may populate for hooks to read
|
|
171
|
+
CTX_USER_ID_KEY = "user_id"
|
|
172
|
+
CTX_TENANT_ID_KEY = "tenant_id"
|
|
173
|
+
CTX_AUTH_KEY = "auth"
|
|
174
|
+
|
|
175
|
+
# Request.state attribute that carries auth context (tenant/user ids)
|
|
176
|
+
TIGRBL_AUTH_CONTEXT_ATTR = "__tigrbl_auth_context__"
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# Regex for safe SQL identifiers
|
|
180
|
+
__SAFE_IDENT__ = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
|
181
|
+
|
|
182
|
+
__all__ = [
|
|
183
|
+
"ALL_VERBS",
|
|
184
|
+
"BULK_VERBS",
|
|
185
|
+
"ROUTING_VERBS",
|
|
186
|
+
"DEFAULT_HTTP_METHODS",
|
|
187
|
+
"MODEL_LEVEL_CFGS",
|
|
188
|
+
"TIGRBL_REQUEST_EXTRAS_ATTR",
|
|
189
|
+
"TIGRBL_RESPONSE_EXTRAS_ATTR",
|
|
190
|
+
"TIGRBL_OPS_ATTR",
|
|
191
|
+
"TIGRBL_VERB_ALIASES_ATTR",
|
|
192
|
+
"TIGRBL_VERB_ALIAS_POLICY_ATTR",
|
|
193
|
+
"TIGRBL_NESTED_PATHS_ATTR",
|
|
194
|
+
"TIGRBL_API_HOOKS_ATTR",
|
|
195
|
+
"TIGRBL_HOOKS_ATTR",
|
|
196
|
+
"TIGRBL_REGISTRY_LISTENER_ATTR",
|
|
197
|
+
"TIGRBL_GET_DB_ATTR",
|
|
198
|
+
"TIGRBL_AUTH_DEP_ATTR",
|
|
199
|
+
"TIGRBL_AUTHORIZE_ATTR",
|
|
200
|
+
"TIGRBL_REST_DEPENDENCIES_ATTR",
|
|
201
|
+
"TIGRBL_RPC_DEPENDENCIES_ATTR",
|
|
202
|
+
"TIGRBL_OWNER_POLICY_ATTR",
|
|
203
|
+
"TIGRBL_TENANT_POLICY_ATTR",
|
|
204
|
+
"TIGRBL_ALLOW_ANON_ATTR",
|
|
205
|
+
"TIGRBL_DEFAULTS_MODE_ATTR",
|
|
206
|
+
"TIGRBL_DEFAULTS_INCLUDE_ATTR",
|
|
207
|
+
"TIGRBL_DEFAULTS_EXCLUDE_ATTR",
|
|
208
|
+
"TIGRBL_SCHEMA_DECLS_ATTR",
|
|
209
|
+
"TIGRBL_CUSTOM_OP_ATTR",
|
|
210
|
+
"HOOK_DECLS_ATTR",
|
|
211
|
+
"TIGRBL_TX_MODELS_ATTR",
|
|
212
|
+
"CTX_REQUEST_KEY",
|
|
213
|
+
"CTX_DB_KEY",
|
|
214
|
+
"CTX_PAYLOAD_KEY",
|
|
215
|
+
"CTX_PATH_PARAMS_KEY",
|
|
216
|
+
"CTX_ENV_KEY",
|
|
217
|
+
"CTX_RPC_ID_KEY",
|
|
218
|
+
"CTX_SKIP_PERSIST_FLAG",
|
|
219
|
+
"CTX_USER_ID_KEY",
|
|
220
|
+
"CTX_TENANT_ID_KEY",
|
|
221
|
+
"CTX_AUTH_KEY",
|
|
222
|
+
"TIGRBL_AUTH_CONTEXT_ATTR",
|
|
223
|
+
"__SAFE_IDENT__",
|
|
224
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# tigrbl/v3/config/defaults.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Mapping
|
|
5
|
+
|
|
6
|
+
# Canonical defaults used by config.resolver and runtime atoms.
|
|
7
|
+
# Keep these conservative; adapters/apps can override at any scope.
|
|
8
|
+
DEFAULTS: Mapping[str, Any] = {
|
|
9
|
+
# ── wire/out (response shaping) ────────────────────────────────────────────
|
|
10
|
+
"exclude_none": False, # drop null-valued keys in wire:dump
|
|
11
|
+
"omit_nulls": False, # alias; resolver normalizes both ways
|
|
12
|
+
"response_extras_overwrite": False, # allow extras to replace existing keys
|
|
13
|
+
"extras_overwrite": False, # alias; resolver normalizes both ways
|
|
14
|
+
# ── wire/in (request validation) ───────────────────────────────────────────
|
|
15
|
+
"reject_unknown_fields": False, # if True, unknown input keys cause 422
|
|
16
|
+
# ── refresh / hydration policy ────────────────────────────────────────────
|
|
17
|
+
"refresh_policy": "auto", # 'auto' | 'always' | 'never'
|
|
18
|
+
"refresh_after_write": None, # Optional[bool]; resolver maps → policy
|
|
19
|
+
# ── validation/docs policy buckets (deep-merged) ──────────────────────────
|
|
20
|
+
# Shape: dict[op][field] = bool (true means "required for inbound")
|
|
21
|
+
"required_policy": {},
|
|
22
|
+
# ── docs/openapi knobs (deep-merged) ──────────────────────────────────────
|
|
23
|
+
"openapi": {},
|
|
24
|
+
"docs": {},
|
|
25
|
+
# ── tracing (deep-merged) ─────────────────────────────────────────────────
|
|
26
|
+
"trace": {
|
|
27
|
+
"enabled": True,
|
|
28
|
+
},
|
|
29
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# tigrbl/v3/config/resolver.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import asdict, is_dataclass
|
|
5
|
+
from types import MappingProxyType
|
|
6
|
+
from typing import Any, Dict, Iterable, Mapping, Optional
|
|
7
|
+
|
|
8
|
+
# Optional defaults module (kept tolerant if not present yet)
|
|
9
|
+
try:
|
|
10
|
+
from .defaults import DEFAULTS # expected: Mapping[str, Any]
|
|
11
|
+
except Exception: # pragma: no cover
|
|
12
|
+
DEFAULTS = {
|
|
13
|
+
# wire/out
|
|
14
|
+
"exclude_none": False,
|
|
15
|
+
"omit_nulls": False, # alias; normalized below
|
|
16
|
+
"response_extras_overwrite": False,
|
|
17
|
+
"extras_overwrite": False, # alias
|
|
18
|
+
# wire/in
|
|
19
|
+
"reject_unknown_fields": False,
|
|
20
|
+
# refresh
|
|
21
|
+
"refresh_policy": "auto", # 'auto' | 'always' | 'never'
|
|
22
|
+
"refresh_after_write": None, # Optional[bool] → normalized into refresh_policy
|
|
23
|
+
# validation/docs
|
|
24
|
+
"required_policy": {}, # dict[op][field] = bool
|
|
25
|
+
# misc buckets developers may use
|
|
26
|
+
"openapi": {},
|
|
27
|
+
"docs": {},
|
|
28
|
+
"trace": {"enabled": True},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Keys that should be deep-merged (dict ← dict) instead of overridden.
|
|
33
|
+
_DEEP_KEYS = {
|
|
34
|
+
"required_policy",
|
|
35
|
+
"openapi",
|
|
36
|
+
"docs",
|
|
37
|
+
"trace",
|
|
38
|
+
"policies",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class CfgView:
|
|
43
|
+
"""
|
|
44
|
+
Read-only attribute/dict view over a plain dict.
|
|
45
|
+
Unknown attributes return None (to play nicely with getattr(cfg, 'x', None)).
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
__slots__ = ("_data",)
|
|
49
|
+
|
|
50
|
+
def __init__(self, data: Mapping[str, Any]):
|
|
51
|
+
# freeze top-level mapping
|
|
52
|
+
self._data = MappingProxyType(dict(data))
|
|
53
|
+
|
|
54
|
+
def __getattr__(self, name: str) -> Any:
|
|
55
|
+
return self._data.get(name, None)
|
|
56
|
+
|
|
57
|
+
def __getitem__(self, key: str) -> Any:
|
|
58
|
+
return self._data[key]
|
|
59
|
+
|
|
60
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
61
|
+
return self._data.get(key, default)
|
|
62
|
+
|
|
63
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
64
|
+
"""Copy out a mutable dict (for diagnostics/serialization)."""
|
|
65
|
+
return dict(self._data)
|
|
66
|
+
|
|
67
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
68
|
+
return f"CfgView({dict(self._data)!r})"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
72
|
+
# Public API
|
|
73
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def resolve_cfg(
|
|
77
|
+
*,
|
|
78
|
+
model: Any = None,
|
|
79
|
+
specs: Optional[Mapping[str, Any]] = None,
|
|
80
|
+
op: Optional[str] = None,
|
|
81
|
+
opspec: Any = None,
|
|
82
|
+
tabspec: Any = None,
|
|
83
|
+
apispec: Any = None,
|
|
84
|
+
appspec: Any = None,
|
|
85
|
+
overrides: Optional[Mapping[str, Any]] = None,
|
|
86
|
+
) -> CfgView:
|
|
87
|
+
"""
|
|
88
|
+
Merge configuration from multiple scopes with precedence:
|
|
89
|
+
|
|
90
|
+
opspec > colspecs > tabspec > apispec > appspec > defaults
|
|
91
|
+
|
|
92
|
+
The result is normalized and returned as a read-only CfgView suitable for ctx.cfg.
|
|
93
|
+
|
|
94
|
+
Notes:
|
|
95
|
+
- 'specs' is the {field -> ColumnSpec} map; any col-level '.cfg' dicts are merged.
|
|
96
|
+
- Non-dict or None layers are ignored.
|
|
97
|
+
- For a few known keys we support aliases and light normalization (see _normalize()).
|
|
98
|
+
"""
|
|
99
|
+
layers: list[Mapping[str, Any]] = []
|
|
100
|
+
|
|
101
|
+
# 1) Base defaults (lowest precedence)
|
|
102
|
+
layers.append(_coerce_map(DEFAULTS))
|
|
103
|
+
|
|
104
|
+
# 2) App / API / Tab scopes
|
|
105
|
+
if appspec is not None:
|
|
106
|
+
layers.append(_extract_cfg(appspec))
|
|
107
|
+
if apispec is not None:
|
|
108
|
+
layers.append(_extract_cfg(apispec))
|
|
109
|
+
if tabspec is not None:
|
|
110
|
+
layers.append(_extract_cfg(tabspec))
|
|
111
|
+
|
|
112
|
+
# 3) Column-level aggregation (merged across all columns in stable order)
|
|
113
|
+
if specs:
|
|
114
|
+
col_cfg = _collect_col_cfg(specs)
|
|
115
|
+
if col_cfg:
|
|
116
|
+
layers.append(col_cfg)
|
|
117
|
+
|
|
118
|
+
# 4) Op-spec (highest declared spec layer)
|
|
119
|
+
if opspec is not None:
|
|
120
|
+
layers.append(_extract_cfg(opspec))
|
|
121
|
+
|
|
122
|
+
# 5) Per-request overrides (absolute highest precedence)
|
|
123
|
+
if overrides:
|
|
124
|
+
layers.append(_coerce_map(overrides))
|
|
125
|
+
|
|
126
|
+
# Merge with precedence (later wins), then normalize and freeze
|
|
127
|
+
merged = _merge_layers(layers)
|
|
128
|
+
merged = _normalize(merged, op=op)
|
|
129
|
+
|
|
130
|
+
return CfgView(merged)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
134
|
+
# Internals
|
|
135
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _coerce_map(obj: Any) -> Mapping[str, Any]:
|
|
139
|
+
"""Best-effort conversion of common config carriers to a plain mapping."""
|
|
140
|
+
if obj is None:
|
|
141
|
+
return {}
|
|
142
|
+
if isinstance(obj, Mapping):
|
|
143
|
+
return obj
|
|
144
|
+
# dataclass?
|
|
145
|
+
if is_dataclass(obj):
|
|
146
|
+
try:
|
|
147
|
+
# Drop keys with ``None`` values so they don't override defaults.
|
|
148
|
+
return {k: v for k, v in asdict(obj).items() if v is not None}
|
|
149
|
+
except Exception:
|
|
150
|
+
pass
|
|
151
|
+
# namespace-like with __dict__
|
|
152
|
+
d = getattr(obj, "__dict__", None)
|
|
153
|
+
if isinstance(d, dict):
|
|
154
|
+
return d
|
|
155
|
+
# pydantic v2 config-like
|
|
156
|
+
if hasattr(obj, "model_dump") and callable(getattr(obj, "model_dump")):
|
|
157
|
+
try:
|
|
158
|
+
return dict(obj.model_dump())
|
|
159
|
+
except Exception:
|
|
160
|
+
pass
|
|
161
|
+
# last resort: single 'cfg' attr if it's a mapping
|
|
162
|
+
cfg = getattr(obj, "cfg", None)
|
|
163
|
+
if isinstance(cfg, Mapping):
|
|
164
|
+
return cfg
|
|
165
|
+
return {}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _extract_cfg(spec: Any) -> Mapping[str, Any]:
|
|
169
|
+
"""
|
|
170
|
+
Pull a config mapping from a spec-like object.
|
|
171
|
+
Accepts: spec.cfg, spec.config, or the object itself if it's a mapping.
|
|
172
|
+
"""
|
|
173
|
+
if isinstance(spec, Mapping):
|
|
174
|
+
return spec
|
|
175
|
+
for name in ("cfg", "config"):
|
|
176
|
+
val = getattr(spec, name, None)
|
|
177
|
+
if isinstance(val, Mapping):
|
|
178
|
+
return val
|
|
179
|
+
# dataclass or namespace
|
|
180
|
+
return _coerce_map(spec)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _collect_col_cfg(specs: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
184
|
+
"""
|
|
185
|
+
Merge .cfg from ColumnSpec and its sub-specs (io/field/storage) across all columns.
|
|
186
|
+
Deep-merge dict values for keys in _DEEP_KEYS; override for scalars/others.
|
|
187
|
+
Later fields (lexicographic) win on conflicts to keep determinism.
|
|
188
|
+
"""
|
|
189
|
+
acc: Dict[str, Any] = {}
|
|
190
|
+
for field in sorted(specs.keys()):
|
|
191
|
+
col = specs[field]
|
|
192
|
+
# Collect potential cfg dictionaries from multiple places on ColumnSpec
|
|
193
|
+
for obj in (
|
|
194
|
+
col,
|
|
195
|
+
getattr(col, "io", None),
|
|
196
|
+
getattr(col, "field", None),
|
|
197
|
+
getattr(col, "storage", None),
|
|
198
|
+
):
|
|
199
|
+
mapping = _extract_cfg(obj)
|
|
200
|
+
if mapping:
|
|
201
|
+
acc = _merge_layers([acc, mapping])
|
|
202
|
+
return acc
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _merge_layers(layers: Iterable[Mapping[str, Any]]) -> Dict[str, Any]:
|
|
206
|
+
"""
|
|
207
|
+
Precedence-aware merge: later layers override earlier ones.
|
|
208
|
+
Dict values are shallow-merged except for keys in _DEEP_KEYS (deep merge).
|
|
209
|
+
"""
|
|
210
|
+
result: Dict[str, Any] = {}
|
|
211
|
+
for layer in layers:
|
|
212
|
+
if not isinstance(layer, Mapping):
|
|
213
|
+
continue
|
|
214
|
+
for k, v in layer.items():
|
|
215
|
+
if (
|
|
216
|
+
k in _DEEP_KEYS
|
|
217
|
+
and isinstance(result.get(k), Mapping)
|
|
218
|
+
and isinstance(v, Mapping)
|
|
219
|
+
):
|
|
220
|
+
result[k] = _deep_merge_dicts(result[k], v)
|
|
221
|
+
else:
|
|
222
|
+
result[k] = v
|
|
223
|
+
return result
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _deep_merge_dicts(a: Mapping[str, Any], b: Mapping[str, Any]) -> Dict[str, Any]:
|
|
227
|
+
"""
|
|
228
|
+
Deep merge two dicts; values in 'b' override 'a'. Only recurses on dicts.
|
|
229
|
+
Lists/sets/tuples are overridden (not concatenated) to remain predictable.
|
|
230
|
+
"""
|
|
231
|
+
out: Dict[str, Any] = dict(a)
|
|
232
|
+
for k, v in b.items():
|
|
233
|
+
av = out.get(k)
|
|
234
|
+
if isinstance(av, Mapping) and isinstance(v, Mapping):
|
|
235
|
+
out[k] = _deep_merge_dicts(av, v)
|
|
236
|
+
else:
|
|
237
|
+
out[k] = v
|
|
238
|
+
return out
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _normalize(cfg: Mapping[str, Any], *, op: Optional[str]) -> Dict[str, Any]:
|
|
242
|
+
"""
|
|
243
|
+
Produce a normalized config dict with aliases resolved and policy rules applied.
|
|
244
|
+
"""
|
|
245
|
+
d = dict(cfg) # copy
|
|
246
|
+
|
|
247
|
+
# 1) Refresh policy normalization
|
|
248
|
+
# - If refresh_after_write is explicitly True/False, that wins.
|
|
249
|
+
# - Otherwise ensure refresh_policy has a sane default.
|
|
250
|
+
raw_after = d.get("refresh_after_write", None)
|
|
251
|
+
if isinstance(raw_after, bool):
|
|
252
|
+
d["refresh_policy"] = "always" if raw_after else "never"
|
|
253
|
+
else:
|
|
254
|
+
pol = d.get("refresh_policy", None)
|
|
255
|
+
if pol not in {"auto", "always", "never"}:
|
|
256
|
+
d["refresh_policy"] = "auto"
|
|
257
|
+
|
|
258
|
+
# 2) Alias handling for omit nulls and extras overwrite
|
|
259
|
+
if "exclude_none" not in d and isinstance(d.get("omit_nulls"), bool):
|
|
260
|
+
d["exclude_none"] = bool(d["omit_nulls"])
|
|
261
|
+
if "omit_nulls" not in d and isinstance(d.get("exclude_none"), bool):
|
|
262
|
+
d["omit_nulls"] = bool(d["exclude_none"])
|
|
263
|
+
|
|
264
|
+
if "response_extras_overwrite" not in d and isinstance(
|
|
265
|
+
d.get("extras_overwrite"), bool
|
|
266
|
+
):
|
|
267
|
+
d["response_extras_overwrite"] = bool(d["extras_overwrite"])
|
|
268
|
+
if "extras_overwrite" not in d and isinstance(
|
|
269
|
+
d.get("response_extras_overwrite"), bool
|
|
270
|
+
):
|
|
271
|
+
d["extras_overwrite"] = bool(d["response_extras_overwrite"])
|
|
272
|
+
|
|
273
|
+
# 3) required_policy structure sanity: dict[op][field] = bool
|
|
274
|
+
rp = d.get("required_policy")
|
|
275
|
+
if not isinstance(rp, Mapping):
|
|
276
|
+
d["required_policy"] = {}
|
|
277
|
+
else:
|
|
278
|
+
# ensure nested dicts & booleans; ignore malformed entries
|
|
279
|
+
fixed: Dict[str, Dict[str, bool]] = {}
|
|
280
|
+
for op_name, per_field in rp.items():
|
|
281
|
+
if not isinstance(per_field, Mapping):
|
|
282
|
+
continue
|
|
283
|
+
fixed[str(op_name)] = {
|
|
284
|
+
str(f): bool(v)
|
|
285
|
+
for f, v in per_field.items()
|
|
286
|
+
if isinstance(v, (bool, int))
|
|
287
|
+
}
|
|
288
|
+
d["required_policy"] = fixed
|
|
289
|
+
|
|
290
|
+
# 4) Optional op-specific view (pre-resolved convenience)
|
|
291
|
+
if op:
|
|
292
|
+
per_op = d["required_policy"].get(op, {})
|
|
293
|
+
d.setdefault("_required_for_op", per_op)
|
|
294
|
+
|
|
295
|
+
return d
|
tigrbl/core/__init__.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# tigrbl/v3/core/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Tigrbl v3 – Core operations.
|
|
4
|
+
|
|
5
|
+
Re-exports the canonical CRUD bodies implemented in `.crud`.
|
|
6
|
+
|
|
7
|
+
Notes:
|
|
8
|
+
- These functions are **flush-only**. They never call `db.commit()`.
|
|
9
|
+
- Final commits are driven by the runtime executor's `END_TX` phase.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from .crud import (
|
|
15
|
+
create,
|
|
16
|
+
read,
|
|
17
|
+
update,
|
|
18
|
+
replace,
|
|
19
|
+
merge,
|
|
20
|
+
delete,
|
|
21
|
+
list as _list, # avoid shadowing built-in, then re-export as `list`
|
|
22
|
+
clear,
|
|
23
|
+
bulk_create,
|
|
24
|
+
bulk_update,
|
|
25
|
+
bulk_replace,
|
|
26
|
+
bulk_merge,
|
|
27
|
+
bulk_delete,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Public alias named exactly `list` to preserve API surface
|
|
31
|
+
list = _list # noqa: A001 - intentional shadow of built-in for public API
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"create",
|
|
35
|
+
"read",
|
|
36
|
+
"update",
|
|
37
|
+
"replace",
|
|
38
|
+
"merge",
|
|
39
|
+
"delete",
|
|
40
|
+
"list",
|
|
41
|
+
"clear",
|
|
42
|
+
"bulk_create",
|
|
43
|
+
"bulk_update",
|
|
44
|
+
"bulk_replace",
|
|
45
|
+
"bulk_merge",
|
|
46
|
+
"bulk_delete",
|
|
47
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from .ops import (
|
|
2
|
+
create,
|
|
3
|
+
read,
|
|
4
|
+
update,
|
|
5
|
+
replace,
|
|
6
|
+
merge,
|
|
7
|
+
delete,
|
|
8
|
+
list as _list,
|
|
9
|
+
clear,
|
|
10
|
+
)
|
|
11
|
+
from .bulk import (
|
|
12
|
+
bulk_create,
|
|
13
|
+
bulk_update,
|
|
14
|
+
bulk_replace,
|
|
15
|
+
bulk_merge,
|
|
16
|
+
bulk_delete,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Public alias named exactly `list` to preserve API surface
|
|
20
|
+
list = _list # noqa: A001 - intentional shadow of built-in for public API
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"create",
|
|
24
|
+
"read",
|
|
25
|
+
"update",
|
|
26
|
+
"replace",
|
|
27
|
+
"merge",
|
|
28
|
+
"delete",
|
|
29
|
+
"list",
|
|
30
|
+
"clear",
|
|
31
|
+
"bulk_create",
|
|
32
|
+
"bulk_update",
|
|
33
|
+
"bulk_replace",
|
|
34
|
+
"bulk_merge",
|
|
35
|
+
"bulk_delete",
|
|
36
|
+
]
|