Completed
Push — master ( 86257c...b08d4e )
by Ionel Cristian
61:05 queued 60:12
created

src.hunter.Or.__hash__()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 2
rs 10
cc 1
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 != 'tracer')
14
ALLOWED_OPERATORS = 'startswith', 'endswith', 'in', 'contains', 'regex'
15
16
17
def _sloppy_hash(obj):
18
    try:
19
        return hash(obj)
20
    except TypeError:
21
        return 'id(%x)' % id(obj)
22
23
24
class Query(Fields.query_eq.query_startswith.query_endswith.query_in.query_contains):
25
    """
26
    A query class.
27
28
    See :class:`hunter.Event` for fields that can be filtered on.
29
    """
30
    def __init__(self, **query):
31
        """
32
        Args:
33
            query: criteria to match on.
34
35
                Accepted arguments: ``arg``, ``code``, ``filename``, ``frame``, ``fullsource``, ``function``,
36
                ``globals``, ``kind``, ``lineno``, ``locals``, ``module``, ``source``, ``stdlib``, ``tracer``.
37
        """
38
        query_eq = {}
39
        query_startswith = {}
40
        query_endswith = {}
41
        query_in = {}
42
        query_contains = {}
43
        query_regex = {}
44
45
        for key, value in query.items():
46
            parts = [p for p in key.split('_') if p]
47
            count = len(parts)
48
            if count > 2:
49
                raise TypeError('Unexpected argument %r. Must be one of %s with optional operators like: %s' % (
50
                    key, ALLOWED_KEYS, ALLOWED_OPERATORS
51
                ))
52
            elif count == 2:
53
                prefix, operator = parts
54
                if operator == 'startswith':
55
                    if not isinstance(value, string_types):
56
                        if not isinstance(value, (list, set, tuple)):
57
                            raise ValueError('Value %r for %r is invalid. Must be a string, list, tuple or set.' % (value, key))
58
                        value = tuple(value)
59
                    mapping = query_startswith
60
                elif operator == 'endswith':
61
                    if not isinstance(value, string_types):
62
                        if not isinstance(value, (list, set, tuple)):
63
                            raise ValueError('Value %r for %r is invalid. Must be a string, list, tuple or set.' % (value, key))
64
                        value = tuple(value)
65
                    mapping = query_endswith
66
                elif operator == 'in':
67
                    mapping = query_in
68
                elif operator == 'contains':
69
                    mapping = query_contains
70
                elif operator == 'regex':
71
                    value = re.compile(value)
72
                    mapping = query_regex
73
                else:
74
                    raise TypeError('Unexpected operator %r. Must be one of %s.'.format(operator, ALLOWED_OPERATORS))
75
            else:
76
                mapping = query_eq
77
                prefix = key
78
79
            if prefix not in ALLOWED_KEYS:
80
                raise TypeError('Unexpected argument %r. Must be one of %s.' % (key, ALLOWED_KEYS))
81
82
            mapping[prefix] = value
83
84
        self.query_eq = tuple(sorted(query_eq.items()))
85
        self.query_startswith = tuple(sorted(query_startswith.items()))
86
        self.query_endswith = tuple(sorted(query_endswith.items()))
87
        self.query_in = tuple(sorted(query_in.items()))
88
        self.query_contains = tuple(sorted(query_contains.items()))
89
        self.query_regex = tuple(sorted(query_regex.items()))
90
91
    def __str__(self):
92
        return 'Query(%s)' % (
93
            ', '.join(
94
                ', '.join('%s%s=%r' % (key, kind, value) for key, value in mapping)
95
                for kind, mapping in [
96
                    ('', self.query_eq),
97
                    ('_in', self.query_in),
98
                    ('_contains', self.query_contains),
99
                    ('_startswith', self.query_startswith),
100
                    ('_endswith', self.query_endswith),
101
                    ('_regex', self.query_regex),
102
                ] if mapping
103
            )
104
        )
105
106
    def __repr__(self):
107
        return '<hunter.predicates.Query: %s>' % ' '.join(
108
            fmt % (mapping,) for fmt, mapping in [
109
                ('query_eq=%r', self.query_eq),
110
                ('query_in=%r', self.query_in),
111
                ('query_contains=%r', self.query_contains),
112
                ('query_startswith=%r', self.query_startswith),
113
                ('query_endswith=%r', self.query_endswith),
114
                ('query_regex=%r', self.query_regex),
115
            ] if mapping
116
        )
117
118
    def __call__(self, event):
119
        """
120
        Handles event. Returns True if all criteria matched.
121
        """
122
        for key, value in self.query_eq:
123
            evalue = event[key]
124
            if evalue != value:
125
                return False
126
        for key, value in self.query_in:
127
            evalue = event[key]
128
            if evalue not in value:
129
                return False
130
        for key, value in self.query_contains:
131
            evalue = event[key]
132
            if value not in evalue:
133
                return False
134
        for key, value in self.query_startswith:
135
            evalue = event[key]
136
            if not evalue.startswith(value):
137
                return False
138
        for key, value in self.query_endswith:
139
            evalue = event[key]
