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,261 @@
|
|
|
1
|
+
# tigrbl/v3/engine/engine_spec.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, field, fields
|
|
5
|
+
from typing import Optional, Mapping, Union, Any, Tuple
|
|
6
|
+
from urllib.parse import urlsplit, urlunsplit
|
|
7
|
+
|
|
8
|
+
from ._engine import Engine, Provider, SessionFactory
|
|
9
|
+
from .builders import (
|
|
10
|
+
async_postgres_engine,
|
|
11
|
+
async_sqlite_engine,
|
|
12
|
+
blocking_postgres_engine,
|
|
13
|
+
blocking_sqlite_engine,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# The value stored by @engine_ctx on App/API/Table/Op.
|
|
17
|
+
# Accept either a DSN string, structured mapping, or pre-built objects.
|
|
18
|
+
EngineCfg = Union[str, Mapping[str, object], "EngineSpec", Provider, Engine]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class EngineSpec:
|
|
23
|
+
"""
|
|
24
|
+
Canonical, normalized engine spec → Provider factory.
|
|
25
|
+
|
|
26
|
+
Input comes from @engine_ctx attached to an App/API/Table/Op:
|
|
27
|
+
• DSN string:
|
|
28
|
+
"sqlite://:memory:" ,
|
|
29
|
+
"sqlite:///./file.db" ,
|
|
30
|
+
"sqlite+aiosqlite:///./file.db" ,
|
|
31
|
+
"postgresql://user:pwd@host:5432/db" ,
|
|
32
|
+
"postgresql+asyncpg://user:pwd@host:5432/db"
|
|
33
|
+
• Mapping (recommended for clarity/portability):
|
|
34
|
+
{
|
|
35
|
+
"kind": "sqlite" | "postgres",
|
|
36
|
+
"async": bool, # default False
|
|
37
|
+
# sqlite:
|
|
38
|
+
"path": "./file.db", # file-backed
|
|
39
|
+
"mode": "memory" | None, # memory uses StaticPool
|
|
40
|
+
# postgres:
|
|
41
|
+
"user": "app", "pwd": "secret",
|
|
42
|
+
"host": "localhost", "port": 5432, "db": "app_db",
|
|
43
|
+
"pool_size": 10, "max": 20 # max_overflow (sync) or max_size (async)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
This class *does not* open connections; call .to_provider() to obtain
|
|
47
|
+
a lazy Provider that builds (engine, sessionmaker) on first use.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
# normalized fields
|
|
51
|
+
kind: Optional[str] = None # "sqlite" | "postgres"
|
|
52
|
+
async_: bool = False
|
|
53
|
+
|
|
54
|
+
# sqlite
|
|
55
|
+
path: Optional[str] = None # file path (None when memory=True)
|
|
56
|
+
memory: bool = False
|
|
57
|
+
|
|
58
|
+
# postgres
|
|
59
|
+
user: Optional[str] = None
|
|
60
|
+
pwd: Optional[str] = field(default=None, repr=False)
|
|
61
|
+
host: Optional[str] = None
|
|
62
|
+
port: Optional[int] = None
|
|
63
|
+
name: Optional[str] = None
|
|
64
|
+
pool_size: int = 10
|
|
65
|
+
max: int = 20 # max_overflow (sync) or max_size (async)
|
|
66
|
+
|
|
67
|
+
# raw passthroughs (for diagnostics)
|
|
68
|
+
dsn: Optional[str] = None
|
|
69
|
+
mapping: Optional[Mapping[str, object]] = field(default=None, repr=False)
|
|
70
|
+
|
|
71
|
+
def __repr__(self) -> str: # pragma: no cover - representation logic
|
|
72
|
+
parts = []
|
|
73
|
+
for f in fields(self):
|
|
74
|
+
if not f.repr:
|
|
75
|
+
continue
|
|
76
|
+
value = getattr(self, f.name)
|
|
77
|
+
if f.name == "dsn":
|
|
78
|
+
value = self._redact_dsn(value)
|
|
79
|
+
if value is not None:
|
|
80
|
+
parts.append(f"{f.name}={value!r}")
|
|
81
|
+
return f"EngineSpec({', '.join(parts)})"
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def _redact_dsn(dsn: Optional[str]) -> Optional[str]:
|
|
85
|
+
if not dsn:
|
|
86
|
+
return dsn
|
|
87
|
+
try:
|
|
88
|
+
parts = urlsplit(dsn)
|
|
89
|
+
if parts.password:
|
|
90
|
+
netloc = parts.netloc.replace(parts.password, "***")
|
|
91
|
+
return urlunsplit(
|
|
92
|
+
(parts.scheme, netloc, parts.path, parts.query, parts.fragment)
|
|
93
|
+
)
|
|
94
|
+
except Exception:
|
|
95
|
+
pass
|
|
96
|
+
return dsn
|
|
97
|
+
|
|
98
|
+
# ---------- parsing / normalization ----------
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def from_any(x: EngineCfg | None) -> Optional["EngineSpec"]:
|
|
102
|
+
"""
|
|
103
|
+
Parse a DSN or mapping (as attached by @engine_ctx) into an EngineSpec.
|
|
104
|
+
"""
|
|
105
|
+
if x is None:
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
if isinstance(x, EngineSpec):
|
|
109
|
+
return x
|
|
110
|
+
|
|
111
|
+
if isinstance(x, Provider):
|
|
112
|
+
return x.spec
|
|
113
|
+
|
|
114
|
+
if isinstance(x, Engine):
|
|
115
|
+
return x.spec
|
|
116
|
+
|
|
117
|
+
# String DSN
|
|
118
|
+
if isinstance(x, str):
|
|
119
|
+
s = x.strip()
|
|
120
|
+
# sqlite memory
|
|
121
|
+
if s == "sqlite://:memory:" or s.startswith("sqlite+memory://"):
|
|
122
|
+
return EngineSpec(
|
|
123
|
+
kind="sqlite",
|
|
124
|
+
async_=s.startswith("sqlite+aiosqlite://"),
|
|
125
|
+
memory=True,
|
|
126
|
+
dsn=s,
|
|
127
|
+
)
|
|
128
|
+
# sqlite async file
|
|
129
|
+
if s.startswith("sqlite+aiosqlite:///"):
|
|
130
|
+
return EngineSpec(
|
|
131
|
+
kind="sqlite", async_=True, path=s.split(":///")[1], dsn=s
|
|
132
|
+
)
|
|
133
|
+
# sqlite sync file
|
|
134
|
+
if s.startswith("sqlite:///"):
|
|
135
|
+
return EngineSpec(
|
|
136
|
+
kind="sqlite", async_=False, path=s.split(":///")[1], dsn=s
|
|
137
|
+
)
|
|
138
|
+
# postgres async
|
|
139
|
+
if s.startswith("postgresql+asyncpg://"):
|
|
140
|
+
return EngineSpec(kind="postgres", async_=True, dsn=s)
|
|
141
|
+
# postgres sync
|
|
142
|
+
if s.startswith("postgresql://"):
|
|
143
|
+
return EngineSpec(kind="postgres", async_=False, dsn=s)
|
|
144
|
+
raise ValueError(f"Unsupported DSN: {s}")
|
|
145
|
+
|
|
146
|
+
# Mapping
|
|
147
|
+
m = x # type: ignore[assignment]
|
|
148
|
+
|
|
149
|
+
# allow a few common aliases for ergonomics
|
|
150
|
+
def _get_bool(key: str, *aliases: str, default: bool = False) -> bool:
|
|
151
|
+
for k in (key, *aliases):
|
|
152
|
+
if k in m:
|
|
153
|
+
return bool(m[k]) # type: ignore[index]
|
|
154
|
+
return default
|
|
155
|
+
|
|
156
|
+
def _get_str(
|
|
157
|
+
key: str, *aliases: str, default: Optional[str] = None
|
|
158
|
+
) -> Optional[str]:
|
|
159
|
+
for k in (key, *aliases):
|
|
160
|
+
if k in m and m[k] is not None:
|
|
161
|
+
return str(m[k]) # type: ignore[index]
|
|
162
|
+
return default
|
|
163
|
+
|
|
164
|
+
def _get_int(
|
|
165
|
+
key: str, *aliases: str, default: Optional[int] = None
|
|
166
|
+
) -> Optional[int]:
|
|
167
|
+
for k in (key, *aliases):
|
|
168
|
+
if k in m and m[k] is not None:
|
|
169
|
+
return int(m[k]) # type: ignore[index]
|
|
170
|
+
return default
|
|
171
|
+
|
|
172
|
+
k = str(m.get("kind", m.get("engine", ""))).lower() # type: ignore[index]
|
|
173
|
+
if k == "sqlite":
|
|
174
|
+
async_ = _get_bool("async", "async_", default=False)
|
|
175
|
+
path = _get_str("path")
|
|
176
|
+
# support either {"mode": "memory"} or {"memory": True} or no path
|
|
177
|
+
memory = (
|
|
178
|
+
_get_bool("memory", default=False)
|
|
179
|
+
or (str(m.get("mode", "")).lower() == "memory")
|
|
180
|
+
or (path is None)
|
|
181
|
+
)
|
|
182
|
+
return EngineSpec(
|
|
183
|
+
kind="sqlite",
|
|
184
|
+
async_=async_,
|
|
185
|
+
path=None if memory else path,
|
|
186
|
+
memory=memory,
|
|
187
|
+
mapping=m,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if k == "postgres":
|
|
191
|
+
async_ = _get_bool("async", "async_", default=False)
|
|
192
|
+
return EngineSpec(
|
|
193
|
+
kind="postgres",
|
|
194
|
+
async_=async_,
|
|
195
|
+
user=_get_str("user"),
|
|
196
|
+
pwd=_get_str("pwd", "password"),
|
|
197
|
+
host=_get_str("host"),
|
|
198
|
+
port=_get_int("port"),
|
|
199
|
+
name=_get_str("db", "name"),
|
|
200
|
+
pool_size=_get_int("pool_size", default=10) or 10,
|
|
201
|
+
max=_get_int("max", "max_overflow", "max_size", default=20) or 20,
|
|
202
|
+
mapping=m,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
raise ValueError(f"Unsupported provider kind: {k!r}")
|
|
206
|
+
|
|
207
|
+
# ---------- realization ----------
|
|
208
|
+
|
|
209
|
+
def build(self) -> Tuple[Any, SessionFactory]:
|
|
210
|
+
"""Construct the engine and sessionmaker for this spec."""
|
|
211
|
+
if self.kind == "sqlite":
|
|
212
|
+
if self.memory:
|
|
213
|
+
if self.async_:
|
|
214
|
+
return async_sqlite_engine(path=None)
|
|
215
|
+
return blocking_sqlite_engine(path=None)
|
|
216
|
+
if not self.path:
|
|
217
|
+
raise ValueError("sqlite file requires 'path'")
|
|
218
|
+
if self.async_:
|
|
219
|
+
return async_sqlite_engine(path=self.path)
|
|
220
|
+
return blocking_sqlite_engine(path=self.path)
|
|
221
|
+
|
|
222
|
+
if self.kind == "postgres":
|
|
223
|
+
if self.dsn:
|
|
224
|
+
if self.async_:
|
|
225
|
+
return async_postgres_engine(
|
|
226
|
+
dsn=self.dsn,
|
|
227
|
+
pool_size=self.pool_size,
|
|
228
|
+
max_size=self.max,
|
|
229
|
+
)
|
|
230
|
+
return blocking_postgres_engine(
|
|
231
|
+
dsn=self.dsn,
|
|
232
|
+
pool_size=self.pool_size,
|
|
233
|
+
max_overflow=self.max,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
kwargs = {
|
|
237
|
+
"pool_size": self.pool_size,
|
|
238
|
+
}
|
|
239
|
+
if self.async_:
|
|
240
|
+
kwargs["max_size"] = self.max
|
|
241
|
+
else:
|
|
242
|
+
kwargs["max_overflow"] = self.max
|
|
243
|
+
if self.user is not None:
|
|
244
|
+
kwargs["user"] = self.user
|
|
245
|
+
if self.pwd is not None:
|
|
246
|
+
kwargs["pwd"] = self.pwd
|
|
247
|
+
if self.host is not None:
|
|
248
|
+
kwargs["host"] = self.host
|
|
249
|
+
if self.port is not None:
|
|
250
|
+
kwargs["port"] = self.port
|
|
251
|
+
if self.name is not None:
|
|
252
|
+
kwargs["db"] = self.name
|
|
253
|
+
if self.async_:
|
|
254
|
+
return async_postgres_engine(**kwargs)
|
|
255
|
+
return blocking_postgres_engine(**kwargs)
|
|
256
|
+
|
|
257
|
+
raise ValueError("EngineSpec has no kind")
|
|
258
|
+
|
|
259
|
+
def to_provider(self) -> Provider:
|
|
260
|
+
"""Materialize a lazy :class:`Provider` for this spec."""
|
|
261
|
+
return Provider(self)
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# tigrbl/tigrbl/v3/engine/resolver.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import inspect
|
|
6
|
+
import logging
|
|
7
|
+
import threading
|
|
8
|
+
from typing import Any, Callable, Optional
|
|
9
|
+
|
|
10
|
+
from ._engine import AsyncSession, Engine, Provider, Session
|
|
11
|
+
from .engine_spec import EngineSpec, EngineCfg
|
|
12
|
+
|
|
13
|
+
logging.getLogger("uvicorn").setLevel(logging.DEBUG)
|
|
14
|
+
logger = logging.getLogger("uvicorn")
|
|
15
|
+
|
|
16
|
+
# Registry with strict precedence: op > model > api > app
|
|
17
|
+
_LOCK = threading.RLock()
|
|
18
|
+
_DEFAULT: Optional[Provider] = None
|
|
19
|
+
_API: dict[int, Provider] = {}
|
|
20
|
+
_TAB: dict[Any, Provider] = {}
|
|
21
|
+
_OP: dict[tuple[Any, str], Provider] = {}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _with_class(obj: Any) -> list[Any]:
|
|
25
|
+
"""Return ``obj`` and its class when ``obj`` is an instance.
|
|
26
|
+
|
|
27
|
+
This allows resolution to honor providers registered on classes even when
|
|
28
|
+
an instance is supplied at lookup time.
|
|
29
|
+
"""
|
|
30
|
+
return [obj] if isinstance(obj, type) else [obj, type(obj)]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _coerce(ctx: Optional[EngineCfg]) -> Optional[Provider]:
|
|
34
|
+
"""
|
|
35
|
+
Promote an @engine_ctx value to a lazy Provider.
|
|
36
|
+
"""
|
|
37
|
+
logger.debug("_coerce called with ctx=%r", ctx)
|
|
38
|
+
if ctx is None:
|
|
39
|
+
logger.debug("_coerce: ctx is None")
|
|
40
|
+
return None
|
|
41
|
+
if isinstance(ctx, Provider):
|
|
42
|
+
logger.debug("_coerce: ctx is already a Provider")
|
|
43
|
+
return ctx
|
|
44
|
+
if isinstance(ctx, Engine):
|
|
45
|
+
logger.debug("_coerce: ctx is an Engine; returning provider")
|
|
46
|
+
return ctx.provider
|
|
47
|
+
if isinstance(ctx, EngineSpec):
|
|
48
|
+
logger.debug("_coerce: ctx is an EngineSpec; converting to provider")
|
|
49
|
+
return ctx.to_provider()
|
|
50
|
+
spec = EngineSpec.from_any(ctx)
|
|
51
|
+
logger.debug("_coerce: EngineSpec.from_any returned %r", spec)
|
|
52
|
+
return spec.to_provider() if spec else None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ---- registration -----------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def set_default(ctx: EngineCfg | None) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Register the app-level default Provider used when no API/table/op binds.
|
|
61
|
+
"""
|
|
62
|
+
global _DEFAULT
|
|
63
|
+
prov = _coerce(ctx)
|
|
64
|
+
logger.debug("set_default: setting default provider to %r", prov)
|
|
65
|
+
with _LOCK:
|
|
66
|
+
_DEFAULT = prov
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def register_api(api: Any, ctx: EngineCfg | None) -> None:
|
|
70
|
+
"""
|
|
71
|
+
Register an API-level Provider.
|
|
72
|
+
"""
|
|
73
|
+
prov = _coerce(ctx)
|
|
74
|
+
logger.debug("register_api: api=%r coerced provider=%r", api, prov)
|
|
75
|
+
if prov is None:
|
|
76
|
+
logger.debug("register_api: no provider; skipping registration")
|
|
77
|
+
return
|
|
78
|
+
with _LOCK:
|
|
79
|
+
_API[id(api)] = prov
|
|
80
|
+
logger.debug("register_api: registered provider for api id %s", id(api))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def register_table(model: Any, ctx: EngineCfg | None) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Register a table/model-level Provider.
|
|
86
|
+
"""
|
|
87
|
+
prov = _coerce(ctx)
|
|
88
|
+
logger.debug("register_table: model=%r coerced provider=%r", model, prov)
|
|
89
|
+
if prov is None:
|
|
90
|
+
logger.debug("register_table: no provider; skipping registration")
|
|
91
|
+
return
|
|
92
|
+
with _LOCK:
|
|
93
|
+
_TAB[model] = prov
|
|
94
|
+
logger.debug("register_table: registered provider for model %r", model)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def register_op(model: Any, alias: str, ctx: EngineCfg | None) -> None:
|
|
98
|
+
"""
|
|
99
|
+
Register an op-level Provider for (model, alias).
|
|
100
|
+
"""
|
|
101
|
+
prov = _coerce(ctx)
|
|
102
|
+
logger.debug(
|
|
103
|
+
"register_op: model=%r alias=%r coerced provider=%r", model, alias, prov
|
|
104
|
+
)
|
|
105
|
+
if prov is None:
|
|
106
|
+
logger.debug("register_op: no provider; skipping registration")
|
|
107
|
+
return
|
|
108
|
+
with _LOCK:
|
|
109
|
+
_OP[(model, alias)] = prov
|
|
110
|
+
logger.debug(
|
|
111
|
+
"register_op: registered provider for model %r alias %s", model, alias
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ---- resolution -------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def resolve_provider(
|
|
119
|
+
*,
|
|
120
|
+
api: Any = None,
|
|
121
|
+
model: Any = None,
|
|
122
|
+
op_alias: str | None = None,
|
|
123
|
+
) -> Optional[Provider]:
|
|
124
|
+
"""
|
|
125
|
+
Resolve the effective Provider using precedence:
|
|
126
|
+
op > model > api > app(default)
|
|
127
|
+
"""
|
|
128
|
+
logger.debug(
|
|
129
|
+
"resolve_provider called with api=%r model=%r op_alias=%r",
|
|
130
|
+
api,
|
|
131
|
+
model,
|
|
132
|
+
op_alias,
|
|
133
|
+
)
|
|
134
|
+
with _LOCK:
|
|
135
|
+
if model is not None and op_alias is not None:
|
|
136
|
+
logger.debug("resolve_provider: checking op-level provider")
|
|
137
|
+
for m in _with_class(model):
|
|
138
|
+
logger.debug(
|
|
139
|
+
"resolve_provider: looking for op provider for %r alias %s",
|
|
140
|
+
m,
|
|
141
|
+
op_alias,
|
|
142
|
+
)
|
|
143
|
+
p = _OP.get((m, op_alias))
|
|
144
|
+
if p:
|
|
145
|
+
logger.debug("resolve_provider: found op-level provider %r", p)
|
|
146
|
+
return p
|
|
147
|
+
if model is not None:
|
|
148
|
+
logger.debug("resolve_provider: checking model-level provider")
|
|
149
|
+
for m in _with_class(model):
|
|
150
|
+
logger.debug("resolve_provider: looking for model provider %r", m)
|
|
151
|
+
p = _TAB.get(m)
|
|
152
|
+
if p:
|
|
153
|
+
logger.debug("resolve_provider: found model-level provider %r", p)
|
|
154
|
+
return p
|
|
155
|
+
if api is not None:
|
|
156
|
+
logger.debug("resolve_provider: checking api-level provider")
|
|
157
|
+
for a in _with_class(api):
|
|
158
|
+
logger.debug("resolve_provider: looking for api provider %r", a)
|
|
159
|
+
# APIs are keyed by ``id`` to avoid relying on ``__hash__``
|
|
160
|
+
p = _API.get(id(a))
|
|
161
|
+
if p:
|
|
162
|
+
logger.debug("resolve_provider: found api-level provider %r", p)
|
|
163
|
+
return p
|
|
164
|
+
logger.debug("resolve_provider: returning default provider %r", _DEFAULT)
|
|
165
|
+
return _DEFAULT
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
SessionT = Session | AsyncSession
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def acquire(
|
|
172
|
+
*,
|
|
173
|
+
api: Any = None,
|
|
174
|
+
model: Any = None,
|
|
175
|
+
op_alias: str | None = None,
|
|
176
|
+
) -> tuple[SessionT, Callable[[], None]]:
|
|
177
|
+
"""
|
|
178
|
+
Acquire a DB session from the resolved Provider.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
(session, release_fn)
|
|
182
|
+
|
|
183
|
+
Raises:
|
|
184
|
+
RuntimeError: if no Provider can be resolved and no default is set.
|
|
185
|
+
"""
|
|
186
|
+
logger.debug(
|
|
187
|
+
"acquire called with api=%r model=%r op_alias=%r", api, model, op_alias
|
|
188
|
+
)
|
|
189
|
+
p = resolve_provider(api=api, model=model, op_alias=op_alias)
|
|
190
|
+
if p is None:
|
|
191
|
+
logger.debug("acquire: no provider resolved; raising error")
|
|
192
|
+
raise RuntimeError(
|
|
193
|
+
f"No database provider configured for op={op_alias} "
|
|
194
|
+
f"model={getattr(model, '__name__', model)} "
|
|
195
|
+
f"api={type(api).__name__ if api else None} and no default"
|
|
196
|
+
)
|
|
197
|
+
db: SessionT = p.session()
|
|
198
|
+
logger.debug("acquire: session %r acquired from provider %r", db, p)
|
|
199
|
+
|
|
200
|
+
def _release() -> None:
|
|
201
|
+
logger.debug("_release: attempting to release session %r", db)
|
|
202
|
+
close = getattr(db, "close", None)
|
|
203
|
+
if callable(close):
|
|
204
|
+
try:
|
|
205
|
+
rv = close()
|
|
206
|
+
logger.debug("_release: close returned %r", rv)
|
|
207
|
+
if inspect.isawaitable(rv):
|
|
208
|
+
logger.debug("_release: awaiting asynchronous close")
|
|
209
|
+
try:
|
|
210
|
+
loop = asyncio.get_running_loop()
|
|
211
|
+
except RuntimeError:
|
|
212
|
+
logger.debug("_release: no running loop; using asyncio.run")
|
|
213
|
+
asyncio.run(rv)
|
|
214
|
+
else:
|
|
215
|
+
logger.debug("_release: scheduling close on running loop")
|
|
216
|
+
loop.create_task(rv)
|
|
217
|
+
# If close is sync, it has already executed
|
|
218
|
+
except Exception:
|
|
219
|
+
logger.debug("_release: error during close", exc_info=True)
|
|
220
|
+
# best-effort close; swallow to avoid masking handler errors
|
|
221
|
+
pass
|
|
222
|
+
logger.debug("_release: release complete for session %r", db)
|
|
223
|
+
|
|
224
|
+
return db, _release
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# tigrbl/tigrbl/v3/engine/shortcuts.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Mapping, Optional, Union
|
|
5
|
+
|
|
6
|
+
from .engine_spec import EngineSpec
|
|
7
|
+
from ._engine import Provider, Engine
|
|
8
|
+
|
|
9
|
+
EngineCfg = Union[str, Mapping[str, object]] # DSN string or structured mapping
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
# EngineSpec / Provider / Engine helpers (ctx builder collapsed into
|
|
14
|
+
# engine_spec)
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def engine_spec(
|
|
19
|
+
spec: Union[EngineCfg, Mapping[str, Any], str, None] = None, **kw: Any
|
|
20
|
+
) -> EngineSpec:
|
|
21
|
+
"""Build an :class:`EngineSpec` from a DSN string, mapping, or keyword fields."""
|
|
22
|
+
if spec is None and kw:
|
|
23
|
+
# Inline the former ctx builder behavior (no double wrap)
|
|
24
|
+
dsn: Optional[str] = kw.get("dsn")
|
|
25
|
+
if dsn:
|
|
26
|
+
spec = dsn
|
|
27
|
+
else:
|
|
28
|
+
kind = kw.get("kind")
|
|
29
|
+
if not kind:
|
|
30
|
+
raise ValueError(
|
|
31
|
+
"Provide spec=<DSN|mapping> or kind=('sqlite'|'postgres') with appropriate fields"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
async_kw = kw.get("async_")
|
|
35
|
+
if async_kw is None:
|
|
36
|
+
async_kw = kw.get("async")
|
|
37
|
+
|
|
38
|
+
if kind == "sqlite":
|
|
39
|
+
path = kw.get("path")
|
|
40
|
+
mode = kw.get("mode")
|
|
41
|
+
memory_flag = kw.get("memory")
|
|
42
|
+
memory = (
|
|
43
|
+
(mode == "memory")
|
|
44
|
+
or (memory_flag is True)
|
|
45
|
+
or (not path and mode != "file")
|
|
46
|
+
)
|
|
47
|
+
async_default = True if async_kw is None and memory else False
|
|
48
|
+
async_ = bool(async_kw) if async_kw is not None else async_default
|
|
49
|
+
if memory:
|
|
50
|
+
spec = {"kind": "sqlite", "async": async_, "mode": "memory"}
|
|
51
|
+
else:
|
|
52
|
+
if not path:
|
|
53
|
+
raise ValueError("sqlite file requires 'path'")
|
|
54
|
+
spec = {"kind": "sqlite", "async": async_, "path": path}
|
|
55
|
+
|
|
56
|
+
elif kind == "postgres":
|
|
57
|
+
async_ = bool(async_kw) if async_kw is not None else False
|
|
58
|
+
spec = {
|
|
59
|
+
"kind": "postgres",
|
|
60
|
+
"async": async_,
|
|
61
|
+
"user": kw.get("user", "app"),
|
|
62
|
+
"pwd": kw.get("pwd", "secret"),
|
|
63
|
+
"host": kw.get("host", "localhost"),
|
|
64
|
+
"port": kw.get("port", 5432),
|
|
65
|
+
"db": kw.get("name", kw.get("db", "app_db")),
|
|
66
|
+
"pool_size": kw.get("pool_size", 10),
|
|
67
|
+
"max": kw.get("max", 20),
|
|
68
|
+
}
|
|
69
|
+
else:
|
|
70
|
+
raise ValueError("kind must be 'sqlite' or 'postgres'")
|
|
71
|
+
|
|
72
|
+
return EngineSpec.from_any(spec)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def prov(
|
|
76
|
+
spec: Union[EngineSpec, EngineCfg, Mapping[str, Any], str, None] = None, **kw: Any
|
|
77
|
+
) -> Provider:
|
|
78
|
+
"""
|
|
79
|
+
Get a lazy Provider (engine+sessionmaker).
|
|
80
|
+
Accepts EngineSpec, EngineCfg (mapping/DSN), or kw fields (collapsed former ctxS).
|
|
81
|
+
"""
|
|
82
|
+
if isinstance(spec, EngineSpec):
|
|
83
|
+
return spec.to_provider()
|
|
84
|
+
return engine_spec(spec, **kw).to_provider()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def engine(
|
|
88
|
+
spec: Union[EngineSpec, EngineCfg, Mapping[str, Any], str, None] = None, **kw: Any
|
|
89
|
+
) -> Engine:
|
|
90
|
+
"""Return an Engine façade for convenience in ad-hoc flows."""
|
|
91
|
+
if isinstance(spec, EngineSpec):
|
|
92
|
+
return Engine(spec)
|
|
93
|
+
return Engine(engine_spec(spec, **kw))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# Convenience helpers (construct EngineCfg mappings directly; no ctxS needed)
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def sqlite_cfg(
|
|
102
|
+
path: Optional[str] = None, *, async_: bool = True, memory: Optional[bool] = None
|
|
103
|
+
) -> EngineCfg:
|
|
104
|
+
return (
|
|
105
|
+
{"kind": "sqlite", "async": async_, "mode": "memory"}
|
|
106
|
+
if (memory or path is None)
|
|
107
|
+
else {"kind": "sqlite", "async": async_, "path": path}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def pg_cfg(
|
|
112
|
+
*,
|
|
113
|
+
async_: bool = False,
|
|
114
|
+
user: str = "app",
|
|
115
|
+
pwd: str = "secret",
|
|
116
|
+
host: str = "localhost",
|
|
117
|
+
port: int = 5432,
|
|
118
|
+
name: str = "app_db",
|
|
119
|
+
pool_size: int = 10,
|
|
120
|
+
max: int = 20,
|
|
121
|
+
) -> EngineCfg:
|
|
122
|
+
return {
|
|
123
|
+
"kind": "postgres",
|
|
124
|
+
"async": async_,
|
|
125
|
+
"user": user,
|
|
126
|
+
"pwd": pwd,
|
|
127
|
+
"host": host,
|
|
128
|
+
"port": port,
|
|
129
|
+
"db": name,
|
|
130
|
+
"pool_size": pool_size,
|
|
131
|
+
"max": max,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def mem(async_: bool = True) -> EngineCfg:
|
|
136
|
+
"""SQLite in-memory (StaticPool) EngineCfg mapping."""
|
|
137
|
+
return {"kind": "sqlite", "async": async_, "mode": "memory"}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def sqlitef(path: str, *, async_: bool = False) -> EngineCfg:
|
|
141
|
+
"""SQLite file EngineCfg mapping."""
|
|
142
|
+
return {"kind": "sqlite", "async": async_, "path": path}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def pg(**kw: Any) -> EngineCfg:
|
|
146
|
+
"""Postgres EngineCfg; set async_=True for asyncpg."""
|
|
147
|
+
return pg_cfg(**kw)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def pga(**kw: Any) -> EngineCfg:
|
|
151
|
+
"""Async Postgres EngineCfg (asyncpg)."""
|
|
152
|
+
kw.setdefault("async_", True)
|
|
153
|
+
return pg_cfg(**kw)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def pgs(**kw: Any) -> EngineCfg:
|
|
157
|
+
"""Sync Postgres EngineCfg (psycopg/pg8000 depending on your builders)."""
|
|
158
|
+
kw.setdefault("async_", False)
|
|
159
|
+
return pg_cfg(**kw)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
# Provider one-liners
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def provider_sqlite_memory(async_: bool = True) -> Provider:
|
|
168
|
+
return engine_spec(kind="sqlite", mode="memory", async_=async_).to_provider()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def provider_sqlite_file(path: str, async_: bool = False) -> Provider:
|
|
172
|
+
return engine_spec(kind="sqlite", path=path, async_=async_).to_provider()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def provider_postgres(
|
|
176
|
+
*,
|
|
177
|
+
async_: bool = False,
|
|
178
|
+
user: str = "app",
|
|
179
|
+
pwd: str = "secret",
|
|
180
|
+
host: str = "localhost",
|
|
181
|
+
port: int = 5432,
|
|
182
|
+
name: str = "app_db",
|
|
183
|
+
pool_size: int = 10,
|
|
184
|
+
max: int = 20,
|
|
185
|
+
) -> Provider:
|
|
186
|
+
return engine_spec(
|
|
187
|
+
kind="postgres",
|
|
188
|
+
async_=async_,
|
|
189
|
+
user=user,
|
|
190
|
+
pwd=pwd,
|
|
191
|
+
host=host,
|
|
192
|
+
port=port,
|
|
193
|
+
name=name,
|
|
194
|
+
pool_size=pool_size,
|
|
195
|
+
max=max,
|
|
196
|
+
).to_provider()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
__all__ = [
|
|
200
|
+
# EngineSpec / Provider / Engine helpers
|
|
201
|
+
"engine_spec",
|
|
202
|
+
"prov",
|
|
203
|
+
"engine",
|
|
204
|
+
# convenience EngineCfg helpers
|
|
205
|
+
"sqlite_cfg",
|
|
206
|
+
"pg_cfg",
|
|
207
|
+
"mem",
|
|
208
|
+
"sqlitef",
|
|
209
|
+
"pg",
|
|
210
|
+
"pga",
|
|
211
|
+
"pgs",
|
|
212
|
+
# direct providers
|
|
213
|
+
"provider_sqlite_memory",
|
|
214
|
+
"provider_sqlite_file",
|
|
215
|
+
"provider_postgres",
|
|
216
|
+
]
|