Exemple #1
0
 def updatefact(self, fact, expression, **kwargs):
     # AutoBlanking Feature - If there is no expression, zeros relevant fields
     if expression != None and expression.strip() == u"":
         for field in self.graphbasedupdater.updateablefields:
             if field in fact and factproxy.isgeneratedfield(field, fact[field]):
                 fact[field] = u""
         
         # TODO: Nick added this to give up after auto-blanking. He claims it removes a minor
         # delay, but I'm not sure where the delay originates from, which worries me:
         return
     
     FieldUpdater.updatefact(self, fact, expression, **kwargs)
Exemple #2
0
 def updatefact(self, fact, expression, **kwargs):
     # AutoBlanking Feature - If there is no expression, zeros relevant fields
     if expression != None and expression.strip() == u"":
         for field in self.graphbasedupdater.updateablefields:
             if field in fact and factproxy.isgeneratedfield(field, fact[field]):
                 fact[field] = u""
         
         # TODO: Nick added this to give up after auto-blanking. He claims it removes a minor
         # delay, but I'm not sure where the delay originates from, which worries me:
         return
     
     FieldUpdater.updatefact(self, fact, expression, **kwargs)
Exemple #3
0
    def updatefact(self, fact, value, alwaysreformat=False):
        assert self.field in fact
        assert value is None or not(factproxy.isgeneratedfield(self.field, value))
        
        # HACK ALERT: can't think of a nicer way to do mwfieldinfact
        # NB: this is not quite right. The user might have changed whether there is a mw field independently of whether the
        # contents of the field we are considering has changed. However, this bug should be very hard to hit (TODO: fix it).
        delta = value is not None and { self.field : value, "mwfieldinfact" : "mw" in fact } or {}
        graph = self.graphbasedupdater.filledgraph(fact, delta)

        # For the same reason that fields may be missing from the graph but present in the fact,
        # even the field we are updating can be missing. For example, this happens when tabbing
        # away from a generated field.
        #
        # We should also avoid doing this if the field is considered generated, because in that
        # case reformatting leads to changing the field to be spuriously marked as original.
        if self.field in graph and  not(factproxy.isgeneratedfield(self.field, value or fact[self.field])):
            # EAGERLY reformat to produce the new value, which we plop back into the graph again so
            # other computations can see it. Note that this is potentially unsafe because we might now actually
            # need to recompute other thunks in the graph that depended on this one. Thankfully, right now
            # we only have updaters that guarantee not to change the value of an expression which will change
            # the value of anything *they* force to FIND that new value, so it works out.
            fact[self.field] = self.reformatter.reformatfield(self.field, graph, alwaysreformat=alwaysreformat)
            graph[self.field] = (False, utils.Thunk(lambda: fact[self.field]))

        log.info("Keys in the graph after update: %r", graph.keys())
        
        for field in fact:
            # Fields can be missing from the graph if they are generated fields in the input fact that
            # the graph failed to find a way to update. This really does happen!
            if shouldupdatefield(self.graphbasedupdater.config)(field) and field in graph:
                value = graph[field][1]()
                # The value can be None if we tried to update the field but just couldn't find any information.
                # This can happen e.g. with the "meaning" field if there is no definition in the dictionary.
                if value is not None:
                    # Last-ditch attempt to convert to unicode to intercept any ASCII that slipped though the net
                    fact[field] = (graph[field][0] and factproxy.markgeneratedfield or (lambda x: x))(unicode(value))
