sqlalchemy-boltons 1.0.1__py3-none-any.whl → 2.0.0__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.
- sqlalchemy_boltons/orm.py +74 -0
- sqlalchemy_boltons/sqlite.py +15 -0
- {sqlalchemy_boltons-1.0.1.dist-info → sqlalchemy_boltons-2.0.0.dist-info}/METADATA +34 -1
- sqlalchemy_boltons-2.0.0.dist-info/RECORD +10 -0
- sqlalchemy_boltons-1.0.1.dist-info/RECORD +0 -9
- {sqlalchemy_boltons-1.0.1.dist-info → sqlalchemy_boltons-2.0.0.dist-info}/AUTHORS.md +0 -0
- {sqlalchemy_boltons-1.0.1.dist-info → sqlalchemy_boltons-2.0.0.dist-info}/LICENSE +0 -0
- {sqlalchemy_boltons-1.0.1.dist-info → sqlalchemy_boltons-2.0.0.dist-info}/WHEEL +0 -0
- {sqlalchemy_boltons-1.0.1.dist-info → sqlalchemy_boltons-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from sqlalchemy.sql import coercions as _coercions, util as _util
|
4
|
+
|
5
|
+
|
6
|
+
class RelationshipComparator:
|
7
|
+
"""
|
8
|
+
This wrapper makes it possible to compare a table against a relationship using :func:`join_expr`. This can be used
|
9
|
+
for constructing a subquery that filters using a relationship to a table from the outer query.
|
10
|
+
|
11
|
+
For example::
|
12
|
+
|
13
|
+
import sqlalchemy as sa
|
14
|
+
from sqlalchemy import orm as sao
|
15
|
+
from sqlalchemy_boltons.orm import RelationshipComparator as Rel
|
16
|
+
|
17
|
+
Base = sao.declarative_base()
|
18
|
+
|
19
|
+
|
20
|
+
class Parent(Base):
|
21
|
+
__tablename__ = "parent"
|
22
|
+
|
23
|
+
id = sa.Column(sa.Integer, primary_key=True)
|
24
|
+
|
25
|
+
children = sao.relationship("Child", back_populates="parent")
|
26
|
+
|
27
|
+
|
28
|
+
class Child(Base):
|
29
|
+
__tablename__ = "child"
|
30
|
+
|
31
|
+
id = sa.Column(sa.Integer, primary_key=True)
|
32
|
+
parent_id = sa.Column(sa.ForeignKey("parent.id"), index=True)
|
33
|
+
|
34
|
+
parent = sao.relationship("Parent", back_populates="children")
|
35
|
+
|
36
|
+
|
37
|
+
# We want a query that selects every Parent that has at least one child. We also want to use aliases.
|
38
|
+
P = sao.aliased(Parent)
|
39
|
+
C = sao.aliased(Child)
|
40
|
+
|
41
|
+
# This is the boring old way of doing it, which requires explicitly stating the filtering conditions in terms
|
42
|
+
# of the columns in both tables.
|
43
|
+
q1 = sa.select(P).where(sa.select(C).where(C.parent_id == P.id).exists())
|
44
|
+
|
45
|
+
# Reuse the filtering conditions from the ORM relationship!
|
46
|
+
q2 = sa.select(P).where(sa.select(C).where(Rel(C.parent) == P).exists())
|
47
|
+
|
48
|
+
assert str(q1) == str(q2), "these should produce the same SQL code!"
|
49
|
+
|
50
|
+
Based on this discussion: https://groups.google.com/g/sqlalchemy/c/R-qOlzzVi0o/m/NtFswgJioDIJ
|
51
|
+
"""
|
52
|
+
|
53
|
+
def __init__(self, relationship):
|
54
|
+
self._relationship = relationship
|
55
|
+
|
56
|
+
def __eq__(self, other):
|
57
|
+
if other is None:
|
58
|
+
raise TypeError("cannot compare against None")
|
59
|
+
return join_expr(other, self._relationship)
|
60
|
+
|
61
|
+
def __ne__(self, other):
|
62
|
+
return ~(self == other)
|
63
|
+
|
64
|
+
|
65
|
+
def join_expr(right, relationship):
|
66
|
+
"""
|
67
|
+
Turn an ORM relationship into an expression that you can use for filtering.
|
68
|
+
"""
|
69
|
+
|
70
|
+
expr = _coercions.expect(_coercions.roles.ColumnArgumentRole, relationship)
|
71
|
+
if right is not None:
|
72
|
+
right = _coercions.expect(_coercions.roles.FromClauseRole, right)
|
73
|
+
expr = _util.ClauseAdapter(right).traverse(expr)
|
74
|
+
return expr
|
sqlalchemy_boltons/sqlite.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import dataclasses as dc
|
2
4
|
import functools
|
3
5
|
from pathlib import Path
|
@@ -54,6 +56,16 @@ class SQLAlchemySqliteTransactionFix:
|
|
54
56
|
This class exists because sqlalchemy doesn't automatically fix pysqlite's stupid default behaviour. Additionally,
|
55
57
|
we implement support for foreign keys.
|
56
58
|
|
59
|
+
The execution options we look for are as follows:
|
60
|
+
|
61
|
+
- `x_sqlite_begin_mode`: The type of transaction to be started, such as "BEGIN" or
|
62
|
+
"[BEGIN IMMEDIATE](https://www.sqlite.org/lang_transaction.html)" (or
|
63
|
+
"[BEGIN CONCURRENT](https://www.sqlite.org/cgi/src/doc/begin-concurrent/doc/begin_concurrent.md)" someday maybe).
|
64
|
+
- `x_sqlite_foreign_keys`: The [foreign-key enforcement setting](https://www.sqlite.org/foreignkeys.html). Must be
|
65
|
+
`True`, `False`, or `"defer"`.
|
66
|
+
- `x_sqlite_journal_mode`: The [journal mode](https://www.sqlite.org/pragma.html#pragma_journal_mode) such as
|
67
|
+
`"DELETE"` or `"WAL"`. Optional.
|
68
|
+
|
57
69
|
https://docs.sqlalchemy.org/en/20/dialects/sqlite.html#serializable-isolation-savepoints-transactional-ddl
|
58
70
|
"""
|
59
71
|
|
@@ -188,6 +200,9 @@ def create_engine_sqlite(
|
|
188
200
|
# always default to QueuePool
|
189
201
|
if (k := "poolclass") not in create_engine_args:
|
190
202
|
create_engine_args[k] = sap.QueuePool
|
203
|
+
if (k := "max_overflow") not in create_engine_args:
|
204
|
+
# for SQLite it doesn't make sense to restrict the number of concurrent (read-only) connections
|
205
|
+
create_engine_args["max_overflow"] = -1
|
191
206
|
|
192
207
|
if (v := create_engine_args.get(k := "connect_args")) is None:
|
193
208
|
create_engine_args[k] = v = {}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sqlalchemy-boltons
|
3
|
-
Version:
|
3
|
+
Version: 2.0.0
|
4
4
|
Summary: Utilities that should've been inside SQLAlchemy but aren't
|
5
5
|
Author-email: Eduard Christian Dumitrescu <eduard.c.dumitrescu@gmail.com>
|
6
6
|
Maintainer-email: Eduard Christian Dumitrescu <eduard.c.dumitrescu@gmail.com>
|
@@ -71,3 +71,36 @@ with Session() as session:
|
|
71
71
|
with SessionW() as session:
|
72
72
|
session.execute(update(...))
|
73
73
|
```
|
74
|
+
|
75
|
+
## orm
|
76
|
+
|
77
|
+
Somedays you really wish you could write something like:
|
78
|
+
|
79
|
+
```python
|
80
|
+
# Find every parent that has at least one child.
|
81
|
+
Parent1 = aliased(Parent)
|
82
|
+
select(Parent1).where(
|
83
|
+
exists().select_from(Child).where(Child.parent == Parent1) # this doesn't work
|
84
|
+
)
|
85
|
+
```
|
86
|
+
|
87
|
+
But it doesn't work. You get an exception. You try various other things but it just won't produce the right subquery.
|
88
|
+
You find tons of people online saying you must resign yourself to writing the explicit filtering condition, writing out
|
89
|
+
`.where(Child.parent_id == Parent1.id)`. Like a caveman. Why even bother using an ORM at this point? If you're lucky,
|
90
|
+
you find yourself reading mail archives from [a decade ago](https://groups.google.com/g/sqlalchemy/c/R-qOlzzVi0o/m/NtFswgJioDIJ)
|
91
|
+
with somewhat of a solution, but it's not obvious how to make it work with table
|
92
|
+
[aliases](https://docs.sqlalchemy.org/en/20/orm/queryguide/api.html#sqlalchemy.orm.aliased). Despair is setting in.
|
93
|
+
|
94
|
+
But lucky you, you can now use this library:
|
95
|
+
|
96
|
+
```python
|
97
|
+
from sqlalchemy_boltons.orm import RelationshipComparator as Rel
|
98
|
+
|
99
|
+
# Find every parent that has at least one child.
|
100
|
+
Parent1 = aliased(Parent)
|
101
|
+
select(Parent1).where(
|
102
|
+
exists().select_from(Child).where(Rel(Child.parent) == Parent1)
|
103
|
+
) # ^^^^ ^
|
104
|
+
```
|
105
|
+
|
106
|
+
Hope is restored.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
sqlalchemy_boltons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
sqlalchemy_boltons/orm.py,sha256=vCRQX1NjpJh-b_PEgEDT4Mk1cgy3JRRNjq0AnszJWbA,2489
|
3
|
+
sqlalchemy_boltons/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
+
sqlalchemy_boltons/sqlite.py,sha256=r875URPWSrHxIBR5lfTK82us0yP5i32GgtW2R_DXhoY,8889
|
5
|
+
sqlalchemy_boltons-2.0.0.dist-info/AUTHORS.md,sha256=sXkm88GaYJ63k0Hy7UlGboAT4aytU8e1_K0wNo7WwD8,144
|
6
|
+
sqlalchemy_boltons-2.0.0.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
+
sqlalchemy_boltons-2.0.0.dist-info/METADATA,sha256=PA9TEywSFVYd8x3Pb0_RBesQbO-zaMA0_GeHTL5rG1U,4168
|
8
|
+
sqlalchemy_boltons-2.0.0.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
|
9
|
+
sqlalchemy_boltons-2.0.0.dist-info/top_level.txt,sha256=mlvZ3R5FelrQt2SNXCtw2bDXcTROxl5O_gDFnWXPQ84,19
|
10
|
+
sqlalchemy_boltons-2.0.0.dist-info/RECORD,,
|
@@ -1,9 +0,0 @@
|
|
1
|
-
sqlalchemy_boltons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
sqlalchemy_boltons/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
sqlalchemy_boltons/sqlite.py,sha256=fIS9rsmEFRakSl8DLwHqVJ-M9K1w6Q0XEJieYNH1b98,7985
|
4
|
-
sqlalchemy_boltons-1.0.1.dist-info/AUTHORS.md,sha256=sXkm88GaYJ63k0Hy7UlGboAT4aytU8e1_K0wNo7WwD8,144
|
5
|
-
sqlalchemy_boltons-1.0.1.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
sqlalchemy_boltons-1.0.1.dist-info/METADATA,sha256=t5yyOnZzl415Jo4q4gh2YeJD-EtDYdLEdqT0qolPzn4,2852
|
7
|
-
sqlalchemy_boltons-1.0.1.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
|
8
|
-
sqlalchemy_boltons-1.0.1.dist-info/top_level.txt,sha256=mlvZ3R5FelrQt2SNXCtw2bDXcTROxl5O_gDFnWXPQ84,19
|
9
|
-
sqlalchemy_boltons-1.0.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|