def has_changes(self): current_frame = inspect.currentframe() caller_frame = current_frame.f_back current_file = inspect.getfile(current_frame) caller_file = inspect.getfile(caller_frame) real_caller_is_parent_frame = current_file == caller_file if real_caller_is_parent_frame: caller_frame = caller_frame.f_back frameinfo = inspect.getframeinfo(caller_frame) filename, caller_lineno, _, code_context, _ = frameinfo code_context = code_context[0] context_indent = len(code_context) - len(code_context.lstrip()) lines = [] # TODO: Memoize open(filename, 'r') in a way that clears the memoized # version with each run of the user's script. Then use the memoized # text here, in st.echo, and other places. with open(filename, "r") as f: for line in f.readlines()[caller_lineno:]: if line.strip() == "": lines.append(line) indent = len(line) - len(line.lstrip()) if indent <= context_indent: break if line.strip() and not line.lstrip().startswith("#"): lines.append(line) while lines[-1].strip() == "": lines.pop() code_block = "".join(lines) program = textwrap.dedent(code_block) context = Context(dict(caller_frame.f_globals, **caller_frame.f_locals), {}, {}) code = compile(program, filename, "exec") code_hasher = CodeHasher("md5") code_hasher.update(code, context) LOGGER.debug("Hashing block in %i bytes.", code_hasher.size) key = code_hasher.hexdigest() LOGGER.debug("Cache key: %s", key) try: value, _ = _read_from_cache( key, self._persist, self._allow_output_mutation, code, [caller_lineno + 1, caller_lineno + len(lines)], ) self.update(value) except CacheKeyNotFoundError: if self._allow_output_mutation and not self._persist: # If we don't hash the results, we don't need to use exec and just return True. # This way line numbers will be correct. _write_to_cache( key=key, value=self, persist=False, allow_output_mutation=True ) return True exec(code, caller_frame.f_globals, caller_frame.f_locals) _write_to_cache( key=key, value=self, persist=self._persist, allow_output_mutation=self._allow_output_mutation, ) # Return False so that we have control over the execution. return False
def _get_key(obj: Any) -> str: hasher = CodeHasher() hasher.update(obj) return hasher.hexdigest()