Total Complexity | 68 |
Total Lines | 280 |
Duplicated Lines | 93.57 % |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like dynamicserialize.dstypes.com.raytheon.uf.common.dataquery.requests.RequestConstraint 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 | # |
||
2 | # SOFTWARE HISTORY |
||
3 | # |
||
4 | # Date Ticket# Engineer Description |
||
5 | # ------------ ---------- ----------- -------------------------- |
||
6 | # Jun 01, 2016 5574 tgurney Initial creation |
||
7 | # Jun 27, 2016 5725 tgurney Add NOT IN |
||
8 | # Jul 22, 2016 2416 tgurney Add evaluate() |
||
9 | # Oct 05, 2018 mjames@ucar Python 3 types |
||
10 | # |
||
11 | # |
||
12 | |||
13 | import re |
||
14 | import six |
||
15 | from dynamicserialize.dstypes.com.raytheon.uf.common.time import DataTime |
||
16 | |||
17 | |||
18 | View Code Duplication | class RequestConstraint(object): |
|
|
|||
19 | |||
20 | TOLERANCE = 0.0001 |
||
21 | |||
22 | IN_PATTERN = re.compile(',\s?') |
||
23 | |||
24 | def __init__(self): |
||
25 | self.constraintValue = None |
||
26 | self.constraintType = None |
||
27 | |||
28 | def getConstraintValue(self): |
||
29 | return self.constraintValue |
||
30 | |||
31 | def setConstraintValue(self, constraintValue): |
||
32 | if hasattr(self, '_evalValue'): |
||
33 | del self._evalValue |
||
34 | self.constraintValue = constraintValue |
||
35 | |||
36 | def getConstraintType(self): |
||
37 | return self.constraintType |
||
38 | |||
39 | def setConstraintType(self, constraintType): |
||
40 | if hasattr(self, '_evalValue'): |
||
41 | del self._evalValue |
||
42 | self.constraintType = constraintType |
||
43 | |||
44 | def evaluate(self, value): |
||
45 | if not hasattr(self, '_evalValue'): |
||
46 | self._setupEvalValue() |
||
47 | |||
48 | if self.constraintType == 'EQUALS': |
||
49 | return self._evalEquals(value) |
||
50 | elif self.constraintType == 'NOT_EQUALS': |
||
51 | return not self._evalEquals(value) |
||
52 | elif self.constraintType == 'GREATER_THAN': |
||
53 | return self._evalGreaterThan(value) |
||
54 | elif self.constraintType == 'GREATER_THAN_EQUALS': |
||
55 | return self._evalGreaterThanEquals(value) |
||
56 | elif self.constraintType == 'LESS_THAN': |
||
57 | return self._evalLessThan(value) |
||
58 | elif self.constraintType == 'LESS_THAN_EQUALS': |
||
59 | return self._evalLessThanEquals(value) |
||
60 | elif self.constraintType == 'BETWEEN': |
||
61 | return self._evalBetween(value) |
||
62 | elif self.constraintType == 'IN': |
||
63 | return self._evalIn(value) |
||
64 | elif self.constraintType == 'NOT_IN': |
||
65 | return not self._evalIn(value) |
||
66 | elif self.constraintType == 'LIKE': |
||
67 | return self._evalLike(value) |
||
68 | # setupConstraintType already adds correct flags for ilike |
||
69 | # on regex pattern |
||
70 | elif self.constraintType == 'ILIKE': |
||
71 | return self._evalLike(value) |
||
72 | elif self.constraintType == 'ISNULL': |
||
73 | return self._evalIsNull(value) |
||
74 | elif self.constraintType == 'ISNOTNULL': |
||
75 | return not self._evalIsNull(value) |
||
76 | else: |
||
77 | errmsg = '{} is not a valid constraint type.' |
||
78 | raise ValueError(errmsg.format(self.constraintType)) |
||
79 | |||
80 | def _makeRegex(self, pattern, flags): |
||
81 | """Make a pattern using % wildcard into a regex""" |
||
82 | pattern = re.escape(pattern) |
||
83 | pattern = pattern.replace('\\%', '.*') |
||
84 | pattern = pattern.replace('\\_', '.') |
||
85 | pattern = pattern + '$' |
||
86 | return re.compile(pattern, flags) |
||
87 | |||
88 | def _setupEvalValue(self): |
||
89 | if self.constraintType == 'BETWEEN': |
||
90 | self._evalValue = self.constraintValue.split('--') |
||
91 | self._evalValue[0] = self._adjustValueType(self._evalValue[0]) |
||
92 | self._evalValue[1] = self._adjustValueType(self._evalValue[1]) |
||
93 | elif self.constraintType in ('IN', 'NOT_IN'): |
||
94 | splitValue = self.IN_PATTERN.split(self.constraintValue) |
||
95 | self._evalValue = { |
||
96 | self._adjustValueType(value) |
||
97 | for value in splitValue |
||
98 | } |
||
99 | # if collection now contains multiple types we have to force |
||
100 | # everything to string instead |
||
101 | initialType = next(iter(self._evalValue)).__class__ |
||
102 | for item in self._evalValue: |
||
103 | if item.__class__ is not initialType: |
||
104 | self._evalValue = {str(value) for value in splitValue} |
||
105 | break |
||
106 | elif self.constraintType == 'LIKE': |
||
107 | self._evalValue = self._makeRegex(self.constraintValue, re.DOTALL) |
||
108 | elif self.constraintType == 'ILIKE': |
||
109 | self._evalValue = self._makeRegex(self.constraintValue, re.IGNORECASE | re.DOTALL) |
||
110 | elif self.constraintValue is None: |
||
111 | self._evalValue = None |
||
112 | else: |
||
113 | self._evalValue = self._adjustValueType(self.constraintValue) |
||
114 | |||
115 | def _adjustValueType(self, value): |
||
116 | """ |
||
117 | Try to take part of a constraint value, encoded as a string, and |
||
118 | return it as its 'true type'. |
||
119 | |||
120 | _adjustValueType('3.0') -> 3.0 |
||
121 | _adjustValueType('3') -> 3.0 |
||
122 | _adjustValueType('a string') -> 'a string' |
||
123 | """ |
||
124 | try: |
||
125 | return float(value) |
||
126 | except ValueError: |
||
127 | pass |
||
128 | try: |
||
129 | return DataTime(value) |
||
130 | except ValueError: |
||
131 | pass |
||
132 | return value |
||
133 | |||
134 | def _matchType(self, value, otherValue): |
||
135 | """ |
||
136 | Return value coerced to be the same type as otherValue. If this is |
||
137 | not possible, just return value unmodified. |
||
138 | """ |
||
139 | if not isinstance(value, otherValue.__class__): |
||
140 | try: |
||
141 | return otherValue.__class__(value) |
||
142 | except ValueError: |
||
143 | pass |
||
144 | return value |
||
145 | |||
146 | def _evalEquals(self, value): |
||
147 | value = self._matchType(value, self._evalValue) |
||
148 | if isinstance(value, float): |
||
149 | return abs(float(self._evalValue) - value) < self.TOLERANCE |
||
150 | return value == self._evalValue |
||
151 | |||
152 | def _evalGreaterThan(self, value): |
||
153 | value = self._matchType(value, self._evalValue) |
||
154 | return value > self._evalValue |
||
155 | |||
156 | def _evalGreaterThanEquals(self, value): |
||
157 | value = self._matchType(value, self._evalValue) |
||
158 | return value >= self._evalValue |
||
159 | |||
160 | def _evalLessThan(self, value): |
||
161 | value = self._matchType(value, self._evalValue) |
||
162 | return value < self._evalValue |
||
163 | |||
164 | def _evalLessThanEquals(self, value): |
||
165 | value = self._matchType(value, self._evalValue) |
||
166 | return value <= self._evalValue |
||
167 | |||
168 | def _evalBetween(self, value): |
||
169 | value = self._matchType(value, self._evalValue[0]) |
||
170 | return self._evalValue[0] <= value <= self._evalValue[1] |
||
171 | |||
172 | def _evalIn(self, value): |
||
173 | anEvalValue = next(iter(self._evalValue)) |
||
174 | if isinstance(anEvalValue, float): |
||
175 | for otherValue in self._evalValue: |
||
176 | try: |
||
177 | if abs(otherValue - float(value)) < self.TOLERANCE: |
||
178 | return True |
||
179 | except ValueError: |
||
180 | pass |
||
181 | return False |
||
182 | value = self._matchType(value, anEvalValue) |
||
183 | return value in self._evalValue |
||
184 | |||
185 | def _evalLike(self, value): |
||
186 | value = self._matchType(value, self._evalValue) |
||
187 | if self.constraintValue == '%': |
||
188 | return True |
||
189 | return self._evalValue.match(value) is not None |
||
190 | |||
191 | def _evalIsNull(self, value): |
||
192 | return value is None or value == 'null' |
||
193 | |||
194 | # DAF-specific stuff begins here ########################################## |
||
195 | |||
196 | CONSTRAINT_MAP = {'=': 'EQUALS', |
||
197 | '!=': 'NOT_EQUALS', |
||
198 | '>': 'GREATER_THAN', |
||
199 | '>=': 'GREATER_THAN_EQUALS', |
||
200 | '<': 'LESS_THAN', |
||
201 | '<=': 'LESS_THAN_EQUALS', |
||
202 | 'IN': 'IN', |
||
203 | 'NOT IN': 'NOT_IN' |
||
204 | } |
||
205 | |||
206 | @staticmethod |
||
207 | def _stringify(value): |
||
208 | if six.PY2: |
||
209 | if isinstance(value, (str, int, long, bool, float, unicode)): |
||
210 | return str(value) |
||
211 | else: |
||
212 | # Collections are not allowed; they are handled separately. |
||
213 | # Arbitrary objects are not allowed because the string |
||
214 | # representation may not be sufficient to reconstruct the object. |
||
215 | raise TypeError('Constraint values of type ' + repr(type(value)) + |
||
216 | 'are not allowed') |
||
217 | else: |
||
218 | if isinstance(value, (str, int, bool, float)): |
||
219 | return str(value) |
||
220 | else: |
||
221 | # Collections are not allowed; they are handled separately. |
||
222 | # Arbitrary objects are not allowed because the string |
||
223 | # representation may not be sufficient to reconstruct the object. |
||
224 | raise TypeError('Constraint values of type ' + repr(type(value)) + |
||
225 | 'are not allowed') |
||
226 | |||
227 | @classmethod |
||
228 | def _constructIn(cls, constraintType, constraintValue): |
||
229 | """Build a new "IN" or "NOT IN" constraint from an iterable.""" |
||
230 | try: |
||
231 | iterator = iter(constraintValue) |
||
232 | except TypeError: |
||
233 | raise TypeError("value for IN / NOT IN constraint must be an iterable") |
||
234 | stringValue = ', '.join(cls._stringify(item) for item in iterator) |
||
235 | if not stringValue: |
||
236 | raise ValueError('cannot use IN / NOT IN with empty collection') |
||
237 | obj = cls() |
||
238 | obj.setConstraintType(constraintType) |
||
239 | obj.setConstraintValue(stringValue) |
||
240 | return obj |
||
241 | |||
242 | @classmethod |
||
243 | def _constructEq(cls, constraintType, constraintValue): |
||
244 | """Build a new = or != constraint. Handle None specially by making an |
||
245 | "is null" or "is not null" instead. |
||
246 | """ |
||
247 | obj = cls() |
||
248 | if constraintValue is None: |
||
249 | if constraintType == 'EQUALS': |
||
250 | obj.setConstraintType('ISNULL') |
||
251 | elif constraintType == 'NOT_EQUALS': |
||
252 | obj.setConstraintType('ISNOTNULL') |
||
253 | else: |
||
254 | obj = cls._construct(constraintType, constraintValue) |
||
255 | return obj |
||
256 | |||
257 | @classmethod |
||
258 | def _construct(cls, constraintType, constraintValue): |
||
259 | """Build a new constraint.""" |
||
260 | stringValue = cls._stringify(constraintValue) |
||
261 | obj = cls() |
||
262 | obj.setConstraintType(constraintType) |
||
263 | obj.setConstraintValue(stringValue) |
||
264 | return obj |
||
265 | |||
266 | @classmethod |
||
267 | def new(cls, operator, constraintValue): |
||
268 | """Build a new RequestConstraint.""" |
||
269 | try: |
||
270 | constraintType = cls.CONSTRAINT_MAP[operator.upper()] |
||
271 | except KeyError: |
||
272 | errmsg = '{} is not a valid operator. Valid operators are: {}' |
||
273 | validOperators = list(sorted(cls.CONSTRAINT_MAP.keys())) |
||
274 | raise ValueError(errmsg.format(operator, validOperators)) |
||
275 | if constraintType in ('IN', 'NOT_IN'): |
||
276 | return cls._constructIn(constraintType, constraintValue) |
||
277 | elif constraintType in {'EQUALS', 'NOT_EQUALS'}: |
||
278 | return cls._constructEq(constraintType, constraintValue) |
||
279 | return cls._construct(constraintType, constraintValue) |
||
280 |