140
            if not evalue.endswith(value):
141
                return False
142
        for key, value in self.query_regex:
143
            evalue = event[key]
144
            if not value.match(evalue):
145
                return False
146
147
        return True
148
149
    def __or__(self, other):
150
        """
151
        Convenience API so you can do ``Q() | Q()``. It converts that to ``Or(Q(), Q())``.
152
        """
153
        return Or(self, other)
154
155
    def __and__(self, other):
156
        """
157
        Convenience API so you can do ``Q() & Q()``. It converts that to ``And(Q(), Q())``.
158
        """
159
        return And(self, other)
160
161
    def __invert__(self):
162
        return Not(self)
163
164
165
class When(Fields.condition.actions):
166
    """
167
    Runs ``actions`` when ``condition(event)`` is ``True``.
168
169
    Actions take a single ``event`` argument.
170
    """
171
172
    def __init__(self, condition, *actions):
173
        if not actions:
174
            raise TypeError('Must give at least one action.')
175
        super(When, self).__init__(condition, tuple(
176
            action() if inspect.isclass(action) and issubclass(action, Action) else action
177
            for action in actions))
178
179
    def __str__(self):
180
        return 'When(%s, %s)' % (
181
            self.condition,
182
            ', '.join(repr(p) for p in self.actions)
183
        )
184
185
    def __repr__(self):
186
        return '<hunter.predicates.When: condition=%r, actions=%r>' % (self.condition, self.actions)
187
188
    def __call__(self, event):
189
        """
190
        Handles the event.
191
        """
192
        if self.condition(event):
193
            for action in self.actions:
194
                action(event)
195
            return True
196
        else:
197
            return False
198
199
    def __or__(self, other):
200
        return Or(self, other)
201
202
    def __and__(self, other):
203
        return And(self, other)
204
205
206
class And(Fields.predicates):
207
    """
208
    `And` predicate. Exits at the first sub-predicate that returns ``False``.
209
    """
210
211
    def __init__(self, *predicates):
212
        self.predicates = predicates
213
214
    def __str__(self):
215
        return 'And(%s)' % ', '.join(str(p) for p in self.predicates)
216
217
    def __repr__(self):
218
        return '<hunter.predicates.And: predicates=%r>' % (self.predicates,)
219
220
    def __call__(self, event):
221
        """
222
        Handles the event.
223
        """
224
        for predicate in self.predicates:
225
            if not predicate(event):
226
                return False
227
        else:
228
            return True
229
230
    def __eq__(self, other):
231
        if isinstance(other, And):
232
            if len(self.predicates) != len(other.predicates):
233
                return False
234
            return set(self.predicates) == set(other.predicates)
235
        return NotImplemented
236
237
    def __or__(self, other):
238
        return Or(self, other)
239
240
    def __and__(self, other):
241
        return And(*chain(self.predicates, other.predicates if isinstance(other, And) else (other,)))
242
243
    def __invert__(self):
244
        return Not(self)
245
246
    def __hash__(self):
247
        return hash(self.predicates)
248
249
250
class Or(Fields.predicates):
251
    """
252
    `Or` predicate. Exits at first sub-predicate that returns ``True``.
253
    """
254
255
    def __init__(self, *predicates):
256
        self.predicates = predicates
257
258
    def __str__(self):
259
        return 'Or(%s)' % ', '.join(str(p) for p in self.predicates)
260
261
    def __repr__(self):
262
        return '<hunter.predicates.Or: predicates=%r>' % (self.predicates,)
263
264
    def __call__(self, event):
265
        """
266
        Handles the event.
267
        """
268
        for predicate in self.predicates:
269
            if predicate(event):
270
                return True
271
        else:
272
            return False
273
274
    def __eq__(self, other):
275
        if isinstance(other, Or):
276
            if len(self.predicates) != len(other.predicates):
277
                return False
278
            return set(self.predicates) == set(other.predicates)
279
        return NotImplemented
280
281
    def __or__(self, other):
282
        return Or(*chain(self.predicates, other.predicates if isinstance(other, Or) else (other,)))
283
284
    def __and__(self, other):
285
        return And(self, other)
286
287
    def __invert__(self):
288
        return Not(self)
289
290
    def __hash__(self):
291
        return hash(self.predicates)
292
293
294
class Not(Fields.predicate):
295
    """
296
    `Not` predicate.
297
    """
298
299
    def __str__(self):
300
        return 'Not(%s)' % self.predicate
301
302
    def __repr__(self):
303
        return '<hunter.predicates.Not: predicate=%r>' % self.predicate
304
305
    def __call__(self, event):
306
        """
307
        Handles the event.
308
        """
309
        return not self.predicate(event)
310
311
    def __or__(self, other):
312
        if isinstance(other, Not):
313
            return Not(And(self.predicate, other.predicate))
314
        else:
315
            return Or(self, other)
316
317
    def __and__(self, other):
318
        if isinstance(other, Not):
319
            return Not(Or(self.predicate, other.predicate))
320
        else:
321
            return And(self, other)
322
323
    def __invert__(self):
324
        return self.predicate
325