Completed
Push — master ( c47a1a...d9aecd )
by Ionel Cristian
33s
created

When.__invert__()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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