| 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 |