sqlalchemy-boltons 1.1.0__py3-none-any.whl → 2.0.1__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 +2 -0
- {sqlalchemy_boltons-1.1.0.dist-info → sqlalchemy_boltons-2.0.1.dist-info}/METADATA +34 -1
- sqlalchemy_boltons-2.0.1.dist-info/RECORD +10 -0
- sqlalchemy_boltons-1.1.0.dist-info/RECORD +0 -9
- {sqlalchemy_boltons-1.1.0.dist-info → sqlalchemy_boltons-2.0.1.dist-info}/AUTHORS.md +0 -0
- {sqlalchemy_boltons-1.1.0.dist-info → sqlalchemy_boltons-2.0.1.dist-info}/LICENSE +0 -0
- {sqlalchemy_boltons-1.1.0.dist-info → sqlalchemy_boltons-2.0.1.dist-info}/WHEEL +0 -0
- {sqlalchemy_boltons-1.1.0.dist-info → sqlalchemy_boltons-2.0.1.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,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sqlalchemy-boltons
|
3
|
-
Version:
|
3
|
+
Version: 2.0.1
|
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. This even works on self-referential tables!
|
@@ -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.1.dist-info/AUTHORS.md,sha256=sXkm88GaYJ63k0Hy7UlGboAT4aytU8e1_K0wNo7WwD8,144
|
6
|
+
sqlalchemy_boltons-2.0.1.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
+
sqlalchemy_boltons-2.0.1.dist-info/METADATA,sha256=A38vL0q_clwZpP4czpbEUMEEG4yWlwsYPFNwDkMExIo,4212
|
8
|
+
sqlalchemy_boltons-2.0.1.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
|
9
|
+
sqlalchemy_boltons-2.0.1.dist-info/top_level.txt,sha256=mlvZ3R5FelrQt2SNXCtw2bDXcTROxl5O_gDFnWXPQ84,19
|
10
|
+
sqlalchemy_boltons-2.0.1.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=dPPsxJh_YARZ1f2EoLNNo4XJSJLjKOPVDGOsO-O03Qo,8853
|
4
|
-
sqlalchemy_boltons-1.1.0.dist-info/AUTHORS.md,sha256=sXkm88GaYJ63k0Hy7UlGboAT4aytU8e1_K0wNo7WwD8,144
|
5
|
-
sqlalchemy_boltons-1.1.0.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
sqlalchemy_boltons-1.1.0.dist-info/METADATA,sha256=7GJWM3mC9MUqGyKCg6wL7IOKOl7LGAU5YgqzALPNFlY,2852
|
7
|
-
sqlalchemy_boltons-1.1.0.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
|
8
|
-
sqlalchemy_boltons-1.1.0.dist-info/top_level.txt,sha256=mlvZ3R5FelrQt2SNXCtw2bDXcTROxl5O_gDFnWXPQ84,19
|
9
|
-
sqlalchemy_boltons-1.1.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|