1"""
2QueryChain is a wrapper for sequence of queries.
3
4
5Features:
6
7 * Easy iteration for sequence of queries
8 * Limit, offset and count which are applied to all queries in the chain
9 * Smart __getitem__ support
10
11
12Initialization
13^^^^^^^^^^^^^^
14
15QueryChain takes iterable of queries as first argument. Additionally limit and
16offset parameters can be given
17
18::
19
20 chain = QueryChain([session.query(User), session.query(Article)])
21
22 chain = QueryChain(
23 [session.query(User), session.query(Article)],
24 limit=4
25 )
26
27
28Simple iteration
29^^^^^^^^^^^^^^^^
30::
31
32 chain = QueryChain([session.query(User), session.query(Article)])
33
34 for obj in chain:
35 print obj
36
37
38Limit and offset
39^^^^^^^^^^^^^^^^
40
41Lets say you have 5 blog posts, 5 articles and 5 news items in your
42database.
43
44::
45
46 chain = QueryChain(
47 [
48 session.query(BlogPost),
49 session.query(Article),
50 session.query(NewsItem)
51 ],
52 limit=5
53 )
54
55 list(chain) # all blog posts but not articles and news items
56
57
58 chain = chain.offset(4)
59 list(chain) # last blog post, and first four articles
60
61
62Just like with original query object the limit and offset can be chained to
63return a new QueryChain.
64
65::
66
67 chain = chain.limit(5).offset(7)
68
69
70Chain slicing
71^^^^^^^^^^^^^
72
73::
74
75 chain = QueryChain(
76 [
77 session.query(BlogPost),
78 session.query(Article),
79 session.query(NewsItem)
80 ]
81 )
82
83 chain[3:6] # New QueryChain with offset=3 and limit=6
84
85
86Count
87^^^^^
88
89Let's assume that there are five blog posts, five articles and five news
90items in the database, and you have the following query chain::
91
92 chain = QueryChain(
93 [
94 session.query(BlogPost),
95 session.query(Article),
96 session.query(NewsItem)
97 ]
98 )
99
100You can then get the total number rows returned by the query chain
101with :meth:`~QueryChain.count`::
102
103 >>> chain.count()
104 15
105
106
107"""
108
109from copy import copy
110
111
112class QueryChain:
113 """
114 QueryChain can be used as a wrapper for sequence of queries.
115
116 :param queries: A sequence of SQLAlchemy Query objects
117 :param limit: Similar to normal query limit this parameter can be used for
118 limiting the number of results for the whole query chain.
119 :param offset: Similar to normal query offset this parameter can be used
120 for offsetting the query chain as a whole.
121
122 .. versionadded: 0.26.0
123 """
124
125 def __init__(self, queries, limit=None, offset=None):
126 self.queries = queries
127 self._limit = limit
128 self._offset = offset
129
130 def __iter__(self):
131 consumed = 0
132 skipped = 0
133 for query in self.queries:
134 query_copy = copy(query)
135 if self._limit:
136 query = query.limit(self._limit - consumed)
137 if self._offset:
138 query = query.offset(self._offset - skipped)
139
140 obj_count = 0
141 for obj in query:
142 consumed += 1
143 obj_count += 1
144 yield obj
145
146 if not obj_count:
147 skipped += query_copy.count()
148 else:
149 skipped += obj_count
150
151 def limit(self, value):
152 return self[:value]
153
154 def offset(self, value):
155 return self[value:]
156
157 def count(self):
158 """
159 Return the total number of rows this QueryChain's queries would return.
160 """
161 return sum(q.count() for q in self.queries)
162
163 def __getitem__(self, key):
164 if isinstance(key, slice):
165 return self.__class__(
166 queries=self.queries,
167 limit=key.stop if key.stop is not None else self._limit,
168 offset=key.start if key.start is not None else self._offset,
169 )
170 else:
171 for obj in self[key:1]:
172 return obj
173
174 def __repr__(self):
175 return '<QueryChain at 0x%x>' % id(self)