sqlakit 0.22.11__tar.gz → 0.22.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sqlakit-0.22.11 → sqlakit-0.22.12}/PKG-INFO +4 -3
- {sqlakit-0.22.11 → sqlakit-0.22.12}/README.md +2 -2
- {sqlakit-0.22.11 → sqlakit-0.22.12}/pyproject.toml +3 -2
- {sqlakit-0.22.11 → sqlakit-0.22.12}/pyproject.toml.orig +3 -2
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/_base.py +9 -9
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/_db.py +6 -6
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/_model.py +1 -1
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/_registry.py +2 -2
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/_sql.py +3 -3
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/asyncio/_db.py +6 -6
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/asyncio/_registry.py +2 -2
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/asyncio/orm.py +17 -9
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/asyncio/sql.py +1 -3
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/orm.py +11 -9
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/pytest_plugin.py +4 -4
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/sql.py +1 -3
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/testing.py +2 -2
- {sqlakit-0.22.11 → sqlakit-0.22.12}/LICENSE +0 -0
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/__init__.py +0 -0
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/_cli.py +0 -0
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/_discovery.py +0 -0
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/_linters.py +0 -0
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/_project.py +0 -0
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/_pycharm.py +0 -0
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/_query.py +0 -0
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/_recording.py +0 -0
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/_render.py +0 -0
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/_routing.py +0 -0
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/_sql_formatter.py +0 -0
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/_static.py +0 -0
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/asyncio/__init__.py +0 -0
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/exceptions.py +0 -0
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/py.typed +0 -0
- {sqlakit-0.22.11 → sqlakit-0.22.12}/sqlakit/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlakit
|
|
3
|
-
Version: 0.22.
|
|
3
|
+
Version: 0.22.12
|
|
4
4
|
Summary: A toolkit for SQLAlchemy applications.
|
|
5
5
|
Keywords: sqlalchemy,database,orm,sql,asyncio
|
|
6
6
|
Author: Anton Ruhlov
|
|
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
18
19
|
Classifier: Topic :: Database
|
|
19
20
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
21
|
Classifier: Typing :: Typed
|
|
@@ -387,7 +388,7 @@ there is nothing to await.
|
|
|
387
388
|
## `FastAPI` integration
|
|
388
389
|
|
|
389
390
|
```python
|
|
390
|
-
from collections.abc import
|
|
391
|
+
from collections.abc import AsyncGenerator
|
|
391
392
|
from contextlib import asynccontextmanager
|
|
392
393
|
|
|
393
394
|
from fastapi import FastAPI
|
|
@@ -400,7 +401,7 @@ db = Database("postgresql+psycopg://localhost/app")
|
|
|
400
401
|
|
|
401
402
|
|
|
402
403
|
@asynccontextmanager
|
|
403
|
-
async def lifespan(app: FastAPI) ->
|
|
404
|
+
async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
|
|
404
405
|
yield
|
|
405
406
|
await db.dispose() # close the pool on shutdown
|
|
406
407
|
|
|
@@ -356,7 +356,7 @@ there is nothing to await.
|
|
|
356
356
|
## `FastAPI` integration
|
|
357
357
|
|
|
358
358
|
```python
|
|
359
|
-
from collections.abc import
|
|
359
|
+
from collections.abc import AsyncGenerator
|
|
360
360
|
from contextlib import asynccontextmanager
|
|
361
361
|
|
|
362
362
|
from fastapi import FastAPI
|
|
@@ -369,7 +369,7 @@ db = Database("postgresql+psycopg://localhost/app")
|
|
|
369
369
|
|
|
370
370
|
|
|
371
371
|
@asynccontextmanager
|
|
372
|
-
async def lifespan(app: FastAPI) ->
|
|
372
|
+
async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
|
|
373
373
|
yield
|
|
374
374
|
await db.dispose() # close the pool on shutdown
|
|
375
375
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sqlakit"
|
|
3
|
-
version = "0.22.
|
|
3
|
+
version = "0.22.12"
|
|
4
4
|
description = "A toolkit for SQLAlchemy applications."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -21,6 +21,7 @@ classifiers = [
|
|
|
21
21
|
"Programming Language :: Python :: 3.12",
|
|
22
22
|
"Programming Language :: Python :: 3.13",
|
|
23
23
|
"Programming Language :: Python :: 3.14",
|
|
24
|
+
"Programming Language :: Python :: 3.15",
|
|
24
25
|
"Topic :: Database",
|
|
25
26
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
27
|
"Typing :: Typed",
|
|
@@ -178,7 +179,7 @@ skip = "./.venv,./site,./uv.lock,./.git"
|
|
|
178
179
|
[dependency-groups]
|
|
179
180
|
dev = [
|
|
180
181
|
"pytest>=9.1.1",
|
|
181
|
-
"ty>=0.0.
|
|
182
|
+
"ty>=0.0.87",
|
|
182
183
|
"ruff>=0.16.6",
|
|
183
184
|
"poethepoet>=0.48.0",
|
|
184
185
|
"anyio>=4.14.2",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sqlakit"
|
|
3
|
-
version = "0.22.
|
|
3
|
+
version = "0.22.12"
|
|
4
4
|
description = "A toolkit for SQLAlchemy applications."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -16,6 +16,7 @@ classifiers = [
|
|
|
16
16
|
"Programming Language :: Python :: 3.12",
|
|
17
17
|
"Programming Language :: Python :: 3.13",
|
|
18
18
|
"Programming Language :: Python :: 3.14",
|
|
19
|
+
"Programming Language :: Python :: 3.15",
|
|
19
20
|
"Topic :: Database",
|
|
20
21
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
21
22
|
"Typing :: Typed",
|
|
@@ -54,7 +55,7 @@ default-groups = ["dev", "docs"]
|
|
|
54
55
|
[dependency-groups]
|
|
55
56
|
dev = [
|
|
56
57
|
"pytest>=9.1.1",
|
|
57
|
-
"ty>=0.0.
|
|
58
|
+
"ty>=0.0.87",
|
|
58
59
|
"ruff>=0.16.6",
|
|
59
60
|
"poethepoet>=0.48.0",
|
|
60
61
|
"anyio>=4.14.2",
|
|
@@ -5,7 +5,7 @@ import inspect
|
|
|
5
5
|
import random
|
|
6
6
|
import threading
|
|
7
7
|
import time
|
|
8
|
-
from collections.abc import Callable, Mapping
|
|
8
|
+
from collections.abc import Callable, Generator, Mapping
|
|
9
9
|
from contextlib import ExitStack, contextmanager
|
|
10
10
|
from contextvars import ContextVar
|
|
11
11
|
from dataclasses import dataclass, replace
|
|
@@ -56,7 +56,7 @@ from .exceptions import (
|
|
|
56
56
|
|
|
57
57
|
if TYPE_CHECKING:
|
|
58
58
|
import logging
|
|
59
|
-
from collections.abc import
|
|
59
|
+
from collections.abc import Sequence
|
|
60
60
|
from os import PathLike
|
|
61
61
|
|
|
62
62
|
from sqlalchemy.engine import Engine
|
|
@@ -364,7 +364,7 @@ class BaseDatabase(Generic[ConnectionT, SessionT]):
|
|
|
364
364
|
skip_queries_from: Sequence[str | PathLike[str]] = (),
|
|
365
365
|
into: Recording | None = None,
|
|
366
366
|
send_to: Callable[[Recording], object] | None = None,
|
|
367
|
-
) ->
|
|
367
|
+
) -> Generator[Recording]:
|
|
368
368
|
"""Record the statements of this block, and what they add up to.
|
|
369
369
|
|
|
370
370
|
```python
|
|
@@ -430,7 +430,7 @@ class BaseDatabase(Generic[ConnectionT, SessionT]):
|
|
|
430
430
|
*,
|
|
431
431
|
at_most: int | None = None,
|
|
432
432
|
duplicates: bool = True,
|
|
433
|
-
) ->
|
|
433
|
+
) -> Generator[Recording]:
|
|
434
434
|
"""Assert what the block asks of this database.
|
|
435
435
|
|
|
436
436
|
```python
|
|
@@ -568,7 +568,7 @@ class BaseDatabase(Generic[ConnectionT, SessionT]):
|
|
|
568
568
|
return scope is not None and scope.session is not None
|
|
569
569
|
|
|
570
570
|
@contextmanager
|
|
571
|
-
def unbound(self) ->
|
|
571
|
+
def unbound(self) -> Generator[None]:
|
|
572
572
|
"""Hide the block open around this one, for the code inside to open its own.
|
|
573
573
|
|
|
574
574
|
A test opens a transaction for the whole test, so code that reaches for
|
|
@@ -644,7 +644,7 @@ class BaseDatabase(Generic[ConnectionT, SessionT]):
|
|
|
644
644
|
return outer, savepoint or rollback or bool(outer and outer.savepoint)
|
|
645
645
|
|
|
646
646
|
@contextmanager
|
|
647
|
-
def override(self, db: Self) ->
|
|
647
|
+
def override(self, db: Self) -> Generator[Self]:
|
|
648
648
|
"""Use another database in place of this one until the block ends.
|
|
649
649
|
|
|
650
650
|
```python
|
|
@@ -944,7 +944,7 @@ class _DatabaseRegistryMixin(BaseDatabase[Any, Any], Generic[DatabaseT]):
|
|
|
944
944
|
into: Recording | None = None,
|
|
945
945
|
send_to: Callable[[Recording], object] | None = None,
|
|
946
946
|
using: str | DatabaseT | Sequence[str | DatabaseT] | None = None,
|
|
947
|
-
) ->
|
|
947
|
+
) -> Generator[Recording]:
|
|
948
948
|
"""Record every database this registry has, not the default one alone.
|
|
949
949
|
|
|
950
950
|
```python
|
|
@@ -996,7 +996,7 @@ class _DatabaseRegistryMixin(BaseDatabase[Any, Any], Generic[DatabaseT]):
|
|
|
996
996
|
send_to(together)
|
|
997
997
|
|
|
998
998
|
@contextmanager
|
|
999
|
-
def unbound(self) ->
|
|
999
|
+
def unbound(self) -> Generator[None]:
|
|
1000
1000
|
"""Hide the block open on every database this registry holds."""
|
|
1001
1001
|
with ExitStack() as stack:
|
|
1002
1002
|
for alias in self.aliases:
|
|
@@ -1024,7 +1024,7 @@ class _DatabaseRegistryMixin(BaseDatabase[Any, Any], Generic[DatabaseT]):
|
|
|
1024
1024
|
db: DatabaseT,
|
|
1025
1025
|
*,
|
|
1026
1026
|
alias: str = DEFAULT_ALIAS,
|
|
1027
|
-
) ->
|
|
1027
|
+
) -> Generator[DatabaseT]:
|
|
1028
1028
|
"""Use another database under an alias until the block ends.
|
|
1029
1029
|
|
|
1030
1030
|
```python
|
|
@@ -29,7 +29,7 @@ from ._base import (
|
|
|
29
29
|
from .exceptions import TransactionRolledBackError
|
|
30
30
|
|
|
31
31
|
if TYPE_CHECKING:
|
|
32
|
-
from collections.abc import Callable,
|
|
32
|
+
from collections.abc import Callable, Generator, Sequence
|
|
33
33
|
from types import TracebackType
|
|
34
34
|
|
|
35
35
|
from sqlalchemy.engine import Engine
|
|
@@ -151,7 +151,7 @@ class Database(BaseDatabase[sa.Connection, Session]):
|
|
|
151
151
|
return cast("sa.Connection", scope.connection)
|
|
152
152
|
|
|
153
153
|
@contextmanager
|
|
154
|
-
def connect(self) ->
|
|
154
|
+
def connect(self) -> Generator[sa.Connection]:
|
|
155
155
|
"""Open a connection and bind it, or reuse the one already bound."""
|
|
156
156
|
elsewhere = self._override
|
|
157
157
|
if elsewhere is not None:
|
|
@@ -198,7 +198,7 @@ class Database(BaseDatabase[sa.Connection, Session]):
|
|
|
198
198
|
return autocommit
|
|
199
199
|
|
|
200
200
|
@contextmanager
|
|
201
|
-
def _autocommit(self) ->
|
|
201
|
+
def _autocommit(self) -> Generator[sa.Connection]:
|
|
202
202
|
"""Open a connection in ``AUTOCOMMIT`` and bind it, or join the outer one."""
|
|
203
203
|
elsewhere = self._override
|
|
204
204
|
if elsewhere is not None:
|
|
@@ -328,7 +328,7 @@ class Database(BaseDatabase[sa.Connection, Session]):
|
|
|
328
328
|
return transaction
|
|
329
329
|
|
|
330
330
|
@contextmanager
|
|
331
|
-
def session_factory(self) ->
|
|
331
|
+
def session_factory(self) -> Generator[Session]:
|
|
332
332
|
"""Open a session for the block, and bind it.
|
|
333
333
|
|
|
334
334
|
The session arrives at once, the connection on its first query or
|
|
@@ -365,7 +365,7 @@ class Database(BaseDatabase[sa.Connection, Session]):
|
|
|
365
365
|
*,
|
|
366
366
|
commit: bool = False,
|
|
367
367
|
autocommit: bool = False,
|
|
368
|
-
) ->
|
|
368
|
+
) -> Generator[sa.Connection]:
|
|
369
369
|
"""Bind ``connection`` for the block, ending the session it opened.
|
|
370
370
|
|
|
371
371
|
``commit`` keeps that session's work. Closing a session that isolates
|
|
@@ -389,7 +389,7 @@ class Database(BaseDatabase[sa.Connection, Session]):
|
|
|
389
389
|
metadata: sa.MetaData,
|
|
390
390
|
*,
|
|
391
391
|
tables: Sequence[sa.Table] | None = None,
|
|
392
|
-
) ->
|
|
392
|
+
) -> Generator[None]:
|
|
393
393
|
"""Create these tables here, and drop them when the block ends.
|
|
394
394
|
|
|
395
395
|
A test session opens this once, around everything that needs a schema:
|
|
@@ -53,7 +53,7 @@ DatabaseT = TypeVar("DatabaseT", bound="BaseDatabase[Any, Any]")
|
|
|
53
53
|
class DatabaseSource(Protocol):
|
|
54
54
|
"""The registry an alias is looked up in: the importable one, or a stand-in."""
|
|
55
55
|
|
|
56
|
-
def __getitem__(self, alias: str) -> BaseDatabase[Any, Any]: ...
|
|
56
|
+
def __getitem__(self, alias: str, /) -> BaseDatabase[Any, Any]: ...
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
class DatabaseDescriptor(Generic[DatabaseT]):
|
|
@@ -8,7 +8,7 @@ from ._base import _DatabaseRegistryMixin, default_backoff, late_bound
|
|
|
8
8
|
from ._db import Database, RetryingTransaction
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
|
-
from collections.abc import Callable,
|
|
11
|
+
from collections.abc import Callable, Generator
|
|
12
12
|
|
|
13
13
|
from ._base import RetryOn
|
|
14
14
|
from ._db import Transaction
|
|
@@ -40,7 +40,7 @@ class Databases(_DatabaseRegistryMixin[Database], Database):
|
|
|
40
40
|
def transactions(
|
|
41
41
|
self,
|
|
42
42
|
**arguments: Any, # noqa: ANN401
|
|
43
|
-
) ->
|
|
43
|
+
) -> Generator[None]:
|
|
44
44
|
"""Open a transaction on every database, not the default one alone.
|
|
45
45
|
|
|
46
46
|
A single database needs `transaction(rollback=True)`. A test harness
|
|
@@ -6,7 +6,7 @@ import importlib.util
|
|
|
6
6
|
import inspect
|
|
7
7
|
import itertools
|
|
8
8
|
import re
|
|
9
|
-
from collections.abc import Mapping, Sized
|
|
9
|
+
from collections.abc import Generator, Mapping, Sized
|
|
10
10
|
from contextlib import contextmanager
|
|
11
11
|
from contextvars import ContextVar
|
|
12
12
|
from dataclasses import dataclass, is_dataclass
|
|
@@ -49,7 +49,7 @@ from .exceptions import (
|
|
|
49
49
|
)
|
|
50
50
|
|
|
51
51
|
if TYPE_CHECKING:
|
|
52
|
-
from collections.abc import Callable, Iterable,
|
|
52
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
53
53
|
|
|
54
54
|
from pydantic import BaseModel, TypeAdapter
|
|
55
55
|
from sqlalchemy.sql import Executable
|
|
@@ -86,7 +86,7 @@ _calls_kept: ContextVar[bool] = ContextVar("sqlakit.calls_kept", default=False)
|
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
@contextmanager
|
|
89
|
-
def calls_kept() ->
|
|
89
|
+
def calls_kept() -> Generator[None]:
|
|
90
90
|
"""Write a call that cannot be made with the values at hand as the call itself.
|
|
91
91
|
|
|
92
92
|
An editor shows a template rendered with made-up values, or with none: a
|
|
@@ -37,7 +37,7 @@ from sqlakit._base import (
|
|
|
37
37
|
from sqlakit.exceptions import MissingConnectionError, TransactionRolledBackError
|
|
38
38
|
|
|
39
39
|
if TYPE_CHECKING:
|
|
40
|
-
from collections.abc import
|
|
40
|
+
from collections.abc import AsyncGenerator, Callable, Coroutine, Sequence
|
|
41
41
|
from types import TracebackType
|
|
42
42
|
|
|
43
43
|
from sqlalchemy.ext.asyncio import AsyncTransaction
|
|
@@ -187,7 +187,7 @@ class Database(BaseDatabase[AsyncConnection, AsyncSession]):
|
|
|
187
187
|
return cast("AsyncConnection", scope.connection)
|
|
188
188
|
|
|
189
189
|
@asynccontextmanager
|
|
190
|
-
async def connect(self) ->
|
|
190
|
+
async def connect(self) -> AsyncGenerator[AsyncConnection]:
|
|
191
191
|
"""Open a connection and bind it, or reuse the one already bound."""
|
|
192
192
|
elsewhere = self._override
|
|
193
193
|
if elsewhere is not None:
|
|
@@ -234,7 +234,7 @@ class Database(BaseDatabase[AsyncConnection, AsyncSession]):
|
|
|
234
234
|
return autocommit
|
|
235
235
|
|
|
236
236
|
@asynccontextmanager
|
|
237
|
-
async def _autocommit(self) ->
|
|
237
|
+
async def _autocommit(self) -> AsyncGenerator[AsyncConnection]:
|
|
238
238
|
"""Open a connection in ``AUTOCOMMIT`` and bind it, or join the outer one."""
|
|
239
239
|
elsewhere = self._override
|
|
240
240
|
if elsewhere is not None:
|
|
@@ -364,7 +364,7 @@ class Database(BaseDatabase[AsyncConnection, AsyncSession]):
|
|
|
364
364
|
return transaction
|
|
365
365
|
|
|
366
366
|
@asynccontextmanager
|
|
367
|
-
async def session_factory(self) ->
|
|
367
|
+
async def session_factory(self) -> AsyncGenerator[AsyncSession]:
|
|
368
368
|
"""Open a session for the block, and bind it.
|
|
369
369
|
|
|
370
370
|
The session arrives at once, the connection on its first query or
|
|
@@ -401,7 +401,7 @@ class Database(BaseDatabase[AsyncConnection, AsyncSession]):
|
|
|
401
401
|
*,
|
|
402
402
|
commit: bool = False,
|
|
403
403
|
autocommit: bool = False,
|
|
404
|
-
) ->
|
|
404
|
+
) -> AsyncGenerator[AsyncConnection]:
|
|
405
405
|
"""Bind ``connection`` for the block, ending the session it opened.
|
|
406
406
|
|
|
407
407
|
``commit`` keeps that session's work. Closing a session that isolates
|
|
@@ -425,7 +425,7 @@ class Database(BaseDatabase[AsyncConnection, AsyncSession]):
|
|
|
425
425
|
metadata: sa.MetaData,
|
|
426
426
|
*,
|
|
427
427
|
tables: Sequence[sa.Table] | None = None,
|
|
428
|
-
) ->
|
|
428
|
+
) -> AsyncGenerator[None]:
|
|
429
429
|
"""Create these tables here, and drop them when the block ends.
|
|
430
430
|
|
|
431
431
|
A test session opens this once, around everything that needs a schema:
|
|
@@ -9,7 +9,7 @@ from sqlakit._base import _DatabaseRegistryMixin, default_backoff, late_bound
|
|
|
9
9
|
from ._db import Database, RetryingTransaction
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
|
-
from collections.abc import
|
|
12
|
+
from collections.abc import AsyncGenerator, Callable
|
|
13
13
|
|
|
14
14
|
from sqlakit._base import RetryOn
|
|
15
15
|
|
|
@@ -41,7 +41,7 @@ class Databases(_DatabaseRegistryMixin[Database], Database):
|
|
|
41
41
|
async def transactions(
|
|
42
42
|
self,
|
|
43
43
|
**arguments: Any, # noqa: ANN401
|
|
44
|
-
) ->
|
|
44
|
+
) -> AsyncGenerator[None]:
|
|
45
45
|
"""Open a transaction on every database, not the default one alone.
|
|
46
46
|
|
|
47
47
|
A test harness with more than one database needs this, where a single
|
|
@@ -42,10 +42,16 @@ from sqlakit._query import (
|
|
|
42
42
|
from sqlakit.exceptions import InstanceNotFoundError
|
|
43
43
|
|
|
44
44
|
from ._db import Database
|
|
45
|
-
from ._registry import
|
|
45
|
+
from ._registry import db
|
|
46
46
|
|
|
47
47
|
if TYPE_CHECKING:
|
|
48
|
-
from collections.abc import
|
|
48
|
+
from collections.abc import (
|
|
49
|
+
AsyncGenerator,
|
|
50
|
+
AsyncIterator,
|
|
51
|
+
Iterable,
|
|
52
|
+
Mapping,
|
|
53
|
+
Sequence,
|
|
54
|
+
)
|
|
49
55
|
|
|
50
56
|
from sqlalchemy.engine import CursorResult, Result, ScalarResult
|
|
51
57
|
from sqlalchemy.orm import InstrumentedAttribute
|
|
@@ -611,8 +617,8 @@ class QueryDescriptor(Generic[QueryT]):
|
|
|
611
617
|
def __init__(self, query_class: type[QueryT]) -> None:
|
|
612
618
|
self.query_class = query_class
|
|
613
619
|
|
|
614
|
-
# `
|
|
615
|
-
#
|
|
620
|
+
# `QueryDescriptor(Query)` reads as a query of the model it is read from,
|
|
621
|
+
# any other query class as itself.
|
|
616
622
|
@overload
|
|
617
623
|
def __get__(
|
|
618
624
|
self: QueryDescriptor[Query[Any]],
|
|
@@ -644,16 +650,18 @@ class ModelMixin(BaseModel[Database]):
|
|
|
644
650
|
[`set_db`][sqlakit.asyncio.orm.ModelMixin.set_db].
|
|
645
651
|
"""
|
|
646
652
|
|
|
647
|
-
__db__
|
|
648
|
-
__dbs__
|
|
653
|
+
__db__ = DEFAULT_ALIAS
|
|
654
|
+
__dbs__ = db
|
|
649
655
|
|
|
650
|
-
|
|
656
|
+
# Not annotated: a model's `query = UserQuery.as_descriptor()` would take
|
|
657
|
+
# the declared type and read as `Query[User]`.
|
|
658
|
+
query = QueryDescriptor(Query)
|
|
651
659
|
|
|
652
660
|
db: ClassVar[DatabaseDescriptor[Database]] = DatabaseDescriptor()
|
|
653
661
|
|
|
654
662
|
@classmethod
|
|
655
663
|
@asynccontextmanager
|
|
656
|
-
async def provisioned_tables(cls, alias: str | None = None) ->
|
|
664
|
+
async def provisioned_tables(cls, alias: str | None = None) -> AsyncGenerator[None]:
|
|
657
665
|
"""Create the tables that belong on this model's database, and drop them after.
|
|
658
666
|
|
|
659
667
|
A test session opens this once, around everything that needs a schema:
|
|
@@ -668,7 +676,7 @@ class ModelMixin(BaseModel[Database]):
|
|
|
668
676
|
With models on more than one database, name the alias, and each database
|
|
669
677
|
gets the tables of the models pointed at it.
|
|
670
678
|
"""
|
|
671
|
-
db = cls.db if alias is None else cls.
|
|
679
|
+
db = cls.db if alias is None else cls.dbs[alias]
|
|
672
680
|
async with db.provisioned_tables(cls.metadata, tables=tables_for(cls, db)):
|
|
673
681
|
yield
|
|
674
682
|
|
|
@@ -256,9 +256,7 @@ class SQLQuery(SQLRows[sa.Row[Any]]):
|
|
|
256
256
|
|
|
257
257
|
"""
|
|
258
258
|
require_pydantic()
|
|
259
|
-
return
|
|
260
|
-
"SQLRows[OtherT]", self._as(SQLRows, type_=type_, validation=validation)
|
|
261
|
-
)
|
|
259
|
+
return self._as(SQLRows, type_=type_, validation=validation)
|
|
262
260
|
|
|
263
261
|
def scalars(self) -> SQLRows[Any]:
|
|
264
262
|
"""Read the first column of each row instead of whole rows.
|
|
@@ -40,11 +40,11 @@ from ._query import (
|
|
|
40
40
|
orderable,
|
|
41
41
|
ordered,
|
|
42
42
|
)
|
|
43
|
-
from ._registry import
|
|
43
|
+
from ._registry import db
|
|
44
44
|
from .exceptions import InstanceNotFoundError
|
|
45
45
|
|
|
46
46
|
if TYPE_CHECKING:
|
|
47
|
-
from collections.abc import Iterable, Iterator, Mapping, Sequence
|
|
47
|
+
from collections.abc import Generator, Iterable, Iterator, Mapping, Sequence
|
|
48
48
|
|
|
49
49
|
from sqlalchemy.engine import CursorResult, Result, ScalarResult
|
|
50
50
|
from sqlalchemy.orm import InstrumentedAttribute
|
|
@@ -593,8 +593,8 @@ class QueryDescriptor(Generic[QueryT]):
|
|
|
593
593
|
def __init__(self, query_class: type[QueryT]) -> None:
|
|
594
594
|
self.query_class = query_class
|
|
595
595
|
|
|
596
|
-
# `
|
|
597
|
-
#
|
|
596
|
+
# `QueryDescriptor(Query)` reads as a query of the model it is read from,
|
|
597
|
+
# any other query class as itself.
|
|
598
598
|
@overload
|
|
599
599
|
def __get__(
|
|
600
600
|
self: QueryDescriptor[Query[Any]],
|
|
@@ -625,16 +625,18 @@ class ModelMixin(BaseModel[Database]):
|
|
|
625
625
|
importable registry, or one set with [`set_db`][sqlakit.orm.ModelMixin.set_db].
|
|
626
626
|
"""
|
|
627
627
|
|
|
628
|
-
__db__
|
|
629
|
-
__dbs__
|
|
628
|
+
__db__ = DEFAULT_ALIAS
|
|
629
|
+
__dbs__ = db
|
|
630
630
|
|
|
631
|
-
|
|
631
|
+
# Not annotated: a model's `query = UserQuery.as_descriptor()` would take
|
|
632
|
+
# the declared type and read as `Query[User]`.
|
|
633
|
+
query = QueryDescriptor(Query)
|
|
632
634
|
|
|
633
635
|
db: ClassVar[DatabaseDescriptor[Database]] = DatabaseDescriptor()
|
|
634
636
|
|
|
635
637
|
@classmethod
|
|
636
638
|
@contextmanager
|
|
637
|
-
def provisioned_tables(cls, alias: str | None = None) ->
|
|
639
|
+
def provisioned_tables(cls, alias: str | None = None) -> Generator[None]:
|
|
638
640
|
"""Create the tables that belong on this model's database, and drop them after.
|
|
639
641
|
|
|
640
642
|
A test session opens this once, around everything that needs a schema:
|
|
@@ -649,7 +651,7 @@ class ModelMixin(BaseModel[Database]):
|
|
|
649
651
|
With models on more than one database, name the alias, and each database
|
|
650
652
|
gets the tables of the models pointed at it.
|
|
651
653
|
"""
|
|
652
|
-
db = cls.db if alias is None else cls.
|
|
654
|
+
db = cls.db if alias is None else cls.dbs[alias]
|
|
653
655
|
with db.provisioned_tables(cls.metadata, tables=tables_for(cls, db)):
|
|
654
656
|
yield
|
|
655
657
|
|
|
@@ -55,7 +55,7 @@ from ._registry import db as importable_db
|
|
|
55
55
|
from .exceptions import UnknownDatabaseError
|
|
56
56
|
|
|
57
57
|
if TYPE_CHECKING:
|
|
58
|
-
from collections.abc import AsyncIterator, Iterator, Sequence
|
|
58
|
+
from collections.abc import AsyncIterator, Generator, Iterator, Sequence
|
|
59
59
|
|
|
60
60
|
import sqlalchemy as sa
|
|
61
61
|
|
|
@@ -346,7 +346,7 @@ def assert_queries(sqlakit_db: Any) -> AssertQueries: # noqa: ANN401
|
|
|
346
346
|
at_most: int | None = None,
|
|
347
347
|
duplicates: bool = True,
|
|
348
348
|
using: Any = None, # noqa: ANN401
|
|
349
|
-
) ->
|
|
349
|
+
) -> Generator[Recording]:
|
|
350
350
|
require_expectation(count, at_most, duplicates)
|
|
351
351
|
recording = Recording()
|
|
352
352
|
with ExitStack() as stack:
|
|
@@ -402,7 +402,7 @@ async def _sqlakit_async_transaction(
|
|
|
402
402
|
|
|
403
403
|
|
|
404
404
|
@contextmanager
|
|
405
|
-
def _reported(request: pytest.FixtureRequest, db: Any) ->
|
|
405
|
+
def _reported(request: pytest.FixtureRequest, db: Any) -> Generator[None]: # noqa: ANN401
|
|
406
406
|
"""Record what the test runs, for the report, when one was asked for.
|
|
407
407
|
|
|
408
408
|
The recording is named after the test and filed under the file it lives in,
|
|
@@ -435,7 +435,7 @@ def _named(node: pytest.Item) -> str:
|
|
|
435
435
|
|
|
436
436
|
|
|
437
437
|
@contextmanager
|
|
438
|
-
def _entered(blocks: list[Any]) ->
|
|
438
|
+
def _entered(blocks: list[Any]) -> Generator[None]:
|
|
439
439
|
"""Hold these blocks open, awaiting the ones that need it.
|
|
440
440
|
|
|
441
441
|
An `asyncio` database builds its schema in a coroutine, and this fixture is
|
|
@@ -246,9 +246,7 @@ class SQLQuery(SQLRows[sa.Row[Any]]):
|
|
|
246
246
|
|
|
247
247
|
"""
|
|
248
248
|
require_pydantic()
|
|
249
|
-
return
|
|
250
|
-
"SQLRows[OtherT]", self._as(SQLRows, type_=type_, validation=validation)
|
|
251
|
-
)
|
|
249
|
+
return self._as(SQLRows, type_=type_, validation=validation)
|
|
252
250
|
|
|
253
251
|
def scalars(self) -> SQLRows[Any]:
|
|
254
252
|
"""Read the first column of each row instead of whole rows.
|
|
@@ -12,7 +12,7 @@ from .exceptions import UnknownDatabaseError
|
|
|
12
12
|
from .types import AssertQueries
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
|
-
from collections.abc import
|
|
15
|
+
from collections.abc import Generator
|
|
16
16
|
|
|
17
17
|
__all__ = ["AssertQueries", "assert_queries"]
|
|
18
18
|
|
|
@@ -24,7 +24,7 @@ def assert_queries(
|
|
|
24
24
|
at_most: int | None = None,
|
|
25
25
|
duplicates: bool = True,
|
|
26
26
|
using: str | Any = None, # noqa: ANN401
|
|
27
|
-
) ->
|
|
27
|
+
) -> Generator[Recording]:
|
|
28
28
|
"""Assert what the block asks of the database.
|
|
29
29
|
|
|
30
30
|
```python
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|