Total Complexity | 40 |
Total Lines | 132 |
Duplicated Lines | 0 % |
Complex classes like src.hunter.Query often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | from __future__ import absolute_import |
||
17 | class Query(Fields.query_eq.query_startswith.query_endswith.query_in.query_contains): |
||
18 | """ |
||
19 | A query class. |
||
20 | |||
21 | See :class:`hunter.Event` for fields that can be filtered on. |
||
22 | """ |
||
23 | def __init__(self, **query): |
||
24 | """ |
||
25 | Args: |
||
26 | query: criteria to match on. |
||
27 | |||
28 | Accepted arguments: ``arg``, ``code``, ``filename``, ``frame``, ``fullsource``, ``function``, |
||
29 | ``globals``, ``kind``, ``lineno``, ``locals``, ``module``, ``source``, ``stdlib``, ``tracer``. |
||
30 | """ |
||
31 | self.query_eq = {} |
||
32 | self.query_startswith = {} |
||
33 | self.query_endswith = {} |
||
34 | self.query_in = {} |
||
35 | self.query_contains = {} |
||
36 | self.query_regex = {} |
||
37 | |||
38 | for key, value in query.items(): |
||
39 | parts = [p for p in key.split('_') if p] |
||
40 | count = len(parts) |
||
41 | if count > 2: |
||
42 | raise TypeError('Unexpected argument %r. Must be one of %s with optional operators like: %s' % ( |
||
43 | key, ALLOWED_KEYS, ALLOWED_OPERATORS |
||
44 | )) |
||
45 | elif count == 2: |
||
46 | prefix, operator = parts |
||
47 | if operator not in ALLOWED_OPERATORS: |
||
48 | raise TypeError('Unexpected operator %r. Must be one of %s.'.format(operator, ALLOWED_OPERATORS)) |
||
49 | elif operator == 'startswith': |
||
50 | if not isinstance(value, string_types): |
||
51 | if not isinstance(value, (list, set, tuple)): |
||
52 | raise ValueError('Value %r for %r is invalid. Must be a string, list, tuple or set.' % (value, key)) |
||
53 | value = tuple(value) |
||
54 | mapping = self.query_startswith |
||
55 | elif operator == 'endswith': |
||
56 | if not isinstance(value, string_types): |
||
57 | if not isinstance(value, (list, set, tuple)): |
||
58 | raise ValueError('Value %r for %r is invalid. Must be a string, list, tuple or set.' % (value, key)) |
||
59 | value = tuple(value) |
||
60 | mapping = self.query_endswith |
||
61 | elif operator == 'in': |
||
62 | mapping = self.query_in |
||
63 | elif operator == 'contains': |
||
64 | mapping = self.query_contains |
||
65 | elif operator == 'regex': |
||
66 | value = re.compile(value) |
||
67 | mapping = self.query_regex |
||
68 | else: |
||
69 | mapping = self.query_eq |
||
70 | prefix = key |
||
71 | |||
72 | if prefix not in ALLOWED_KEYS: |
||
73 | raise TypeError('Unexpected argument %r. Must be one of %s.' % (key, ALLOWED_KEYS)) |
||
74 | |||
75 | mapping[prefix] = value |
||
76 | |||
77 | def __str__(self): |
||
78 | return 'Query(%s)' % ( |
||
79 | ', '.join( |
||
80 | ', '.join('%s%s=%r' % (key, kind, value) for key, value in mapping.items()) |
||
81 | for kind, mapping in [ |
||
82 | ('', self.query_eq), |
||
83 | ('_in', self.query_in), |
||
84 | ('_contains', self.query_contains), |
||
85 | ('_startswith', self.query_startswith), |
||
86 | ('_endswith', self.query_endswith), |
||
87 | ('_regex', self.query_regex), |
||
88 | ] if mapping |
||
89 | ) |
||
90 | ) |
||
91 | |||
92 | def __repr__(self): |
||
93 | return '<hunter._predicates.Query: %s>' % ' '.join( |
||
94 | fmt % mapping for fmt, mapping in [ |
||
95 | ('query_eq=%r', self.query_eq), |
||
96 | ('query_in=%r', self.query_in), |
||
97 | ('query_contains=%r', self.query_contains), |
||
98 | ('query_startswith=%r', self.query_startswith), |
||
99 | ('query_endswith=%r', self.query_endswith), |
||
100 | ('query_regex=%r', self.query_regex), |
||
101 | ] if mapping |
||
102 | ) |
||
103 | |||
104 | def __call__(self, event): |
||
105 | """ |
||
106 | Handles event. Returns True if all criteria matched. |
||
107 | """ |
||
108 | for key, value in self.query_eq.items(): |
||
109 | evalue = event[key] |
||
110 | if evalue != value: |
||
111 | return False |
||
112 | for key, value in self.query_in.items(): |
||
113 | evalue = event[key] |
||
114 | if evalue not in value: |
||
115 | return False |
||
116 | for key, value in self.query_contains.items(): |
||
117 | evalue = event[key] |
||
118 | if value not in evalue: |
||
119 | return False |
||
120 | for key, value in self.query_startswith.items(): |
||
121 | evalue = event[key] |
||
122 | if not evalue.startswith(value): |
||
123 | return False |
||
124 | for key, value in self.query_endswith.items(): |
||
125 | evalue = event[key] |
||
126 | if not evalue.endswith(value): |
||
127 | return False |
||
128 | for key, value in self.query_regex.items(): |
||
129 | evalue = event[key] |
||
130 | if not value.match(evalue): |
||
131 | return False |
||
132 | |||
133 | return True |
||
134 | |||
135 | def __or__(self, other): |
||
136 | """ |
||
137 | Convenience API so you can do ``Q() | Q()``. It converts that to ``Or(Q(), Q())``. |
||
138 | """ |
||
139 | return Or(self, other) |
||
140 | |||
141 | def __and__(self, other): |
||
142 | """ |
||
143 | Convenience API so you can do ``Q() & Q()``. It converts that to ``And(Q(), Q())``. |
||
144 | """ |
||
145 | return And(self, other) |
||
146 | |||
147 | def __invert__(self): |
||
148 | return Not(self) |
||
149 | |||
292 |