sqlakit 0.9.0__tar.gz → 0.9.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.9.0 → sqlakit-0.9.1}/PKG-INFO +1 -1
- {sqlakit-0.9.0 → sqlakit-0.9.1}/pyproject.toml +1 -1
- {sqlakit-0.9.0 → sqlakit-0.9.1}/pyproject.toml.orig +1 -1
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/_base.py +66 -19
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/_model.py +9 -7
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/_registry.py +3 -1
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/asyncio/_registry.py +3 -1
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/exceptions.py +11 -3
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/pytest_plugin.py +11 -2
- {sqlakit-0.9.0 → sqlakit-0.9.1}/LICENSE +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/README.md +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/__init__.py +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/_cli.py +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/_db.py +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/_debugserver.py +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/_discovery.py +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/_query.py +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/_recording.py +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/_routing.py +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/_sql.py +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/asyncio/__init__.py +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/asyncio/_db.py +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/asyncio/orm.py +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/asyncio/sql.py +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/debugserver.html +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/orm.py +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/py.typed +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/sql.py +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/testing.py +0 -0
- {sqlakit-0.9.0 → sqlakit-0.9.1}/sqlakit/types.py +0 -0
|
@@ -39,6 +39,7 @@ from ._recording import (
|
|
|
39
39
|
from ._routing import Router, as_router
|
|
40
40
|
from .exceptions import (
|
|
41
41
|
DEFAULT_ALIAS,
|
|
42
|
+
REGISTERED_DEFAULT,
|
|
42
43
|
AliasInUseError,
|
|
43
44
|
ConflictingDatabaseUrlError,
|
|
44
45
|
DatabaseAlreadyConfiguredError,
|
|
@@ -703,6 +704,7 @@ class _DatabaseRegistryMixin(BaseDatabase[Any, Any], Generic[DatabaseT]):
|
|
|
703
704
|
|
|
704
705
|
def __init__(self) -> None:
|
|
705
706
|
"""Leave everything to [`configure`][sqlakit.Databases.configure]."""
|
|
707
|
+
self._default: DatabaseT | None = None
|
|
706
708
|
self._aliased: dict[str, DatabaseT] = {}
|
|
707
709
|
self._routers: tuple[Any, ...] = ()
|
|
708
710
|
self._using: ContextVar[str | None] = ContextVar(
|
|
@@ -717,14 +719,15 @@ class _DatabaseRegistryMixin(BaseDatabase[Any, Any], Generic[DatabaseT]):
|
|
|
717
719
|
def __getitem__(self, alias: str) -> Self | DatabaseT:
|
|
718
720
|
"""Return the database configured as ``alias``.
|
|
719
721
|
|
|
720
|
-
``db["default"]`` is
|
|
722
|
+
``db["default"]`` is what the code reaches without an alias: this
|
|
723
|
+
registry, or the database `register` was given for that name.
|
|
721
724
|
|
|
722
725
|
Raises:
|
|
723
726
|
UnknownDatabaseError: if nothing is configured under that alias.
|
|
724
727
|
|
|
725
728
|
"""
|
|
726
729
|
if alias == DEFAULT_ALIAS:
|
|
727
|
-
return self
|
|
730
|
+
return self if self._default is None else self._default
|
|
728
731
|
try:
|
|
729
732
|
return self._aliased[alias]
|
|
730
733
|
except KeyError:
|
|
@@ -744,16 +747,25 @@ class _DatabaseRegistryMixin(BaseDatabase[Any, Any], Generic[DatabaseT]):
|
|
|
744
747
|
db.register("shard-7", Database(SHARD_URL))
|
|
745
748
|
```
|
|
746
749
|
|
|
750
|
+
`default` is the alias the code reaches without naming one, and a
|
|
751
|
+
database registered under it stands where `configure` would have built
|
|
752
|
+
one. The registry itself is then a registry alone: reach that database
|
|
753
|
+
as `db["default"]`, or through the models that live on it.
|
|
754
|
+
|
|
747
755
|
The alias has to be free. Replacing one under a name already in use
|
|
748
756
|
would leave the code that holds the old database talking to it.
|
|
749
757
|
|
|
750
758
|
Raises:
|
|
751
759
|
AliasInUseError: if another database holds that alias.
|
|
752
|
-
DefaultAliasError: if
|
|
760
|
+
DefaultAliasError: if `default` is asked for and the registry
|
|
761
|
+
already has one.
|
|
753
762
|
|
|
754
763
|
"""
|
|
755
764
|
if alias == DEFAULT_ALIAS:
|
|
756
|
-
|
|
765
|
+
if self.is_configured:
|
|
766
|
+
raise DefaultAliasError
|
|
767
|
+
self._default = self._named(alias, db)
|
|
768
|
+
return
|
|
757
769
|
if alias in self._aliased:
|
|
758
770
|
raise AliasInUseError(alias)
|
|
759
771
|
self._aliased[alias] = self._named(alias, db)
|
|
@@ -783,7 +795,7 @@ class _DatabaseRegistryMixin(BaseDatabase[Any, Any], Generic[DatabaseT]):
|
|
|
783
795
|
that one on its own.
|
|
784
796
|
"""
|
|
785
797
|
together = Recording(label=label) if into is None else into
|
|
786
|
-
databases = (self
|
|
798
|
+
databases = tuple(self[alias] for alias in self.aliases)
|
|
787
799
|
with ExitStack() as stack:
|
|
788
800
|
for db in databases:
|
|
789
801
|
stack.enter_context(
|
|
@@ -888,7 +900,12 @@ class _DatabaseRegistryMixin(BaseDatabase[Any, Any], Generic[DatabaseT]):
|
|
|
888
900
|
|
|
889
901
|
@property
|
|
890
902
|
def is_configured(self) -> bool:
|
|
891
|
-
"""Whether
|
|
903
|
+
"""Whether this registry has a default database to reach."""
|
|
904
|
+
return "url" in self.__dict__ or self._default is not None
|
|
905
|
+
|
|
906
|
+
@property
|
|
907
|
+
def _built_its_own(self) -> bool:
|
|
908
|
+
"""Whether the default database is this registry, `configure` having built it."""
|
|
892
909
|
return "url" in self.__dict__
|
|
893
910
|
|
|
894
911
|
@overload
|
|
@@ -963,6 +980,8 @@ class _DatabaseRegistryMixin(BaseDatabase[Any, Any], Generic[DatabaseT]):
|
|
|
963
980
|
``InvalidDatabaseConfigError``.
|
|
964
981
|
|
|
965
982
|
"""
|
|
983
|
+
if self._default is not None:
|
|
984
|
+
raise DefaultAliasError
|
|
966
985
|
if not isinstance(url, Mapping):
|
|
967
986
|
self._reject_if_connected()
|
|
968
987
|
super().__init__(url, engine_args, session_args, templates, **parts)
|
|
@@ -997,7 +1016,7 @@ class _DatabaseRegistryMixin(BaseDatabase[Any, Any], Generic[DatabaseT]):
|
|
|
997
1016
|
self.route(*routers)
|
|
998
1017
|
|
|
999
1018
|
def _reject_if_connected(self) -> None:
|
|
1000
|
-
connected = self.
|
|
1019
|
+
connected = self._built_its_own and self._engine is not None
|
|
1001
1020
|
if connected or any(
|
|
1002
1021
|
db._engine is not None # noqa: SLF001
|
|
1003
1022
|
for db in self._aliased.values()
|
|
@@ -1007,19 +1026,47 @@ class _DatabaseRegistryMixin(BaseDatabase[Any, Any], Generic[DatabaseT]):
|
|
|
1007
1026
|
if not TYPE_CHECKING:
|
|
1008
1027
|
# Hidden from type checkers: seeing it, they would take every attribute
|
|
1009
1028
|
# to exist and stop reporting typos. It is reached when normal lookup
|
|
1010
|
-
# fails, which is what
|
|
1029
|
+
# fails, which is what a registry with no database of its own looks
|
|
1030
|
+
# like, from the outside and from its own methods.
|
|
1011
1031
|
def __getattr__(self, name: str) -> object:
|
|
1012
|
-
# Only the database
|
|
1013
|
-
#
|
|
1014
|
-
# `
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1032
|
+
# Only the database half is worth explaining. Anything else is a
|
|
1033
|
+
# name that does not exist, and saying so is what lets `hasattr`,
|
|
1034
|
+
# `copy` and every library that introspects work.
|
|
1035
|
+
state = self.__dict__
|
|
1036
|
+
if "url" in state:
|
|
1037
|
+
raise AttributeError(name)
|
|
1038
|
+
asked_as_a_database = name in DATABASE_STATE or (
|
|
1039
|
+
not name.startswith("_") and hasattr(type(self), name)
|
|
1040
|
+
)
|
|
1041
|
+
if not asked_as_a_database:
|
|
1042
|
+
raise AttributeError(name)
|
|
1043
|
+
if state.get("_default") is not None:
|
|
1044
|
+
raise DatabaseNotConfiguredError(REGISTERED_DEFAULT) from None
|
|
1045
|
+
raise DatabaseNotConfiguredError from None
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
DATABASE_STATE = frozenset(
|
|
1049
|
+
# What `BaseDatabase.__init__` sets. A registry has these once it has a
|
|
1050
|
+
# database of its own, and reaching for one before then is the question
|
|
1051
|
+
# `DatabaseNotConfiguredError` answers, whichever method asked.
|
|
1052
|
+
{
|
|
1053
|
+
"url",
|
|
1054
|
+
"templates",
|
|
1055
|
+
"engine_args",
|
|
1056
|
+
"session_args",
|
|
1057
|
+
"_sessionmaker",
|
|
1058
|
+
"_engine_lock",
|
|
1059
|
+
"_scope",
|
|
1060
|
+
"_outer",
|
|
1061
|
+
"_recordings",
|
|
1062
|
+
"_stacks",
|
|
1063
|
+
"_skipped",
|
|
1064
|
+
"_listening",
|
|
1065
|
+
"_listened",
|
|
1066
|
+
"_listening_lock",
|
|
1067
|
+
"_name",
|
|
1068
|
+
}
|
|
1069
|
+
)
|
|
1023
1070
|
|
|
1024
1071
|
_CONTROL = frozenset(
|
|
1025
1072
|
# Transaction control is not a query, and which of these reach a cursor
|
|
@@ -138,27 +138,29 @@ class BaseModel(Generic[DatabaseT]):
|
|
|
138
138
|
the open `using()` block, in that order. A model left on the default
|
|
139
139
|
alias follows `using()`, which is what makes the switch above work.
|
|
140
140
|
|
|
141
|
-
Without an alias it
|
|
142
|
-
|
|
141
|
+
Without an alias it goes in as the default one, which is where a model
|
|
142
|
+
that names no database lives:
|
|
143
143
|
|
|
144
144
|
```python
|
|
145
145
|
Base.register_db(Database(DB_URL))
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
+
The registry answers for it either way, so `dbs["default"]`,
|
|
149
|
+
`dbs.transactions()` and `using()` all reach what was registered.
|
|
150
|
+
[`set_db`][sqlakit.orm.ModelMixin.set_db] is the other way to name a
|
|
151
|
+
database, and it leaves the registry out of it.
|
|
152
|
+
|
|
148
153
|
Raises:
|
|
149
154
|
AliasInUseError: if another database holds that alias.
|
|
150
|
-
DefaultAliasError: if the
|
|
155
|
+
DefaultAliasError: if the registry already has a default database.
|
|
151
156
|
|
|
152
157
|
"""
|
|
153
|
-
if alias is None:
|
|
154
|
-
cls.set_db(db)
|
|
155
|
-
return
|
|
156
158
|
if _owns_no_registry(cls):
|
|
157
159
|
# A registry of its own: registering into the importable one would
|
|
158
160
|
# configure it for every model in the process. A class under one
|
|
159
161
|
# that already has its own registers into that one.
|
|
160
162
|
cls.__dbs__ = type(cls.__dbs__)()
|
|
161
|
-
cast("DatabaseRegistry", cls.__dbs__).register(alias, db)
|
|
163
|
+
cast("DatabaseRegistry", cls.__dbs__).register(alias or DEFAULT_ALIAS, db)
|
|
162
164
|
|
|
163
165
|
def update(self, values: Mapping[str, Any]) -> Self:
|
|
164
166
|
"""Set these fields on this instance, and return it.
|
|
@@ -52,8 +52,10 @@ class Databases(_DatabaseRegistryMixin[Database], Database):
|
|
|
52
52
|
|
|
53
53
|
def dispose(self, *, close: bool = True) -> None:
|
|
54
54
|
"""Dispose of every configured database, not just the default one."""
|
|
55
|
-
if self.
|
|
55
|
+
if self._built_its_own:
|
|
56
56
|
super().dispose(close=close)
|
|
57
|
+
elif self._default is not None:
|
|
58
|
+
self._default.dispose(close=close)
|
|
57
59
|
for db in self._aliased.values():
|
|
58
60
|
db.dispose(close=close)
|
|
59
61
|
|
|
@@ -47,8 +47,10 @@ class Databases(_DatabaseRegistryMixin[Database], Database):
|
|
|
47
47
|
|
|
48
48
|
async def dispose(self, *, close: bool = True) -> None:
|
|
49
49
|
"""Dispose of every configured database, not just the default one."""
|
|
50
|
-
if self.
|
|
50
|
+
if self._built_its_own:
|
|
51
51
|
await super().dispose(close=close)
|
|
52
|
+
elif self._default is not None:
|
|
53
|
+
await self._default.dispose(close=close)
|
|
52
54
|
for db in self._aliased.values():
|
|
53
55
|
await db.dispose(close=close)
|
|
54
56
|
|
|
@@ -85,6 +85,13 @@ class RetryNotSupportedError(SQLAKitError, TypeError):
|
|
|
85
85
|
super().__init__(message)
|
|
86
86
|
|
|
87
87
|
|
|
88
|
+
REGISTERED_DEFAULT = (
|
|
89
|
+
"This registry did not build the default database, it was registered. "
|
|
90
|
+
"Reach that one as `db['default']`, or through the models that live on it."
|
|
91
|
+
)
|
|
92
|
+
"""What a registry says when it is asked for connections it does not have."""
|
|
93
|
+
|
|
94
|
+
|
|
88
95
|
class DatabaseNotConfiguredError(SQLAKitError, RuntimeError):
|
|
89
96
|
"""Raised when the importable database is used before it has a URL."""
|
|
90
97
|
|
|
@@ -123,12 +130,13 @@ class AliasInUseError(SQLAKitError, ValueError):
|
|
|
123
130
|
|
|
124
131
|
|
|
125
132
|
class DefaultAliasError(SQLAKitError, ValueError):
|
|
126
|
-
"""Raised when
|
|
133
|
+
"""Raised when a registry is given a second default database."""
|
|
127
134
|
|
|
128
135
|
def __init__(self) -> None:
|
|
129
136
|
super().__init__(
|
|
130
|
-
f"`{DEFAULT_ALIAS}`
|
|
131
|
-
"`configure()
|
|
137
|
+
f"This registry already has a `{DEFAULT_ALIAS}` database. "
|
|
138
|
+
"`configure()` builds one and `register()` hands one over, and a "
|
|
139
|
+
"registry keeps whichever came first."
|
|
132
140
|
)
|
|
133
141
|
|
|
134
142
|
|
|
@@ -172,8 +172,8 @@ def _before_the_test_s_own(item: pytest.Function) -> int:
|
|
|
172
172
|
def sqlakit_db(sqlakit_base: Any) -> Any: # noqa: ANN401
|
|
173
173
|
"""Return the database the marked tests run on.
|
|
174
174
|
|
|
175
|
-
The one the models live on, which
|
|
176
|
-
|
|
175
|
+
The one the models live on: their registry, which knows every alias, or the
|
|
176
|
+
`Database` they were given in person. Override it for a project with no
|
|
177
177
|
model layer.
|
|
178
178
|
"""
|
|
179
179
|
if sqlakit_base is None:
|
|
@@ -185,6 +185,15 @@ def sqlakit_db(sqlakit_base: Any) -> Any: # noqa: ANN401
|
|
|
185
185
|
pytrace=False,
|
|
186
186
|
)
|
|
187
187
|
return importable_db
|
|
188
|
+
registry = getattr(sqlakit_base, "dbs", None)
|
|
189
|
+
if (
|
|
190
|
+
isinstance(getattr(sqlakit_base, "__db__", None), str)
|
|
191
|
+
and registry is not None
|
|
192
|
+
and registry.is_configured
|
|
193
|
+
):
|
|
194
|
+
# The models look their aliases up there, so the schema and the
|
|
195
|
+
# transactions cover every one of them, not the default alone.
|
|
196
|
+
return registry
|
|
188
197
|
database = getattr(sqlakit_base, "db", None)
|
|
189
198
|
if database is None:
|
|
190
199
|
pytest.fail(
|
|
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
|