Package gavo :: Package rscdef :: Module rowtriggers
[frames] | no frames]

Source Code for Module gavo.rscdef.rowtriggers

  1  """ 
  2  Framework for ignoring rows based on various conditions. 
  3  """ 
  4   
  5  #c Copyright 2008-2019, the GAVO project 
  6  #c 
  7  #c This program is free software, covered by the GNU GPL.  See the 
  8  #c COPYING file in the source distribution. 
  9   
 10   
 11  from gavo import base 
 12  from gavo.base import typesystems 
 13   
 14   
15 -class TriggerPulled(base.Error):
16 - def __init__(self, msg, triggerName):
17 base.Error.__init__(self, msg) 18 self.triggerName = triggerName 19 self.args = [msg, triggerName]
20 21 22 _triggerRegistry = {}
23 -def registerTrigger(trigger):
24 _triggerRegistry[trigger.name_] = trigger 25 return trigger
26
27 -def getTrigger(name):
28 return _triggerRegistry[name]
29 30
31 -class TriggerBase(base.Structure):
32 """A trigger, i.e., a boolean construct. 33 34 This element does not actually occur in resource descriptors. 35 Refer to Triggers_ for triggers actually available. 36 """ 37 # Basically, a trigger can be called and has to return True when it fires, 38 # false otherwise. So, you generally want to override its __call__ method. 39 # All __call__ methods have the constant signature __call__(dict) -> bool. 40 name_ = "trigger" 41 42 _name = base.UnicodeAttribute("name", default="unnamed", 43 description="A name that should help the user figure out what trigger" 44 " caused some condition to fire.", copyable=True)
45 46
47 -class KeyedCondition(TriggerBase):
48 """is an abstract base class for triggers firing on a single key. 49 """ 50 _key = base.UnicodeAttribute("key", default=base.Undefined, 51 description="Key to check", copyable=True)
52 53
54 -class KeyPresent(KeyedCondition):
55 """A trigger firing if a certain key is present in the dict. 56 """ 57 name_ = "keyPresent" 58
59 - def __call__(self, dict):
60 return self.key in dict
61 62 registerTrigger(KeyPresent) 63 64
65 -class KeyMissing(KeyedCondition):
66 """A trigger firing if a certain key is missing in the dict. 67 68 This is equivalent to:: 69 70 <not><keyPresent key="xy"/></not> 71 """ 72 name_ = "keyMissing" 73
74 - def __call__(self, dict):
75 return self.key not in dict
76 77 registerTrigger(KeyMissing) 78 79
80 -class KeyNull(KeyedCondition):
81 """A trigger firing if a certain key is missing or NULL/None 82 """ 83 name_ = "keyNull" 84
85 - def __call__(self, dict):
86 return dict.get(self.key) is None
87 88 registerTrigger(KeyNull) 89 90 91
92 -class KeyIs(KeyedCondition):
93 """A trigger firing when the value of key in row is equal to the value given. 94 95 Missing keys are always accepted. You can define an SQL type; value will 96 then be interpreted as a literal for this type, and this literal's value will 97 be compared against the key's value. This is only needed for grammars like 98 fitsProductGrammar that actually yield typed values. 99 """ 100 name_ = "keyIs" 101 102 _value = base.UnicodeAttribute("value", default=base.Undefined, 103 description="The string value to fire on.", copyable=True) 104 _type = base.UnicodeAttribute("type", default="text", 105 description="An SQL type the python equivalent of which the value" 106 " should be converted to before checking.") 107
108 - def onElementComplete(self):
109 self.compValue = self.value 110 if self.type!="text": 111 self.compValue = typesystems.sqltypeToPython(self.type)(self.value)
112
113 - def __call__(self, dict):
114 return self.key in dict and dict[self.key]==self.compValue
115 116 registerTrigger(KeyIs) 117 118
119 -class ConditionBase(TriggerBase):
120 """is an abstract base for anything that can incorporate the 121 basic triggers. 122 123 If you don't override __call__, the basic operation is or-ing together 124 all embedded conditions. 125 """ 126 _triggers = base.MultiStructListAttribute("triggers", 127 childFactory=getTrigger, childNames=_triggerRegistry, 128 description=("One or more conditions joined by an implicit logical or." 129 " See Triggers_ for information on what can stand here."), 130 copyable=True) 131
132 - def __call__(self, dict):
133 for t in self.triggers: 134 if t(dict): 135 return True 136 return False
137 138
139 -class Not(ConditionBase):
140 """A trigger that is false when its children, or-ed together, are true and 141 vice versa. 142 """ 143 name_ = "not" 144
145 - def __call__(self, dict):
146 return not ConditionBase.__call__(self, dict)
147 148 registerTrigger(Not) 149 150
151 -class And(ConditionBase):
152 """A trigger that is true when all its children are true. 153 """ 154 name_ = "and" 155
156 - def __call__(self, dict):
157 for t in self.triggers: 158 if not t(dict): 159 return False 160 return True
161 162 registerTrigger(And) 163 164
165 -class IgnoreOn(ConditionBase):
166 """A condition on a row that, if true, causes the row to be dropped. 167 168 Here, you can set bail to abort an import when the condition is met 169 rather than just dropping the row. 170 """ 171 name_ = "ignoreOn" 172 _bail = base.BooleanAttribute("bail", default=False, description= 173 "Abort when condition is met?") 174
175 - def __call__(self, row):
176 conditionMet = ConditionBase.__call__(self, row) 177 if self.bail and conditionMet: 178 raise TriggerPulled("Trigger %s satisfied and set to bail"%self.name, 179 self.name) 180 return conditionMet
181