sqlakit 0.10.9__tar.gz → 0.11.1__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.10.9 → sqlakit-0.11.1}/PKG-INFO +2 -1
- {sqlakit-0.10.9 → sqlakit-0.11.1}/pyproject.toml +8 -5
- {sqlakit-0.10.9 → sqlakit-0.11.1}/pyproject.toml.orig +5 -4
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/_base.py +66 -1
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/_model.py +1 -1
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/exceptions.py +3 -3
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/pytest_plugin.py +14 -14
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/types.py +11 -5
- {sqlakit-0.10.9 → sqlakit-0.11.1}/LICENSE +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/README.md +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/__init__.py +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/_cli.py +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/_db.py +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/_debugserver.py +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/_discovery.py +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/_query.py +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/_recording.py +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/_registry.py +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/_routing.py +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/_sql.py +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/asyncio/__init__.py +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/asyncio/_db.py +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/asyncio/_registry.py +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/asyncio/orm.py +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/asyncio/sql.py +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/debugserver.html +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/orm.py +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/py.typed +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/sql.py +0 -0
- {sqlakit-0.10.9 → sqlakit-0.11.1}/sqlakit/testing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlakit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.1
|
|
4
4
|
Summary: A toolkit for SQLAlchemy applications.
|
|
5
5
|
Keywords: sqlalchemy,database,orm,sql,asyncio
|
|
6
6
|
Author: Anton Ruhlov
|
|
@@ -19,6 +19,7 @@ Classifier: Topic :: Database
|
|
|
19
19
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
20
|
Classifier: Typing :: Typed
|
|
21
21
|
Requires-Dist: sqlalchemy>=2.0.22
|
|
22
|
+
Requires-Dist: typing-extensions>=4.13
|
|
22
23
|
Requires-Dist: sqlalchemy[asyncio]>=2.0.22 ; extra == 'asyncio'
|
|
23
24
|
Requires-Dist: sqlparse>=0.6.0 ; extra == 'debug'
|
|
24
25
|
Requires-Dist: jinja2sql>=0.11.0 ; extra == 'sql'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sqlakit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.11.1"
|
|
4
4
|
description = "A toolkit for SQLAlchemy applications."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -26,7 +26,10 @@ classifiers = [
|
|
|
26
26
|
"Typing :: Typed",
|
|
27
27
|
]
|
|
28
28
|
requires-python = ">=3.11"
|
|
29
|
-
dependencies = [
|
|
29
|
+
dependencies = [
|
|
30
|
+
"sqlalchemy>=2.0.22",
|
|
31
|
+
"typing-extensions>=4.13",
|
|
32
|
+
]
|
|
30
33
|
|
|
31
34
|
[[project.authors]]
|
|
32
35
|
name = "Anton Ruhlov"
|
|
@@ -180,8 +183,8 @@ skip = "./.venv,./site,./uv.lock,./.git,./debugserver/node_modules,./debugserver
|
|
|
180
183
|
[dependency-groups]
|
|
181
184
|
dev = [
|
|
182
185
|
"pytest>=9.1.1",
|
|
183
|
-
"ty>=0.0.
|
|
184
|
-
"ruff>=0.16.
|
|
186
|
+
"ty>=0.0.79",
|
|
187
|
+
"ruff>=0.16.6",
|
|
185
188
|
"poethepoet>=0.48.0",
|
|
186
189
|
"anyio>=4.14.2",
|
|
187
190
|
"trio>=0.34.0",
|
|
@@ -208,5 +211,5 @@ docs = [
|
|
|
208
211
|
"mkdocs>=1.6.1,<2",
|
|
209
212
|
"mkdocs-material>=9.7.7",
|
|
210
213
|
"mkdocstrings[python]>=1.0.6",
|
|
211
|
-
"ruff>=0.16.
|
|
214
|
+
"ruff>=0.16.6",
|
|
212
215
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sqlakit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.11.1"
|
|
4
4
|
description = "A toolkit for SQLAlchemy applications."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -23,6 +23,7 @@ classifiers = [
|
|
|
23
23
|
requires-python = ">=3.11"
|
|
24
24
|
dependencies = [
|
|
25
25
|
"sqlalchemy>=2.0.22",
|
|
26
|
+
"typing-extensions>=4.13",
|
|
26
27
|
]
|
|
27
28
|
|
|
28
29
|
[project.scripts]
|
|
@@ -60,8 +61,8 @@ default-groups = ["dev", "docs"]
|
|
|
60
61
|
[dependency-groups]
|
|
61
62
|
dev = [
|
|
62
63
|
"pytest>=9.1.1",
|
|
63
|
-
"ty>=0.0.
|
|
64
|
-
"ruff>=0.16.
|
|
64
|
+
"ty>=0.0.79",
|
|
65
|
+
"ruff>=0.16.6",
|
|
65
66
|
"poethepoet>=0.48.0",
|
|
66
67
|
"anyio>=4.14.2",
|
|
67
68
|
"trio>=0.34.0",
|
|
@@ -88,7 +89,7 @@ docs = [
|
|
|
88
89
|
"mkdocs>=1.6.1,<2",
|
|
89
90
|
"mkdocs-material>=9.7.7",
|
|
90
91
|
"mkdocstrings[python]>=1.0.6",
|
|
91
|
-
"ruff>=0.16.
|
|
92
|
+
"ruff>=0.16.6",
|
|
92
93
|
]
|
|
93
94
|
|
|
94
95
|
[tool.pytest.ini_options]
|
|
@@ -470,6 +470,32 @@ class BaseDatabase(Generic[ConnectionT, SessionT]):
|
|
|
470
470
|
"""
|
|
471
471
|
return self._outer.get(None) is not None
|
|
472
472
|
|
|
473
|
+
@property
|
|
474
|
+
def aliases(self) -> tuple[str, ...]:
|
|
475
|
+
"""The names this database goes by, which for one database is its own.
|
|
476
|
+
|
|
477
|
+
A registry has one for each database it holds. Both carry this, so code
|
|
478
|
+
that takes either does not have to ask which it was given.
|
|
479
|
+
"""
|
|
480
|
+
return (self._name,)
|
|
481
|
+
|
|
482
|
+
def __getitem__(self, alias: str) -> Any: # noqa: ANN401
|
|
483
|
+
"""Return this database, under the name it carries.
|
|
484
|
+
|
|
485
|
+
`Any` rather than `Self`, so a registry can narrow it to the databases
|
|
486
|
+
it holds.
|
|
487
|
+
|
|
488
|
+
Raises:
|
|
489
|
+
UnknownDatabaseError: if the alias is another database's.
|
|
490
|
+
|
|
491
|
+
"""
|
|
492
|
+
if alias == self._name:
|
|
493
|
+
return self
|
|
494
|
+
raise UnknownDatabaseError(alias, self.aliases)
|
|
495
|
+
|
|
496
|
+
def __contains__(self, alias: str) -> bool:
|
|
497
|
+
return alias == self._name
|
|
498
|
+
|
|
473
499
|
def in_session(self) -> bool:
|
|
474
500
|
"""Whether a session is open in the current context.
|
|
475
501
|
|
|
@@ -910,11 +936,15 @@ class _DatabaseRegistryMixin(BaseDatabase[Any, Any], Generic[DatabaseT]):
|
|
|
910
936
|
opened with `using()` stands in for the default database.
|
|
911
937
|
"""
|
|
912
938
|
placement = self._routed(model) or model.__db__
|
|
939
|
+
override = self._using.get()
|
|
913
940
|
if isinstance(placement, str):
|
|
914
|
-
override = self._using.get()
|
|
915
941
|
if override is not None and placement == DEFAULT_ALIAS:
|
|
916
942
|
placement = override
|
|
917
943
|
return self[placement]
|
|
944
|
+
# A model pinned to the database itself follows `using()` as one on the
|
|
945
|
+
# default alias does, when that database is the one being stood in for.
|
|
946
|
+
if override is not None and self[DEFAULT_ALIAS] is placement:
|
|
947
|
+
return self[override]
|
|
918
948
|
return placement
|
|
919
949
|
|
|
920
950
|
def _routed(self, model: type[Any]) -> str | None:
|
|
@@ -940,6 +970,41 @@ class _DatabaseRegistryMixin(BaseDatabase[Any, Any], Generic[DatabaseT]):
|
|
|
940
970
|
"""Whether the default database is this registry, `configure` having built it."""
|
|
941
971
|
return "url" in self.__dict__
|
|
942
972
|
|
|
973
|
+
if not TYPE_CHECKING:
|
|
974
|
+
# Hidden from type checkers, which keep reading these off `Database`
|
|
975
|
+
# and its asyncio twin, signatures and all.
|
|
976
|
+
def _proxy(name: str, *, attribute: bool = False) -> Any: # noqa: ANN401, N805
|
|
977
|
+
"""Proxy to the database this registry holds, or call its own.
|
|
978
|
+
|
|
979
|
+
A registry handed a default proxies to that database. One that
|
|
980
|
+
`configure` built calls what it inherits, which `super()` reaches.
|
|
981
|
+
"""
|
|
982
|
+
|
|
983
|
+
def reach(self: Any, *args: Any, **kwargs: Any) -> Any: # noqa: ANN401
|
|
984
|
+
held = self._default
|
|
985
|
+
found = (
|
|
986
|
+
getattr(held, name) if held is not None else getattr(super(), name)
|
|
987
|
+
)
|
|
988
|
+
return found if attribute else found(*args, **kwargs)
|
|
989
|
+
|
|
990
|
+
return property(reach) if attribute else reach
|
|
991
|
+
|
|
992
|
+
connection = _proxy("connection", attribute=True)
|
|
993
|
+
engine = _proxy("engine", attribute=True)
|
|
994
|
+
session = _proxy("session", attribute=True)
|
|
995
|
+
sql = _proxy("sql", attribute=True)
|
|
996
|
+
assert_queries = _proxy("assert_queries")
|
|
997
|
+
autocommit = _proxy("autocommit")
|
|
998
|
+
connect = _proxy("connect")
|
|
999
|
+
in_session = _proxy("in_session")
|
|
1000
|
+
in_transaction = _proxy("in_transaction")
|
|
1001
|
+
ping = _proxy("ping")
|
|
1002
|
+
provisioned_tables = _proxy("provisioned_tables")
|
|
1003
|
+
query = _proxy("query")
|
|
1004
|
+
session_factory = _proxy("session_factory")
|
|
1005
|
+
transaction = _proxy("transaction")
|
|
1006
|
+
del _proxy
|
|
1007
|
+
|
|
943
1008
|
@overload
|
|
944
1009
|
def configure(
|
|
945
1010
|
self,
|
|
@@ -145,7 +145,7 @@ class BaseModel(Generic[DatabaseT]):
|
|
|
145
145
|
Base.register_db(Database(DB_URL))
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
The registry
|
|
148
|
+
The registry holds it either way, so `dbs["default"]`,
|
|
149
149
|
`dbs.transactions()` and `using()` all reach what was registered.
|
|
150
150
|
[`set_db`][sqlakit.orm.ModelMixin.set_db] is the other way to name a
|
|
151
151
|
database, and it leaves the registry out of it.
|
|
@@ -86,10 +86,10 @@ class RetryNotSupportedError(SQLAKitError, TypeError):
|
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
REGISTERED_DEFAULT = (
|
|
89
|
-
"This registry did not build the default database, it was registered
|
|
90
|
-
"
|
|
89
|
+
"This registry did not build the default database, it was registered, so "
|
|
90
|
+
"the settings it was built from live on it. Read them from `db['default']`."
|
|
91
91
|
)
|
|
92
|
-
"""What a registry says when
|
|
92
|
+
"""What a registry says when asked for settings the database it holds carries."""
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
class DatabaseNotConfiguredError(SQLAKitError, RuntimeError):
|
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
"""The `db` marker, and the fixtures behind it.
|
|
2
2
|
|
|
3
|
-
Installed with the library, so a project
|
|
4
|
-
|
|
3
|
+
Installed with the library, so a project says which models it has and nothing
|
|
4
|
+
else:
|
|
5
5
|
|
|
6
6
|
```python title="conftest.py"
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
|
-
from app.db import db
|
|
10
9
|
from app.models import Model
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
@pytest.fixture(scope="session")
|
|
14
|
-
def
|
|
15
|
-
return
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@pytest.fixture(scope="session")
|
|
19
|
-
def sqlakit_metadata() -> sa.MetaData:
|
|
20
|
-
return Model.metadata
|
|
13
|
+
def sqlakit_base() -> type[Model]:
|
|
14
|
+
return Model
|
|
21
15
|
```
|
|
22
16
|
|
|
17
|
+
The database comes from the base: the registry its models look their aliases
|
|
18
|
+
up in, or the one they were given in person. A project with no model layer
|
|
19
|
+
defines `sqlakit_db` and `sqlakit_metadata` instead.
|
|
20
|
+
|
|
23
21
|
A test marked `db` runs in a transaction that rolls back, on every database.
|
|
24
22
|
`using` narrows that to the ones a test works on:
|
|
25
23
|
|
|
@@ -361,7 +359,9 @@ def _schema_blocks(
|
|
|
361
359
|
database to put it on.
|
|
362
360
|
"""
|
|
363
361
|
if base is not None:
|
|
364
|
-
|
|
362
|
+
# A database of its own is where the models are pinned, and the alias
|
|
363
|
+
# a registry knows it by is not how the base reaches it.
|
|
364
|
+
aliases = db.aliases if hasattr(db, "transactions") else (None,)
|
|
365
365
|
return [base.provisioned_tables(alias) for alias in aliases]
|
|
366
366
|
if metadata is not None:
|
|
367
367
|
return [db.provisioned_tables(metadata)]
|
|
@@ -393,11 +393,11 @@ def _rolled_back(db: Any, using: tuple[Any, ...]) -> list[Any]: # noqa: ANN401
|
|
|
393
393
|
a connection to each in the tests that read one.
|
|
394
394
|
"""
|
|
395
395
|
if not using:
|
|
396
|
-
# A registry
|
|
397
|
-
#
|
|
396
|
+
# A registry opens every database it holds, and one database opens
|
|
397
|
+
# itself. `transactions` is the one a registry has.
|
|
398
398
|
return [
|
|
399
399
|
db.transactions(rollback=True)
|
|
400
|
-
if hasattr(db, "
|
|
400
|
+
if hasattr(db, "transactions")
|
|
401
401
|
else db.transaction(rollback=True)
|
|
402
402
|
]
|
|
403
403
|
return [
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, Any, Literal, TypeAlias
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Literal, TypeAlias
|
|
4
|
+
|
|
5
|
+
from typing_extensions import TypedDict
|
|
4
6
|
|
|
5
7
|
if TYPE_CHECKING:
|
|
6
8
|
from collections.abc import Callable, Mapping, Sequence
|
|
@@ -27,8 +29,11 @@ TemplatesLike: TypeAlias = "str | Path | Sequence[str | Path] | Templates"
|
|
|
27
29
|
"""Where a database's SQL templates are: a path, several, or the object."""
|
|
28
30
|
|
|
29
31
|
|
|
30
|
-
class EngineArgs(TypedDict, total=False):
|
|
31
|
-
"""Keyword arguments accepted by [`sqlalchemy.create_engine`](https://docs.sqlalchemy.org/en/20/core/engines.html#sqlalchemy.create_engine).
|
|
32
|
+
class EngineArgs(TypedDict, total=False, extra_items=Any):
|
|
33
|
+
"""Keyword arguments accepted by [`sqlalchemy.create_engine`](https://docs.sqlalchemy.org/en/20/core/engines.html#sqlalchemy.create_engine).
|
|
34
|
+
|
|
35
|
+
The portable ones are listed. A dialect keyword is allowed too.
|
|
36
|
+
"""
|
|
32
37
|
|
|
33
38
|
connect_args: dict[str, Any]
|
|
34
39
|
echo: bool | Literal["debug"]
|
|
@@ -61,10 +66,11 @@ class EngineArgs(TypedDict, total=False):
|
|
|
61
66
|
use_insertmanyvalues: bool
|
|
62
67
|
|
|
63
68
|
|
|
64
|
-
class SessionArgs(TypedDict, total=False):
|
|
69
|
+
class SessionArgs(TypedDict, total=False, extra_items=Any):
|
|
65
70
|
"""Keyword arguments accepted by `sqlalchemy.orm.sessionmaker`.
|
|
66
71
|
|
|
67
|
-
No ``bind``: sessions bind to the connection of the surrounding block.
|
|
72
|
+
No ``bind``: sessions bind to the connection of the surrounding block. A
|
|
73
|
+
keyword this list does not have is allowed too.
|
|
68
74
|
"""
|
|
69
75
|
|
|
70
76
|
autobegin: bool
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|