Package intermine :: Module constraints
[hide private]
[frames] | no frames]

Source Code for Module intermine.constraints

  1  import re 
  2  import string 
  3  from .pathfeatures import PathFeature, PATH_PATTERN 
  4  from .util import ReadableException 
5 6 -class Constraint(PathFeature):
7 child_type = "constraint"
8
9 -class LogicNode(object):
10 - def __add__(self, other):
11 if not isinstance(other, LogicNode): 12 return NotImplemented 13 else: 14 return LogicGroup(self, 'AND', other)
15 - def __and__(self, other):
16 if not isinstance(other, LogicNode): 17 return NotImplemented 18 else: 19 return LogicGroup(self, 'AND', other)
20 - def __or__(self, other):
21 if not isinstance(other, LogicNode): 22 return NotImplemented 23 else: 24 return LogicGroup(self, 'OR', other)
25
26 -class LogicGroup(LogicNode):
27 LEGAL_OPS = frozenset(['AND', 'OR'])
28 - def __init__(self, left, op, right, parent=None):
29 if not op in self.LEGAL_OPS: 30 raise TypeError(op + " is not a legal logical operation") 31 self.parent = parent 32 self.left = left 33 self.right = right 34 self.op = op 35 for node in [self.left, self.right]: 36 if isinstance(node, LogicGroup): 37 node.parent = self
38
39 - def __repr__(self):
40 return '<' + self.__class__.__name__ + ': ' + str(self) + '>'
41 - def __str__(self):
42 core = ' '.join(map(str, [self.left, self.op.lower(), self.right])) 43 return '(' + core + ')' if self.parent and self.op != self.parent.op else core
44 - def get_codes(self):
45 codes = [] 46 for node in [self.left, self.right]: 47 if isinstance(node, LogicGroup): 48 codes.extend(node.get_codes()) 49 else: 50 codes.append(node.code) 51 return codes
52
53 -class LogicParseError(ReadableException):
54 pass
55
56 -class LogicParser(object):
57
58 - def __init__(self, query):
59 self._query = query
60
61 - def get_constraint(self, code):
62 return self._query.get_constraint(code)
63
64 - def get_priority(self, op):
65 return { 66 "AND": 2, 67 "OR" : 1, 68 "(" : 3, 69 ")" : 3 70 }.get(op)
71 72 ops = { 73 "AND" : "AND", 74 "&" : "AND", 75 "&&" : "AND", 76 "OR" : "OR", 77 "|" : "OR", 78 "||" : "OR", 79 "(" : "(", 80 ")" : ")" 81 } 82
83 - def parse(self, logic_str):
84 def flatten(l): 85 ret = [] 86 for item in l: 87 if isinstance(item, list): 88 ret.extend(item) 89 else: 90 ret.append(item) 91 return ret
92 logic_str = logic_str.upper() 93 tokens = re.split("\s+", logic_str) 94 tokens = flatten([self.ops[x] if x in self.ops else re.split("\b", x) for x in tokens]) 95 tokens = flatten([list(x) if re.search("[()]", x) else x for x in tokens]) 96 self.check_syntax(tokens) 97 postfix_tokens = self.infix_to_postfix(tokens) 98 abstract_syntax_tree = self.postfix_to_tree(postfix_tokens) 99 return abstract_syntax_tree
100
101 - def check_syntax(self, infix_tokens):
102 need_an_op = False 103 need_binary_op_or_closing_bracket = False 104 processed = [] 105 open_brackets = 0 106 for token in infix_tokens: 107 if token not in self.ops: 108 if need_an_op: 109 raise LogicParseError("Expected an operator after: '" + ' '.join(processed) + "'" 110 + " - but got: '" + token + "'") 111 if need_binary_op_or_closing_bracket: 112 raise LogicParseError("Logic grouping error after: '" + ' '.join(processed) + "'" 113 + " - expected an operator or a closing bracket") 114 115 need_an_op = True 116 else: 117 need_an_op = False 118 if token == "(": 119 if processed and processed[-1] not in self.ops: 120 raise LogicParseError("Logic grouping error after: '" + ' '.join(processed) + "'" 121 + " - got an unexpeced opening bracket") 122 if need_binary_op_or_closing_bracket: 123 raise LogicParseError("Logic grouping error after: '" + ' '.join(processed) + "'" 124 + " - expected an operator or a closing bracket") 125 126 open_brackets += 1 127 elif token == ")": 128 need_binary_op_or_closing_bracket = True 129 open_brackets -= 1 130 else: 131 need_binary_op_or_closing_bracket = False 132 processed.append(token) 133 if open_brackets != 0: 134 if open_brackets < 0: 135 message = "Unmatched closing bracket in: " 136 else: 137 message = "Unmatched opening bracket in: " 138 raise LogicParseError(message + '"' + ' '.join(infix_tokens) + '"')
139
140 - def infix_to_postfix(self, infix_tokens):
141 stack = [] 142 postfix_tokens = [] 143 for token in infix_tokens: 144 if token not in self.ops: 145 postfix_tokens.append(token) 146 else: 147 op = token 148 if op == "(": 149 stack.append(token) 150 elif op == ")": 151 while stack: 152 last_op = stack.pop() 153 if last_op == "(": 154 if stack: 155 previous_op = stack.pop() 156 if previous_op != "(": postfix_tokens.append(previous_op) 157 break 158 else: 159 postfix_tokens.append(last_op) 160 else: 161 while stack and self.get_priority(stack[-1]) <= self.get_priority(op): 162 prev_op = stack.pop() 163 if prev_op != "(": postfix_tokens.append(prev_op) 164 stack.append(op) 165 while stack: postfix_tokens.append(stack.pop()) 166 return postfix_tokens
167
168 - def postfix_to_tree(self, postfix_tokens):
169 stack = [] 170 for token in postfix_tokens: 171 if token not in self.ops: 172 stack.append(token) 173 else: 174 op = token 175 right = stack.pop() 176 left = stack.pop() 177 right = right if isinstance(right, LogicGroup) else self.get_constraint(right) 178 left = left if isinstance(left, LogicGroup) else self.get_constraint(left) 179 stack.append(LogicGroup(left, op, right)) 180 assert len(stack) == 1, "Tree doesn't have a unique root" 181 return stack.pop()
182
183 -class CodedConstraint(Constraint, LogicNode):
184 OPS = set([])
185 - def __init__(self, path, op, code="A"):
186 if op not in self.OPS: 187 raise TypeError(op + " not in " + str(self.OPS)) 188 self.op = op 189 self.code = code 190 super(CodedConstraint, self).__init__(path)
191 - def __str__(self):
192 return self.code
193 - def to_string(self):
194 s = super(CodedConstraint, self).to_string() 195 return " ".join([s, self.op])
196 - def to_dict(self):
197 d = super(CodedConstraint, self).to_dict() 198 d.update(op=self.op, code=self.code) 199 return d
200
201 -class UnaryConstraint(CodedConstraint):
202 OPS = set(['IS NULL', 'IS NOT NULL'])
203
204 -class BinaryConstraint(CodedConstraint):
205 OPS = set(['=', '!=', '<', '>', '<=', '>=', 'LIKE', 'NOT LIKE'])
206 - def __init__(self, path, op, value, code="A"):
207 self.value = value 208 super(BinaryConstraint, self).__init__(path, op, code)
209
210 - def to_string(self):
211 s = super(BinaryConstraint, self).to_string() 212 return " ".join([s, str(self.value)])
213 - def to_dict(self):
214 d = super(BinaryConstraint, self).to_dict() 215 d.update(value=str(self.value)) 216 return d
217
218 -class ListConstraint(CodedConstraint):
219 OPS = set(['IN', 'NOT IN'])
220 - def __init__(self, path, op, list_name, code="A"):
221 self.list_name = list_name 222 super(ListConstraint, self).__init__(path, op, code)
223
224 - def to_string(self):
225 s = super(ListConstraint, self).to_string() 226 return " ".join([s, str(self.list_name)])
227 - def to_dict(self):
228 d = super(ListConstraint, self).to_dict() 229 d.update(value=str(self.list_name)) 230 return d
231
232 -class LoopConstraint(CodedConstraint):
233 OPS = set(['IS', 'IS NOT']) 234 SERIALISED_OPS = {'IS':'=', 'IS NOT':'!='}
235 - def __init__(self, path, op, loopPath, code="A"):
236 self.loopPath = loopPath 237 super(LoopConstraint, self).__init__(path, op, code)
238
239 - def to_string(self):
240 s = super(LoopConstraint, self).to_string() 241 return " ".join([s, self.loopPath])
242 - def to_dict(self):
243 d = super(LoopConstraint, self).to_dict() 244 d.update(loopPath=self.loopPath, op=self.SERIALISED_OPS[self.op]) 245 return d
246
247 -class TernaryConstraint(BinaryConstraint):
248 OPS = set(['LOOKUP'])
249 - def __init__(self, path, op, value, extra_value=None, code="A"):
250 self.extra_value = extra_value 251 super(TernaryConstraint, self).__init__(path, op, value, code)
252
253 - def to_string(self):
254 s = super(TernaryConstraint, self).to_string() 255 if self.extra_value is None: 256 return s 257 else: 258 return " ".join([s, 'IN', self.extra_value])
259 - def to_dict(self):
260 d = super(TernaryConstraint, self).to_dict() 261 if self.extra_value is not None: 262 d.update(extraValue=self.extra_value) 263 return d
264
265 -class MultiConstraint(CodedConstraint):
266 OPS = set(['ONE OF', 'NONE OF'])
267 - def __init__(self, path, op, values, code="A"):
268 if not isinstance(values, list): 269 raise TypeError("values must be a list, not " + str(type(values))) 270 self.values = values 271 super(MultiConstraint, self).__init__(path, op, code)
272
273 - def to_string(self):
274 s = super(MultiConstraint, self).to_string() 275 return ' '.join([s, str(self.values)])
276 - def to_dict(self):
277 d = super(MultiConstraint, self).to_dict() 278 d.update(value=self.values) 279 return d
280
281 -class SubClassConstraint(Constraint):
282 - def __init__(self, path, subclass):
283 if not PATH_PATTERN.match(subclass): 284 raise TypeError 285 self.subclass = subclass 286 super(SubClassConstraint, self).__init__(path)
287 - def to_string(self):
288 s = super(SubClassConstraint, self).to_string() 289 return s + ' ISA ' + self.subclass
290 - def to_dict(self):
291 d = super(SubClassConstraint, self).to_dict() 292 d.update(type=self.subclass) 293 return d
294
295 296 -class TemplateConstraint(object):
297 REQUIRED = "locked" 298 OPTIONAL_ON = "on" 299 OPTIONAL_OFF = "off"
300 - def __init__(self, editable=True, optional="locked"):
301 self.editable = editable 302 if optional == TemplateConstraint.REQUIRED: 303 self.optional = False 304 self.switched_on = True 305 else: 306 self.optional = True 307 if optional == TemplateConstraint.OPTIONAL_ON: 308 self.switched_on = True 309 elif optional == TemplateConstraint.OPTIONAL_OFF: 310 self.switched_on = False 311 else: 312 raise TypeError("Bad value for optional")
313 314 @property
315 - def required(self):
316 return not self.optional
317 318 @property
319 - def switched_off(self):
320 return not self.switched_on
321
322 - def get_switchable_status(self):
323 if not self.optional: 324 return "locked" 325 else: 326 switch = "on" if self.switched_on else "off" 327 return switch
328
329 - def to_string(self):
330 editable = "editable" if self.editable else "non-editable" 331 return '(' + editable + ", " + self.get_switchable_status() + ')'
332 - def separate_arg_sets(self, args):
333 c_args = {} 334 t_args = {} 335 for k, v in args.items(): 336 if k == "editable": 337 t_args[k] = v == "true" 338 elif k == "optional": 339 t_args[k] = v 340 else: 341 c_args[k] = v 342 return (c_args, t_args)
343
344 -class TemplateUnaryConstraint(UnaryConstraint, TemplateConstraint):
345 - def __init__(self, *a, **d):
346 (c_args, t_args) = self.separate_arg_sets(d) 347 UnaryConstraint.__init__(self, *a, **c_args) 348 TemplateConstraint.__init__(self, **t_args)
349 - def to_string(self):
350 return(UnaryConstraint.to_string(self) 351 + " " + TemplateConstraint.to_string(self))
352
353 -class TemplateBinaryConstraint(BinaryConstraint, TemplateConstraint):
354 - def __init__(self, *a, **d):
355 (c_args, t_args) = self.separate_arg_sets(d) 356 BinaryConstraint.__init__(self, *a, **c_args) 357 TemplateConstraint.__init__(self, **t_args)
358 - def to_string(self):
359 return(BinaryConstraint.to_string(self) 360 + " " + TemplateConstraint.to_string(self))
361
362 -class TemplateListConstraint(ListConstraint, TemplateConstraint):
363 - def __init__(self, *a, **d):
364 (c_args, t_args) = self.separate_arg_sets(d) 365 ListConstraint.__init__(self, *a, **c_args) 366 TemplateConstraint.__init__(self, **t_args)
367 - def to_string(self):
368 return(ListConstraint.to_string(self) 369 + " " + TemplateConstraint.to_string(self))
370
371 -class TemplateLoopConstraint(LoopConstraint, TemplateConstraint):
372 - def __init__(self, *a, **d):
373 (c_args, t_args) = self.separate_arg_sets(d) 374 LoopConstraint.__init__(self, *a, **c_args) 375 TemplateConstraint.__init__(self, **t_args)
376 - def to_string(self):
377 return(LoopConstraint.to_string(self) 378 + " " + TemplateConstraint.to_string(self))
379
380 -class TemplateTernaryConstraint(TernaryConstraint, TemplateConstraint):
381 - def __init__(self, *a, **d):
382 (c_args, t_args) = self.separate_arg_sets(d) 383 TernaryConstraint.__init__(self, *a, **c_args) 384 TemplateConstraint.__init__(self, **t_args)
385 - def to_string(self):
386 return(TernaryConstraint.to_string(self) 387 + " " + TemplateConstraint.to_string(self))
388
389 -class TemplateMultiConstraint(MultiConstraint, TemplateConstraint):
390 - def __init__(self, *a, **d):
391 (c_args, t_args) = self.separate_arg_sets(d) 392 MultiConstraint.__init__(self, *a, **c_args) 393 TemplateConstraint.__init__(self, **t_args)
394 - def to_string(self):
395 return(MultiConstraint.to_string(self) 396 + " " + TemplateConstraint.to_string(self))
397
398 -class TemplateSubClassConstraint(SubClassConstraint, TemplateConstraint):
399 - def __init__(self, *a, **d):
400 (c_args, t_args) = self.separate_arg_sets(d) 401 SubClassConstraint.__init__(self, *a, **c_args) 402 TemplateConstraint.__init__(self, **t_args)
403 - def to_string(self):
404 return(SubClassConstraint.to_string(self) 405 + " " + TemplateConstraint.to_string(self))
406
407 -class ConstraintFactory(object):
408 409 CONSTRAINT_CLASSES = set([ 410 UnaryConstraint, BinaryConstraint, TernaryConstraint, 411 MultiConstraint, SubClassConstraint, LoopConstraint, 412 ListConstraint]) 413
414 - def __init__(self):
415 self._codes = iter(string.ascii_uppercase)
416
417 - def get_next_code(self):
418 return self._codes.next()
419
420 - def make_constraint(self, *args, **kwargs):
421 for CC in self.CONSTRAINT_CLASSES: 422 try: 423 c = CC(*args, **kwargs) 424 if hasattr(c, "code"): c.code = self.get_next_code() 425 return c 426 except TypeError, e: 427 pass 428 raise TypeError("No matching constraint class found for " 429 + str(args) + ", " + str(kwargs))
430
431 -class TemplateConstraintFactory(ConstraintFactory):
432 CONSTRAINT_CLASSES = set([ 433 TemplateUnaryConstraint, TemplateBinaryConstraint, 434 TemplateTernaryConstraint, TemplateMultiConstraint, 435 TemplateSubClassConstraint, TemplateLoopConstraint, 436 TemplateListConstraint 437 ])
438