sqlakit 0.5.0__tar.gz → 0.5.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.5.0 → sqlakit-0.5.1}/PKG-INFO +1 -1
- {sqlakit-0.5.0 → sqlakit-0.5.1}/pyproject.toml +1 -1
- {sqlakit-0.5.0 → sqlakit-0.5.1}/pyproject.toml.orig +1 -1
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/__init__.py +4 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/_base.py +27 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/_model.py +57 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/exceptions.py +23 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/LICENSE +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/README.md +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/_db.py +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/_discovery.py +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/_query.py +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/_recording.py +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/_registry.py +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/_routing.py +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/_sql.py +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/asyncio/__init__.py +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/asyncio/_db.py +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/asyncio/_registry.py +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/asyncio/orm.py +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/asyncio/sql.py +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/orm.py +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/py.typed +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/sql.py +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/testing.py +0 -0
- {sqlakit-0.5.0 → sqlakit-0.5.1}/sqlakit/types.py +0 -0
|
@@ -7,11 +7,13 @@ from ._registry import Databases, db
|
|
|
7
7
|
from ._routing import Router
|
|
8
8
|
from .exceptions import (
|
|
9
9
|
DEFAULT_ALIAS,
|
|
10
|
+
AliasInUseError,
|
|
10
11
|
AsyncFilterError,
|
|
11
12
|
BulkQueryError,
|
|
12
13
|
ConflictingDatabaseUrlError,
|
|
13
14
|
DatabaseAlreadyConfiguredError,
|
|
14
15
|
DatabaseNotConfiguredError,
|
|
16
|
+
DefaultAliasError,
|
|
15
17
|
DetachedInstanceError,
|
|
16
18
|
InstanceNotFoundError,
|
|
17
19
|
InvalidCursorError,
|
|
@@ -55,6 +57,7 @@ __all__ = [
|
|
|
55
57
|
"DEFAULT_ALIAS",
|
|
56
58
|
"DEFAULT_ENGINE_ARGS",
|
|
57
59
|
"DEFAULT_SESSION_ARGS",
|
|
60
|
+
"AliasInUseError",
|
|
58
61
|
"AsyncFilterError",
|
|
59
62
|
"BulkQueryError",
|
|
60
63
|
"ConflictingDatabaseUrlError",
|
|
@@ -64,6 +67,7 @@ __all__ = [
|
|
|
64
67
|
"DatabaseConfig",
|
|
65
68
|
"DatabaseNotConfiguredError",
|
|
66
69
|
"Databases",
|
|
70
|
+
"DefaultAliasError",
|
|
67
71
|
"DetachedInstanceError",
|
|
68
72
|
"EngineArgs",
|
|
69
73
|
"InstanceNotFoundError",
|
|
@@ -35,9 +35,11 @@ from ._recording import (
|
|
|
35
35
|
from ._routing import Router, as_router
|
|
36
36
|
from .exceptions import (
|
|
37
37
|
DEFAULT_ALIAS,
|
|
38
|
+
AliasInUseError,
|
|
38
39
|
ConflictingDatabaseUrlError,
|
|
39
40
|
DatabaseAlreadyConfiguredError,
|
|
40
41
|
DatabaseNotConfiguredError,
|
|
42
|
+
DefaultAliasError,
|
|
41
43
|
MissingConnectionError,
|
|
42
44
|
MissingDatabaseUrlError,
|
|
43
45
|
MissingDefaultDatabaseError,
|
|
@@ -693,6 +695,31 @@ class _DatabaseRegistryMixin(BaseDatabase[Any, Any], Generic[DatabaseT]):
|
|
|
693
695
|
def __contains__(self, alias: str) -> bool:
|
|
694
696
|
return alias == DEFAULT_ALIAS or alias in self._aliased
|
|
695
697
|
|
|
698
|
+
def register(self, alias: str, db: DatabaseT) -> None:
|
|
699
|
+
"""Put a database already built under an alias.
|
|
700
|
+
|
|
701
|
+
`configure` takes settings and builds the databases. This takes one you
|
|
702
|
+
built yourself, for a shard that only exists once the application is
|
|
703
|
+
running, or for a registry that never reads settings at all:
|
|
704
|
+
|
|
705
|
+
```python
|
|
706
|
+
db.register("shard-7", Database(SHARD_URL))
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
The alias has to be free. Replacing one under a name already in use
|
|
710
|
+
would leave the code that holds the old database talking to it.
|
|
711
|
+
|
|
712
|
+
Raises:
|
|
713
|
+
AliasInUseError: if another database holds that alias.
|
|
714
|
+
DefaultAliasError: if the alias is `default`, which this registry is.
|
|
715
|
+
|
|
716
|
+
"""
|
|
717
|
+
if alias == DEFAULT_ALIAS:
|
|
718
|
+
raise DefaultAliasError
|
|
719
|
+
if alias in self._aliased:
|
|
720
|
+
raise AliasInUseError(alias)
|
|
721
|
+
self._aliased[alias] = self._named(alias, db)
|
|
722
|
+
|
|
696
723
|
@contextmanager
|
|
697
724
|
def recording(
|
|
698
725
|
self,
|
|
@@ -37,7 +37,9 @@ __all__ = [
|
|
|
37
37
|
"BaseModel",
|
|
38
38
|
"BaseSoftDeletes",
|
|
39
39
|
"DatabaseDescriptor",
|
|
40
|
+
"DatabaseRegistry",
|
|
40
41
|
"DatabaseSource",
|
|
42
|
+
"RegistryDescriptor",
|
|
41
43
|
"db_for",
|
|
42
44
|
"resolve_alias",
|
|
43
45
|
"soft_delete_column",
|
|
@@ -66,6 +68,19 @@ class DatabaseDescriptor(Generic[DatabaseT]):
|
|
|
66
68
|
return cast("DatabaseT", db_for(owner))
|
|
67
69
|
|
|
68
70
|
|
|
71
|
+
class DatabaseRegistry(DatabaseSource, Protocol):
|
|
72
|
+
"""A source that also takes a database under an alias it does not have."""
|
|
73
|
+
|
|
74
|
+
def register(self, alias: str, db: Any) -> None: ... # noqa: ANN401
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class RegistryDescriptor:
|
|
78
|
+
"""Reads ``__dbs__``, on the class as well as on an instance."""
|
|
79
|
+
|
|
80
|
+
def __get__(self, instance: object | None, owner: type[Any]) -> Any: # noqa: ANN401
|
|
81
|
+
return owner.__dbs__
|
|
82
|
+
|
|
83
|
+
|
|
69
84
|
class BaseModel(Generic[DatabaseT]):
|
|
70
85
|
"""What the sync and async models share: everything that is not IO.
|
|
71
86
|
|
|
@@ -87,6 +102,7 @@ class BaseModel(Generic[DatabaseT]):
|
|
|
87
102
|
# Unannotated on purpose: an annotation here reads as a field to
|
|
88
103
|
# pydantic, and SQLModel models would refuse to build.
|
|
89
104
|
db = DatabaseDescriptor[DatabaseT]()
|
|
105
|
+
dbs = RegistryDescriptor()
|
|
90
106
|
|
|
91
107
|
@classmethod
|
|
92
108
|
def set_db(cls, db: str | DatabaseT) -> None:
|
|
@@ -103,6 +119,37 @@ class BaseModel(Generic[DatabaseT]):
|
|
|
103
119
|
"""
|
|
104
120
|
cls.__db__ = db
|
|
105
121
|
|
|
122
|
+
@classmethod
|
|
123
|
+
def register_db(cls, db: DatabaseT, *, alias: str) -> None:
|
|
124
|
+
"""Give this model a database under an alias, and the ones under it too.
|
|
125
|
+
|
|
126
|
+
The registry it goes in belongs to this class, so nothing global is
|
|
127
|
+
configured and two sets of models can each have their own `shard`:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
Base.register_db(Database(DB1_URL), alias="db1")
|
|
131
|
+
Base.register_db(Database(DB2_URL), alias="db2")
|
|
132
|
+
|
|
133
|
+
with Base.dbs.using("db2").transaction():
|
|
134
|
+
User(name="ada").save()
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Which database a model resolves to is still `__db__`, the routers and
|
|
138
|
+
the open `using()` block, in that order. A model left on the default
|
|
139
|
+
alias follows `using()`, which is what makes the switch above work.
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
AliasInUseError: if another database holds that alias.
|
|
143
|
+
DefaultAliasError: if the alias is `default`.
|
|
144
|
+
|
|
145
|
+
"""
|
|
146
|
+
if _owns_no_registry(cls):
|
|
147
|
+
# A registry of its own: registering into the importable one would
|
|
148
|
+
# configure it for every model in the process. A class under one
|
|
149
|
+
# that already has its own registers into that one.
|
|
150
|
+
cls.__dbs__ = type(cls.__dbs__)()
|
|
151
|
+
cast("DatabaseRegistry", cls.__dbs__).register(alias, db)
|
|
152
|
+
|
|
106
153
|
def update(self, values: Mapping[str, Any]) -> Self:
|
|
107
154
|
"""Set these fields on this instance, and return it.
|
|
108
155
|
|
|
@@ -187,6 +234,16 @@ class BaseModel(Generic[DatabaseT]):
|
|
|
187
234
|
self.db.session.add(self)
|
|
188
235
|
|
|
189
236
|
|
|
237
|
+
def _owns_no_registry(model: type[Any]) -> bool:
|
|
238
|
+
"""Whether this model still looks its aliases up in the importable registry."""
|
|
239
|
+
declared = next(
|
|
240
|
+
(klass for klass in model.__mro__ if "__dbs__" in klass.__dict__), None
|
|
241
|
+
)
|
|
242
|
+
return (
|
|
243
|
+
declared is None or declared.__module__.split(".")[0] == __name__.split(".")[0]
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
190
247
|
def db_for(model: type[Any]) -> BaseDatabase[Any, Any]:
|
|
191
248
|
"""Return the database a model lives on.
|
|
192
249
|
|
|
@@ -5,11 +5,13 @@ from sqlalchemy.orm import exc as sa_exc
|
|
|
5
5
|
DEFAULT_ALIAS = "default"
|
|
6
6
|
|
|
7
7
|
__all__ = [
|
|
8
|
+
"AliasInUseError",
|
|
8
9
|
"AsyncFilterError",
|
|
9
10
|
"BulkQueryError",
|
|
10
11
|
"ConflictingDatabaseUrlError",
|
|
11
12
|
"DatabaseAlreadyConfiguredError",
|
|
12
13
|
"DatabaseNotConfiguredError",
|
|
14
|
+
"DefaultAliasError",
|
|
13
15
|
"DetachedInstanceError",
|
|
14
16
|
"InstanceNotFoundError",
|
|
15
17
|
"InvalidCursorError",
|
|
@@ -107,6 +109,27 @@ class DatabaseAlreadyConfiguredError(SQLAKitError, RuntimeError):
|
|
|
107
109
|
super().__init__(message)
|
|
108
110
|
|
|
109
111
|
|
|
112
|
+
class AliasInUseError(SQLAKitError, ValueError):
|
|
113
|
+
"""Raised when registering a database under an alias another one holds."""
|
|
114
|
+
|
|
115
|
+
def __init__(self, alias: str) -> None:
|
|
116
|
+
self.alias = alias
|
|
117
|
+
super().__init__(
|
|
118
|
+
f"`{alias}` is already registered. Dispose of that database before "
|
|
119
|
+
"registering another under the same name."
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class DefaultAliasError(SQLAKitError, ValueError):
|
|
124
|
+
"""Raised when registering a database as the default one."""
|
|
125
|
+
|
|
126
|
+
def __init__(self) -> None:
|
|
127
|
+
super().__init__(
|
|
128
|
+
f"`{DEFAULT_ALIAS}` is the registry itself. Configure it with "
|
|
129
|
+
"`configure()`, and register the others under their own names."
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
110
133
|
class UnknownDatabaseError(SQLAKitError, KeyError):
|
|
111
134
|
"""Raised when asking for a database alias that was never configured."""
|
|
112
135
|
|
|
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
|