def _interpolate_references(self, path, value, inventory): all_refs = False while not all_refs: for ref in value.get_references(): path_from_ref = DictPath(self._delimiter, ref) if path_from_ref in self._unrendered: if self._unrendered[path_from_ref] is False: # every call to _interpolate_inner replaces the value of # self._unrendered[path] with False # Therefore, if we encounter False instead of True, # it means that we have already processed it and are now # faced with a cyclical reference. raise InfiniteRecursionError(path, ref) else: self._interpolate_inner(path_from_ref, inventory) else: # ensure ancestor keys are already dereferenced ancestor = DictPath(self._delimiter) for k in path_from_ref.key_parts(): ancestor = ancestor.new_subpath(k) if ancestor in self._unrendered: self._interpolate_inner(ancestor, inventory) if value.allRefs(): all_refs = True else: # not all references in the value could be calculated previously so # try recalculating references with current context and recursively # call _interpolate_inner if the number of references has increased # Otherwise raise an error old = len(value.get_references()) value.assembleRefs(self._base) if old == len(value.get_references()): raise InterpolationError('Bad reference count, path:' + repr(path))
def _interpolate_inner(self, path, refvalue): self._occurrences[path] = True # mark as seen for ref in refvalue.get_references(): path_from_ref = DictPath(self.delimiter, ref) try: refvalue_inner = self._occurrences[path_from_ref] # If there is no reference, then this will throw a KeyError, # look further down where this is caught and execution passed # to the next iteration of the loop # # If we get here, then the ref references another parameter, # requiring us to recurse, dereferencing first those refs that # are most used and are thus at the leaves of the dependency # tree. if refvalue_inner is True: # every call to _interpolate_inner replaces the value of # the saved occurrences of a reference with True. # Therefore, if we encounter True instead of a refvalue, # it means that we have already processed it and are now # faced with a cyclical reference. raise InfiniteRecursionError(path, ref) self._interpolate_inner(path_from_ref, refvalue_inner) except KeyError as e: # not actually an error, but we are done resolving all # dependencies of the current ref, so move on continue try: new = refvalue.render(self._base) path.set_value(self._base, new) # finally, remove the reference from the occurrences cache del self._occurrences[path] except UndefinedVariableError as e: raise UndefinedVariableError(e.var, path)