Exemple #4
0
def filledgraphforupdaters(all_updaters, fact, delta):
    graph = {}
    dirty = {}
    
    finddirties = lambda usings: [using for using in usings if dirty[using]]
    
    # The initial shell contains just the stuff that is non-generated or being set in this round.
    # Everything else will be generated based off of these values
    initiallyfilledfields = set([field for field in fact if not isgeneratedfield(field, fact[field]) and not isblankfield(fact[field])]).union(set(delta.keys()))
    for field in initiallyfilledfields:
        dirty[field] = field in delta
        graph[field] = (False, Thunk(lambda field=field: cond(field in delta, lambda: delta[field], lambda: fact[field])))
    
    log.info("Initially filled graph fields: %r", dirty)
    
    # Remove useless updaters, and updaters that might confound a delta by updating a field from an
    # old field. For example, if we change the reading we want to regenerate the color field -- but this is no
    # good if we just use the updater that gets the reading from the expression!
    all_updaters = [updater for updater in all_updaters if updater[0] not in initiallyfilledfields]
    delta_updaters = maydependon(all_updaters, delta.keys())
    
    # A complication is the presence of non-generated blank fields. We want to try and update these even if the
    # delta is empty (for example). To this end we need to make sure that the cut doesn't exclude updaters that
    # might potentially be depended on by an updater for a blank field.
    #
    # We also exclude updaters for any field that is going to get filled out by any delta_updater, because those
    # updaters should take priority.
    all_updaters = [updater for updater in all_updaters if all([updater[0] not in delta_field for delta_field, _, _ in delta_updaters])]
    blank_updaters = dependedonby(all_updaters, [field for field in fact if isblankfield(fact[field])])
    
    # Yes, this really works! This is because Python has reference comparison semantics on functions.
    # However, this is the reason we need to use tuples of fields we depend on, rather than lists
    updaters = set(delta_updaters + blank_updaters)
    
    def shell(alreadyfilled):
        # Gather all the fields we are newly able to fill given the most recent changes
        # to the list of already filled things
        cannowfill = FactoryDict(lambda _: [])
        for updatewhat, updatefunction, updateusings in updaters:
            if updatewhat not in alreadyfilled and all([updateusing in alreadyfilled for updateusing in updateusings]):
                # We have to delay the computation of whether something is dirty or not until we are inside
                # the actual thunk for this particular field, hence all the thunking and lambdas here
                inputs = Thunk(lambda updateusings=updateusings: [graph[updateusing][1]() for updateusing in updateusings])
                cannowfill[updatewhat].append((lambda inputs=inputs, updatefunction=updatefunction: updatefunction(*(inputs())),
                                               lambda inputs=inputs, updateusings=updateusings: seq(inputs(), lambda: finddirties(updateusings))))
        
        # Check for quiescence
        if len(cannowfill) == 0:
            # NB: could do something with the unfilled set (self.updateablefields.difference(alreadyfilled)) here
            return
        
        # Set up each fillable graph field with a thunk computing the value
        for field, possiblefillers in cannowfill.items():
            def fillme(field=field, possiblefillers=possiblefillers):
                # For preference, use a filler that will certainly return clean information (i.e. sort by the number of dirty inputs and prefer the first - i.e. the one with fewer dirty inputs)
                for fillerfunction, dirtyinputs, anyinputsdirty in sorted([(f, dirties(), len(dirties()) > 0) for f, dirties in possiblefillers], using(lambda x: x[2])):
                    if field not in fact or isblankfield(fact[field]) or anyinputsdirty:
                        # Don't know what the last value was or it may have changed: recompute.
                        #
                        # We also recompute if the incoming field is blank: this can happen if we have
                        # deleted the contents of a field, and then come back to update the fact by tabbing
                        # away from a filled field. Now we have enough information to fill it, and musn't
                        # just reuse the old version, which will be blank.
                        #
                        # Note that if the field was *generated* as blank one then it will have a marker
                        # in it, and so we won't pointlessly recompute its blankness.
                        log.info("Attempting to fill %s field -- dirty inputs are %s", field, dirtyinputs)
                        result = fillerfunction()
                        if result is None:
                            log.info("Filling %s failed -- falling back on another method", field)
                            continue
                        
                        dirty[field] = cond(field in fact, lambda: result != unmarkgeneratedfield(fact[field]), lambda: anyinputsdirty)
                        return result
                    else:
                        # Last value must not have changed: retain it
                        assert (field in fact and not anyinputsdirty)
                        log.info("Retaining old field value for %s (blank: %s)", field, isblankfield(fact[field]))
                        dirty[field] = False
                        return unmarkgeneratedfield(fact[field])
                
                # What if all of the possible updaters failed? Ideally we would not be in the graph at all, but it's too late for that.
                # All we can do is return None, and deal with this possibility later on.
                log.info("All possible updaters failed for the field %s", field)
                dirty[field] = False
                return None
            
            graph[field] = (True, Thunk(fillme))
        
        shell(alreadyfilled.union(set(cannowfill.keys())))
    
    shell(initiallyfilledfields)
    
    return graph
