forked from carbonblack/cbapi-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbase_query.py
More file actions
executable file
·270 lines (222 loc) · 7.99 KB
/
base_query.py
File metadata and controls
executable file
·270 lines (222 loc) · 7.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
from cbapi.errors import ApiError, MoreThanOneResultError
import functools
from six import string_types
from solrq import Q
class QueryBuilder(object):
"""
Provides a flexible interface for building prepared queries for the CB
PSC backend.
This object can be instantiated directly, or can be managed implicitly
through the :py:meth:`select` API.
"""
def __init__(self, **kwargs):
if kwargs:
self._query = Q(**kwargs)
else:
self._query = None
self._raw_query = None
def _guard_query_params(func):
"""Decorates the query construction methods of *QueryBuilder*, preventing
them from being called with parameters that would result in an internally
inconsistent query.
"""
@functools.wraps(func)
def wrap_guard_query_change(self, q, **kwargs):
if self._raw_query is not None and (kwargs or isinstance(q, Q)):
raise ApiError("Cannot modify a raw query with structured parameters")
if self._query is not None and isinstance(q, string_types):
raise ApiError("Cannot modify a structured query with a raw parameter")
return func(self, q, **kwargs)
return wrap_guard_query_change
@_guard_query_params
def where(self, q, **kwargs):
"""Adds a conjunctive filter to a query.
:param q: string or `solrq.Q` object
:param kwargs: Arguments to construct a `solrq.Q` with
:return: QueryBuilder object
:rtype: :py:class:`QueryBuilder`
"""
if isinstance(q, string_types):
if self._raw_query is None:
self._raw_query = []
self._raw_query.append(q)
elif isinstance(q, Q) or kwargs:
if self._query is not None:
raise ApiError("Use .and_() or .or_() for an extant solrq.Q object")
if kwargs:
q = Q(**kwargs)
self._query = q
else:
raise ApiError(".where() only accepts strings or solrq.Q objects")
return self
@_guard_query_params
def and_(self, q, **kwargs):
"""Adds a conjunctive filter to a query.
:param q: string or `solrq.Q` object
:param kwargs: Arguments to construct a `solrq.Q` with
:return: QueryBuilder object
:rtype: :py:class:`QueryBuilder`
"""
if isinstance(q, string_types):
self.where(q)
elif isinstance(q, Q) or kwargs:
if kwargs:
q = Q(**kwargs)
if self._query is None:
self._query = q
else:
self._query = self._query & q
else:
raise ApiError(".and_() only accepts strings or solrq.Q objects")
return self
@_guard_query_params
def or_(self, q, **kwargs):
"""Adds a disjunctive filter to a query.
:param q: `solrq.Q` object
:param kwargs: Arguments to construct a `solrq.Q` with
:return: QueryBuilder object
:rtype: :py:class:`QueryBuilder`
"""
if kwargs:
q = Q(**kwargs)
if isinstance(q, Q):
if self._query is None:
self._query = q
else:
self._query = self._query | q
else:
raise ApiError(".or_() only accepts solrq.Q objects")
return self
@_guard_query_params
def not_(self, q, **kwargs):
"""Adds a negative filter to a query.
:param q: `solrq.Q` object
:param kwargs: Arguments to construct a `solrq.Q` with
:return: QueryBuilder object
:rtype: :py:class:`QueryBuilder`
"""
if kwargs:
q = ~Q(**kwargs)
if isinstance(q, Q):
if self._query is None:
self._query = q
else:
self._query = self._query & q
else:
raise ApiError(".not_() only accepts solrq.Q objects")
def _collapse(self):
"""The query can be represented by either an array of strings
(_raw_query) which is concatenated and passed directly to Solr, or
a solrq.Q object (_query) which is then converted into a string to
pass to Solr. This function will perform the appropriate conversions to
end up with the 'q' string sent into the POST request to the
PSC-R query endpoint."""
if self._raw_query is not None:
return " ".join(self._raw_query)
elif self._query is not None:
return str(self._query)
else:
return None # return everything
class PSCQueryBase:
"""
Represents the base of all LiveQuery query classes.
"""
def __init__(self, doc_class, cb):
self._doc_class = doc_class
self._cb = cb
class QueryBuilderSupportMixin:
"""
A mixin that supplies wrapper methods to access the _query_builder.
"""
def where(self, q=None, **kwargs):
"""Add a filter to this query.
:param q: Query string, :py:class:`QueryBuilder`, or `solrq.Q` object
:param kwargs: Arguments to construct a `solrq.Q` with
:return: Query object
:rtype: :py:class:`Query`
"""
if not q:
return self
if isinstance(q, QueryBuilder):
self._query_builder = q
else:
self._query_builder.where(q, **kwargs)
return self
def and_(self, q=None, **kwargs):
"""Add a conjunctive filter to this query.
:param q: Query string or `solrq.Q` object
:param kwargs: Arguments to construct a `solrq.Q` with
:return: Query object
:rtype: :py:class:`Query`
"""
if not q and not kwargs:
raise ApiError(".and_() expects a string, a solrq.Q, or kwargs")
self._query_builder.and_(q, **kwargs)
return self
def or_(self, q=None, **kwargs):
"""Add a disjunctive filter to this query.
:param q: `solrq.Q` object
:param kwargs: Arguments to construct a `solrq.Q` with
:return: Query object
:rtype: :py:class:`Query`
"""
if not q and not kwargs:
raise ApiError(".or_() expects a solrq.Q or kwargs")
self._query_builder.or_(q, **kwargs)
return self
def not_(self, q=None, **kwargs):
"""Adds a negated filter to this query.
:param q: `solrq.Q` object
:param kwargs: Arguments to construct a `solrq.Q` with
:return: Query object
:rtype: :py:class:`Query`
"""
if not q and not kwargs:
raise ApiError(".not_() expects a solrq.Q, or kwargs")
self._query_builder.not_(q, **kwargs)
return self
class IterableQueryMixin:
"""
A mix-in to provide iterability to a query.
"""
def all(self):
"""
Returns all the items of a query as a list.
:return: List of query items
"""
return self._perform_query()
def first(self):
"""
Returns the first item that would be returned as the result of a query.
:return: First query item
"""
allres = list(self)
res = allres[:1]
if not len(res):
return None
return res[0]
def one(self):
"""
Returns the only item that would be returned by a query.
:return: Sole query return item
:raises MoreThanOneResultError: If the query returns zero items, or more than one item
"""
allres = list(self)
res = allres[:2]
if len(res) == 0:
raise MoreThanOneResultError(
message="0 results for query {0:s}".format(self._query)
)
if len(res) > 1:
raise MoreThanOneResultError(
message="{0:d} results found for query {1:s}".format(
len(self), self._query
)
)
return res[0]
def __len__(self):
return 0
def __getitem__(self, item):
return None
def __iter__(self):
return self._perform_query()