1 | from __future__ import absolute_import |
||
2 | |||
3 | import inspect |
||
4 | import re |
||
5 | from itertools import chain |
||
6 | |||
7 | from fields import Fields |
||
8 | from six import string_types |
||
9 | |||
10 | from .actions import Action |
||
11 | from .event import Event |
||
12 | |||
13 | ALLOWED_KEYS = tuple(i for i in Event.__dict__.keys() if not i.startswith('_') and i not in ('tracer', 'thread')) |
||
14 | ALLOWED_OPERATORS = ( |
||
15 | 'startswith', 'endswith', 'in', 'contains', 'regex', |
||
16 | 'sw', 'ew', 'has', 'rx', |
||
17 | 'gt', 'gte', 'lt', 'lte', |
||
18 | ) |
||
19 | |||
20 | |||
21 | def _sloppy_hash(obj): |
||
22 | try: |
||
23 | return hash(obj) |
||
24 | except TypeError: |
||
25 | return 'id(%x)' % id(obj) |
||
26 | |||
27 | |||
28 | class Query(Fields.query_eq.query_startswith.query_endswith.query_in.query_contains): |
||
29 | """ |
||
30 | A query class. |
||
31 | |||
32 | See :class:`hunter.Event` for fields that can be filtered on. |
||
33 | """ |
||
34 | def __init__(self, **query): |
||
35 | """ |
||
36 | Args: |
||
37 | query: criteria to match on. |
||
38 | |||
39 | Accepted arguments: |
||
40 | ``arg``, |
||
41 | ``calls``, |
||
42 | ``code``, |
||
43 | ``depth``, |
||
44 | ``filename``, |
||
45 | ``frame``, |
||
46 | ``fullsource``, |
||
47 | ``function``, |
||
48 | ``globals``, |
||
49 | ``kind``, |
||
50 | ``lineno``, |
||
51 | ``locals``, |
||
52 | ``module``, |
||
53 | ``source``, |
||
54 | ``stdlib``, |
||
55 | ``threadid``, |
||
56 | ``threadname``. |
||
57 | """ |
||
58 | query_eq = {} |
||
59 | query_startswith = {} |
||
60 | query_endswith = {} |
||
61 | query_in = {} |
||
62 | query_contains = {} |
||
63 | query_regex = {} |
||
64 | query_lt = {} |
||
65 | query_lte = {} |
||
66 | query_gt = {} |
||
67 | query_gte = {} |
||
68 | |||
69 | for key, value in query.items(): |
||
70 | parts = [p for p in key.split('_') if p] |
||
71 | count = len(parts) |
||
72 | if count > 2: |
||
73 | raise TypeError('Unexpected argument %r. Must be one of %s with optional operators like: %s' % ( |
||
74 | key, ALLOWED_KEYS, ALLOWED_OPERATORS |
||
75 | )) |
||
76 | elif count == 2: |
||
77 | prefix, operator = parts |
||
78 | if operator in ('startswith', 'sw'): |
||
79 | if not isinstance(value, string_types): |
||
80 | if not isinstance(value, (list, set, tuple)): |
||
81 | raise ValueError('Value %r for %r is invalid. Must be a string, list, tuple or set.' % (value, key)) |
||
82 | value = tuple(value) |
||
83 | mapping = query_startswith |
||
84 | elif operator in ('endswith', 'ew'): |
||
85 | if not isinstance(value, string_types): |
||
86 | if not isinstance(value, (list, set, tuple)): |
||
87 | raise ValueError('Value %r for %r is invalid. Must be a string, list, tuple or set.' % (value, key)) |
||
88 | value = tuple(value) |
||
89 | mapping = query_endswith |
||
90 | elif operator == 'in': |
||
91 | mapping = query_in |
||
92 | elif operator in ('contains', 'has'): |
||
93 | mapping = query_contains |
||
94 | elif operator in ('regex', 'rx'): |
||
95 | value = re.compile(value) |
||
96 | mapping = query_regex |
||
97 | elif operator == 'lt': |
||
98 | mapping = query_lt |
||
99 | elif operator == 'lte': |
||
100 | mapping = query_lte |
||
101 | elif operator == 'gt': |
||
102 | mapping = query_gt |
||
103 | elif operator == 'gte': |
||
104 | mapping = query_gte |
||
105 | else: |
||
106 | raise TypeError('Unexpected operator %r. Must be one of %s.' % (operator, ALLOWED_OPERATORS)) |
||
107 | else: |
||
108 | mapping = query_eq |
||
109 | prefix = key |
||
110 | |||
111 | View Code Duplication | if prefix not in ALLOWED_KEYS: |
|
0 ignored issues
–
show
Duplication
introduced
by
Loading history...
|
|||
112 | raise TypeError('Unexpected argument %r. Must be one of %s.' % (key, ALLOWED_KEYS)) |
||
113 | |||
114 | mapping[prefix] = value |
||
115 | |||
116 | self.query_eq = tuple(sorted(query_eq.items())) |
||
117 | self.query_startswith = tuple(sorted(query_startswith.items())) |
||
118 | self.query_endswith = tuple(sorted(query_endswith.items())) |
||
119 | self.query_in = tuple(sorted(query_in.items())) |
||
120 | self.query_contains = tuple(sorted(query_contains.items())) |
||
121 | self.query_regex = tuple(sorted(query_regex.items())) |
||
122 | self.query_lt = tuple(sorted(query_lt.items())) |
||
123 | self.query_lte = tuple(sorted(query_lte.items())) |
||
124 | self.query_gt = tuple(sorted(query_gt.items())) |
||
125 | self.query_gte = tuple(sorted(query_gte.items())) |
||
126 | |||
127 | def __str__(self): |
||
128 | return 'Query(%s)' % ( |
||
129 | ', '.join( |
||
130 | View Code Duplication | ', '.join('%s%s=%r' % (key, kind, value) for key, value in mapping) |
|
0 ignored issues
–
show
|
|||
131 | for kind, mapping in [ |
||
132 | ('', self.query_eq), |
||
133 | ('_in', self.query_in), |
||
134 | ('_contains', self.query_contains), |
||
135 | ('_startswith', self.query_startswith), |
||
136 | ('_endswith', self.query_endswith), |
||
137 | ('_regex', self.query_regex), |
||
138 | ('_lt', self.query_lt), |
||
139 | ('_lte', self.query_lte), |
||
140 | ('_gt', self.query_gt), |
||
141 | ('_gte', self.query_gte), |
||
142 | ] if mapping |
||
143 | ) |
||
144 | ) |
||
145 | |||
146 | def __repr__(self): |
||
147 | return '<hunter.predicates.Query: %s>' % ' '.join( |
||
148 | fmt % (mapping,) for fmt, mapping in [ |
||
149 | ('query_eq=%r', self.query_eq), |
||
150 | ('query_in=%r', self.query_in), |
||
151 | ('query_contains=%r', self.query_contains), |
||
152 | ('query_startswith=%r', self.query_startswith), |
||
153 | ('query_endswith=%r', self.query_endswith), |
||
154 | ('query_regex=%r', self.query_regex), |
||
155 | ('query_regex=%r', self.query_regex), |
||
156 | ('query_lt=%r', self.query_lt), |
||
157 | ('query_lte=%r', self.query_lte), |
||
158 | ('query_gt=%r', self.query_gt), |
||
159 | ('query_gte=%r', self.query_gte), |
||
160 | ] if mapping |
||
161 | ) |
||
162 | |||
163 | def __call__(self, event): |
||
164 | """ |
||
165 | Handles event. Returns True if all criteria matched. |
||
166 | """ |
||
167 | for key, value in self.query_eq: |
||
168 | evalue = event[key] |
||
169 | if evalue != value: |
||
170 | return False |
||
171 | for key, value in self.query_in: |
||
172 | evalue = event[key] |
||
173 | if evalue not in value: |
||
174 | return False |
||
175 | for key, value in self.query_contains: |
||
176 | evalue = event[key] |
||
177 | if value not in evalue: |
||
178 | return False |
||
179 | for key, value in self.query_startswith: |
||
180 | evalue = event[key] |
||
181 | if not evalue.startswith(value): |
||
182 | return False |
||
183 | for key, value in self.query_endswith: |
||
184 | evalue = event[key] |
||
185 | if not evalue.endswith(value): |
||
186 | return False |
||
187 | for key, value in self.query_regex: |
||
188 | evalue = event[key] |
||
189 | if not value.match(evalue): |
||
190 | return False |
||
191 | for key, value in self.query_gt: |
||
192 | evalue = event[key] |
||
193 | if not evalue > value: |
||
194 | return False |
||
195 | for key, value in self.query_gte: |
||
196 | evalue = event[key] |
||
197 | if not evalue >= value: |
||
198 | return False |
||
199 | for key, value in self.query_lt: |
||
200 | evalue = event[key] |
||
201 | if not evalue < value: |
||
202 | return False |
||
203 | for key, value in self.query_lte: |
||
204 | evalue = event[key] |
||
205 | if not evalue <= value: |
||
206 | return False |
||
207 | |||
208 | return True |
||
209 | |||
210 | def __or__(self, other): |
||
211 | """ |
||
212 | Convenience API so you can do ``Q() | Q()``. It converts that to ``Or(Q(), Q())``. |
||
213 | """ |
||
214 | return Or(self, other) |
||
215 | |||
216 | def __and__(self, other): |
||
217 | """ |
||
218 | Convenience API so you can do ``Q() & Q()``. It converts that to ``And(Q(), Q())``. |
||
219 | """ |
||
220 | return And(self, other) |
||
221 | |||
222 | def __invert__(self): |
||
223 | return Not(self) |
||
224 | |||
225 | __ror__ = __or__ |
||
226 | __rand__ = __and__ |
||
227 | |||
228 | |||
229 | class When(Fields.condition.actions): |
||
230 | """ |
||
231 | Runs ``actions`` when ``condition(event)`` is ``True``. |
||
232 | |||
233 | Actions take a single ``event`` argument. |
||
234 | """ |
||
235 | |||
236 | def __init__(self, condition, *actions): |
||
237 | if not actions: |
||
238 | raise TypeError('Must give at least one action.') |
||
239 | super(When, self).__init__(condition, tuple( |
||
240 | action() if inspect.isclass(action) and issubclass(action, Action) else action |
||
241 | for action in actions)) |
||
242 | |||
243 | def __str__(self): |
||
244 | return 'When(%s, %s)' % ( |
||
245 | self.condition, |
||
246 | ', '.join(repr(p) for p in self.actions) |
||
247 | ) |
||
248 | |||
249 | def __repr__(self): |
||
250 | return '<hunter.predicates.When: condition=%r, actions=%r>' % (self.condition, self.actions) |
||
251 | |||
252 | def __call__(self, event): |
||
253 | """ |
||
254 | Handles the event. |
||
255 | """ |
||
256 | if self.condition(event): |
||
257 | for action in self.actions: |
||
258 | action(event) |
||
259 | return True |
||
260 | else: |
||
261 | return False |
||
262 | |||
263 | def __or__(self, other): |
||
264 | return Or(self, other) |
||
265 | |||
266 | def __and__(self, other): |
||
267 | return And(self, other) |
||
268 | |||
269 | def __invert__(self): |
||
270 | return Not(self) |
||
271 | |||
272 | __ror__ = __or__ |
||
273 | __rand__ = __and__ |
||
274 | |||
275 | |||
276 | View Code Duplication | class And(Fields.predicates): |
|
277 | """ |
||
278 | `And` predicate. Exits at the first sub-predicate that returns ``False``. |
||
279 | """ |
||
280 | |||
281 | def __init__(self, *predicates): |
||
282 | self.predicates = predicates |
||
283 | |||
284 | def __str__(self): |
||
285 | return 'And(%s)' % ', '.join(str(p) for p in self.predicates) |
||
286 | |||
287 | def __repr__(self): |
||
288 | return '<hunter.predicates.And: predicates=%r>' % (self.predicates,) |
||
289 | |||
290 | def __call__(self, event): |
||
291 | """ |
||
292 | Handles the event. |
||
293 | """ |
||
294 | for predicate in self.predicates: |
||
295 | if not predicate(event): |
||
296 | return False |
||
297 | else: |
||
298 | return True |
||
299 | |||
300 | def __eq__(self, other): |
||
301 | if isinstance(other, And): |
||
302 | if len(self.predicates) != len(other.predicates): |
||
303 | return False |
||
304 | return set(self.predicates) == set(other.predicates) |
||
305 | return NotImplemented |
||
306 | |||
307 | def __or__(self, other): |
||
308 | return Or(self, other) |
||
309 | |||
310 | def __and__(self, other): |
||
311 | return And(*chain(self.predicates, other.predicates if isinstance(other, And) else (other,))) |
||
312 | |||
313 | def __invert__(self): |
||
314 | return Not(self) |
||
315 | |||
316 | def __hash__(self): |
||
317 | return hash(frozenset(self.predicates)) |
||
318 | |||
319 | __ror__ = __or__ |
||
320 | __rand__ = __and__ |
||
321 | |||
322 | |||
323 | View Code Duplication | class Or(Fields.predicates): |
|
324 | """ |
||
325 | `Or` predicate. Exits at first sub-predicate that returns ``True``. |
||
326 | """ |
||
327 | |||
328 | def __init__(self, *predicates): |
||
329 | self.predicates = predicates |
||
330 | |||
331 | def __str__(self): |
||
332 | return 'Or(%s)' % ', '.join(str(p) for p in self.predicates) |
||
333 | |||
334 | def __repr__(self): |
||
335 | return '<hunter.predicates.Or: predicates=%r>' % (self.predicates,) |
||
336 | |||
337 | def __call__(self, event): |
||
338 | """ |
||
339 | Handles the event. |
||
340 | """ |
||
341 | for predicate in self.predicates: |
||
342 | if predicate(event): |
||
343 | return True |
||
344 | else: |
||
345 | return False |
||
346 | |||
347 | def __eq__(self, other): |
||
348 | if isinstance(other, Or): |
||
349 | if len(self.predicates) != len(other.predicates): |
||
350 | return False |
||
351 | return set(self.predicates) == set(other.predicates) |
||
352 | return NotImplemented |
||
353 | |||
354 | def __or__(self, other): |
||
355 | return Or(*chain(self.predicates, other.predicates if isinstance(other, Or) else (other,))) |
||
356 | |||
357 | def __and__(self, other): |
||
358 | return And(self, other) |
||
359 | |||
360 | def __invert__(self): |
||
361 | return Not(self) |
||
362 | |||
363 | def __hash__(self): |
||
364 | return hash(frozenset(self.predicates)) |
||
365 | |||
366 | __ror__ = __or__ |
||
367 | __rand__ = __and__ |
||
368 | |||
369 | |||
370 | class Not(Fields.predicate): |
||
371 | """ |
||
372 | `Not` predicate. |
||
373 | """ |
||
374 | |||
375 | def __str__(self): |
||
376 | return 'Not(%s)' % self.predicate |
||
377 | |||
378 | def __repr__(self): |
||
379 | return '<hunter.predicates.Not: predicate=%r>' % self.predicate |
||
380 | |||
381 | def __call__(self, event): |
||
382 | """ |
||
383 | Handles the event. |
||
384 | """ |
||
385 | return not self.predicate(event) |
||
386 | |||
387 | def __or__(self, other): |
||
388 | if isinstance(other, Not): |
||
389 | return Not(And(self.predicate, other.predicate)) |
||
390 | else: |
||
391 | return Or(self, other) |
||
392 | |||
393 | def __and__(self, other): |
||
394 | if isinstance(other, Not): |
||
395 | return Not(Or(self.predicate, other.predicate)) |
||
396 | else: |
||
397 | return And(self, other) |
||
398 | |||
399 | def __invert__(self): |
||
400 | return self.predicate |
||
401 | |||
402 | __ror__ = __or__ |
||
403 | __rand__ = __and__ |
||
404 |