Exemple #5
0
def filledgraphforupdaters(all_updaters, fact, delta):
    graph = {}
    dirty = {}
    
    finddirties = lambda usings: [using for using in usings if dirty[using]]
    
    # The initial shell contains just the stuff that is non-generated or being set in this round.
    # Everything else will be generated based off of these values
    initiallyfilledfields = set([field for field in fact if not isgeneratedfield(field, fact[field]) and not isblankfield(fact[field])]).union(set(delta.keys()))
    for field in initiallyfilledfields:
        dirty[field] = field in delta
        graph[field] = (False, Thunk(lambda field=field: cond(field in delta, lambda: delta[field], lambda: fact[field])))
    
    log.info("Initially filled graph fields: %r", dirty)
    
    # Remove useless updaters, and updaters that might confound a delta by updating a field from an
    # old field. For example, if we change the reading we want to regenerate the color field -- but this is no
    # good if we just use the updater that gets the reading from the expression!
    all_updaters = [updater for updater in all_updaters if updater[0] not in initiallyfilledfields]
    delta_updaters = maydependon(all_updaters, delta.keys())
    
    # A complication is the presence of non-generated blank fields. We want to try and update these even if the
    # delta is empty (for example). To this end we need to make sure that the cut doesn't exclude updaters that
    # might potentially be depended on by an updater for a blank field.
    #
    # We also exclude updaters for any field that is going to get filled out by any delta_updater, because those
    # updaters should take priority.
    all_updaters = [updater for updater in all_updaters if all([updater[0] not in delta_field for delta_field, _, _ in delta_updaters])]
    blank_updaters = dependedonby(all_updaters, [field for field in fact if isblankfield(fact[field])])
    
    # Yes, this really works! This is because Python has reference comparison semantics on functions.
    # However, this is the reason we need to use tuples of fields we depend on, rather than lists
    updaters = set(delta_updaters + blank_updaters)
    
    def shell(alreadyfilled):
        # Gather all the fields we are newly able to fill given the most recent changes
        # to the list of already filled things
        cannowfill = FactoryDict(lambda _: [])
        for updatewhat, updatefunction, updateusings in updaters:
            if updatewhat not in alreadyfilled and all([updateusing in alreadyfilled for updateusing in updateusings]):
                # We have to delay the computation of whether something is dirty or not until we are inside
                # the actual thunk for this particular field, hence all the thunking and lambdas here
                inputs = Thunk(lambda updateusings=updateusings: [graph[updateusing][1]() for updateusing in updateusings])
                cannowfill[updatewhat].append((lambda inputs=inputs, updatefunction=updatefunction: updatefunction(*(inputs())),
                                               lambda inputs=inputs, updateusings=updateusings: seq(inputs(), lambda: finddirties(updateusings))))
        
        # Check for quiescence
        if len(cannowfill) == 0:
            # NB: could do something with the unfilled set (self.updateablefields.difference(alreadyfilled)) here
            return
        
        # Set up each fillable graph field with a thunk computing the value
        for field, possiblefillers in cannowfill.items():
            def fillme(field=field, possiblefillers=possiblefillers):
                # For preference, use a filler that will certainly return clean information (i.e. sort by the number of dirty inputs and prefer the first - i.e. the one with fewer dirty inputs)
                for fillerfunction, dirtyinputs, anyinputsdirty in sorted([(f, dirties(), len(dirties()) > 0) for f, dirties in possiblefillers], using(lambda x: x[2])):
                    if field not in fact or isblankfield(fact[field]) or anyinputsdirty:
                        # Don't know what the last value was or it may have changed: recompute.
                        #
                        # We also recompute if the incoming field is blank: this can happen if we have
                        # deleted the contents of a field, and then come back to update the fact by tabbing
                        # away from a filled field. Now we have enough information to fill it, and musn't
                        # just reuse the old version, which will be blank.
                        #
                        # Note that if the field was *generated* as blank one then it will have a marker
                        # in it, and so we won't pointlessly recompute its blankness.
                        log.info("Attempting to fill %s field -- dirty inputs are %s", field, dirtyinputs)
                        result = fillerfunction()
                        if result is None:
                            log.info("Filling %s failed -- falling back on another method", field)
                            continue
                        
                        dirty[field] = cond(field in fact, lambda: result != unmarkgeneratedfield(fact[field]), lambda: anyinputsdirty)
                        return result
                    else:
                        # Last value must not have changed: retain it
                        assert (field in fact and not anyinputsdirty)
                        log.info("Retaining old field value for %s (blank: %s)", field, isblankfield(fact[field]))
                        dirty[field] = False
                        return unmarkgeneratedfield(fact[field])
                
                # What if all of the possible updaters failed? Ideally we would not be in the graph at all, but it's too late for that.
                # All we can do is return None, and deal with this possibility later on.
                log.info("All possible updaters failed for the field %s", field)
                dirty[field] = False
                return None
            
            graph[field] = (True, Thunk(fillme))
        
        shell(alreadyfilled.union(set(cannowfill.keys())))
    
    shell(initiallyfilledfields)
    
    return graph