def simplenamer(): autonamer = AutoNamer("Cells") for i in range(10): autonamer.get_next([]) return autonamer
class System: orig_settings = { "sys.recursionlimit": sys.getrecursionlimit(), "showwarning": warnings.showwarning } def __init__(self, maxdepth=None, setup_shell=False): self.configure_python() self.formula_error = True self.executor = Executor(self, maxdepth) self.callstack = self.executor.callstack self.refstack = self.executor.refstack self._modelnamer = AutoNamer("Model") self._backupnamer = AutoNamer("_BAK") self.currentmodel = None self._models = {} self.serializing = None self._recalc_dependents = False if setup_shell: if is_ipython(): self.is_ipysetup = False self.setup_ipython() else: self.shell = None self.is_ipysetup = False else: self.is_ipysetup = False self.iomanager = IOManager(self) def setup_ipython(self): """Monkey patch shell's error handler. This method is to monkey-patch the showtraceback method of IPython's InteractiveShell to __IPYTHON__ is not detected when starting an IPython kernel, so this method is called from start_kernel in spyder-modelx. """ if self.is_ipysetup: return from ipykernel.kernelapp import IPKernelApp self.shell = IPKernelApp.instance().shell # None in PyCharm console if not self.shell and is_ipython(): self.shell = get_ipython() if self.shell: shell_class = type(self.shell) shell_class.default_showtraceback = shell_class.showtraceback shell_class.showtraceback = custom_showtraceback self.is_ipysetup = True else: raise RuntimeError("IPython shell not found.") def restore_ipython(self): """Restore default IPython showtraceback""" if not self.is_ipysetup: return shell_class = type(self.shell) shell_class.showtraceback = shell_class.default_showtraceback del shell_class.default_showtraceback self.is_ipysetup = False def configure_python(self): """Configure Python settings for modelx The error handler is configured later. """ sys.setrecursionlimit(10**6) warnings.showwarning = custom_showwarning def restore_python(self): """Restore Python settings to the original states""" orig = self.orig_settings sys.setrecursionlimit(orig["sys.recursionlimit"]) if "sys.tracebacklimit" in orig: sys.tracebacklimit = orig["sys.tracebacklimit"] else: if hasattr(sys, "tracebacklimit"): del sys.tracebacklimit if "showwarning" in orig: warnings.showwarning = orig["showwarning"] orig.clear() def new_model(self, name=None): if name in self.models: self._rename_samename(name) self.currentmodel = ModelImpl(system=self, name=name) self.models[self.currentmodel.name] = self.currentmodel return self.currentmodel def rename_model(self, new_name, old_name, rename_old=False): if new_name == old_name: return False else: if rename_old and new_name in self.models: self._rename_samename(new_name) result = self.models[old_name].rename(new_name) if result: self.models[new_name] = self.models.pop(old_name) return True else: return False def _rename_samename(self, name): backupname = self._backupnamer.get_next(self.models, prefix=name) if self.rename_model(backupname, name): warnings.warn("Existing model '%s' renamed to '%s'" % (name, backupname)) else: raise ValueError("Failed to create %s", name) @property def models(self): return self._models @property def currentspace(self): if self.currentmodel: return self.currentmodel.currentspace else: return None def get_curspace(self): """Get or create current space""" if self.currentspace: return self.currentspace else: if self.currentmodel: m = self.currentmodel else: m = self.new_model() # self.new_model sets current_model m.currentspace = m.updater.new_space(m) return m.currentspace def backup_model(self, model, filepath, datapath): model._impl.update_lazyevals() with open(filepath, "wb") as file: SystemPickler(file, datapath, protocol=4).dump(model) def restore_model(self, path, name, datapath): with open(path, "rb") as file: model = SystemUnpickler(file, self).load() model._impl.restore_state(datapath) if name is not None: if not is_valid_name(name): raise ValueError("Invalid name '%s'." % name) newname = name or model.name if newname in self.models: self._rename_samename(newname) if name is not None: if not model._impl.rename(name): raise RuntimeError("must not happen") self.models[newname] = model._impl self.currentmodel = model._impl return model def close_model(self, model): del self.models[model.name] if self.currentmodel is model: self.currentmodel = None model.datarefmgr.del_all() def get_object(self, name, as_proxy=False): """Retrieve an object by its absolute name.""" parts = name.split(".") try: model = self.models[parts.pop(0)].interface except KeyError: raise NameError("'%s' not found" % name) if parts: return model._get_object(".".join(parts), as_proxy) else: return model def get_object_from_tupleid(self, tupleid): """Retrieve an object from tuple id.""" obj = None for key in tupleid: if isinstance(key, str): if obj: obj = getattr(obj, key) else: obj = self.models[key].interface elif isinstance(key, tuple): obj = obj.__call__(*key) else: raise ValueError return obj def _get_object_reduce(self, name): parts = name.split(".") if not parts[0]: parts[0] = self.serializing.model.name name = ".".join(parts) return self.get_object(name) def _get_object_from_tupleid_reduce(self, tupleid): if not tupleid[0]: model = self.serializing.model.name tupleid = (model, ) + tupleid[1:] return self.get_object_from_tupleid(tupleid) # ---------------------------------------------------------------------- # Call stack tracing def _is_stacktrace_active(self): return isinstance(self.callstack, TraceableCallStack) def start_stacktrace(self, maxlen): if self._is_stacktrace_active(): return False if self.callstack.is_empty(): self.callstack = self.executor.callstack = TraceableCallStack( self.executor, maxdepth=self.callstack.maxdepth, maxlen=maxlen) warnings.warn("call stack trace activated") else: raise RuntimeError("callstack not empy") return True def stop_stacktrace(self): if not self._is_stacktrace_active(): return False if self.callstack.is_empty(): self.callstack = self.executor.callstack = CallStack( self.executor, maxdepth=self.callstack.maxdepth) warnings.warn("call stack trace deactivated") else: raise RuntimeError("callstack not empy") return True def get_stacktrace(self, summarize): if self._is_stacktrace_active(): trace = list(self.callstack.tracestack) if not summarize: return trace else: return self._get_stacktrace_summary(trace) else: raise RuntimeError("call stack trace not active") def clear_stacktrace(self): if self._is_stacktrace_active(): self.callstack.tracestack.clear() else: raise RuntimeError("call stack trace not active") def _check_sanity(self): self.iomanager._check_sanity() def _get_stacktrace_summary(self, stacktrace): """ To get result as DataFrame: pd.DataFrame.from_dict(result, orient='index') """ maxlen = self.callstack.tracestack.maxlen if maxlen and maxlen <= len(stacktrace): raise RuntimeError("stacktrace truncated beyond max length") TYPE, LEVEL, TIME, REPR, ARGS = range(5) result = {} stack = [] t_last = 0 for trace in stacktrace: if stack: stack[-1][-1] += trace[TIME] - t_last if trace[TYPE] == 'ENTER': stack.append(list(trace) + [0]) if trace[REPR] not in result: result[trace[REPR]] = { 'calls': 0, 'duration': 0, 'first_entry_at': trace[TIME], 'last_exit_at': None } elif trace[TYPE] == 'EXIT': last = stack.pop() stat = result[last[REPR]] stat['calls'] += 1 stat['duration'] += last[-1] stat['last_exit_at'] = trace[TIME] else: raise RuntimeError('must not happen') t_last = trace[TIME] return result
class System: orig_settings = { "sys.recursionlimit": sys.getrecursionlimit(), "showwarning": warnings.showwarning } def __init__(self, maxdepth=None, setup_shell=False): self.configure_python() self.execution = Execution(self, maxdepth) self.callstack = self.execution.callstack self._modelnamer = AutoNamer("Model") self._backupnamer = AutoNamer("_BAK") self._currentmodel = None self._models = {} self.self = None if setup_shell: if is_ipython(): self.is_ipysetup = False self.setup_ipython() else: self.shell = None self.is_ipysetup = False else: self.is_ipysetup = False def setup_ipython(self): """Monkey patch shell's error handler. This method is to monkey-patch the showtraceback method of IPython's InteractiveShell to __IPYTHON__ is not detected when starting an IPython kernel, so this method is called from start_kernel in spyder-modelx. """ if self.is_ipysetup: return from ipykernel.kernelapp import IPKernelApp self.shell = IPKernelApp.instance().shell # None in PyCharm console if not self.shell and is_ipython(): self.shell = get_ipython() if self.shell: shell_class = type(self.shell) shell_class.default_showtraceback = shell_class.showtraceback shell_class.showtraceback = custom_showtraceback self.is_ipysetup = True else: raise RuntimeError("IPython shell not found.") def restore_ipython(self): """Restore default IPython showtraceback""" if not self.is_ipysetup: return shell_class = type(self.shell) shell_class.showtraceback = shell_class.default_showtraceback del shell_class.default_showtraceback self.is_ipysetup = False def configure_python(self): """Configure Python settings for modelx The error handler is configured later. """ sys.setrecursionlimit(10**6) warnings.showwarning = custom_showwarning threading.stack_size(0xFFFFFFF) def restore_python(self): """Restore Python settings to the original states""" orig = self.orig_settings sys.setrecursionlimit(orig["sys.recursionlimit"]) if "sys.tracebacklimit" in orig: sys.tracebacklimit = orig["sys.tracebacklimit"] else: if hasattr(sys, "tracebacklimit"): del sys.tracebacklimit if "showwarning" in orig: warnings.showwarning = orig["showwarning"] orig.clear() threading.stack_size() def new_model(self, name=None): if name in self.models: self._rename_samename(name) self._currentmodel = ModelImpl(system=self, name=name) self.models[self._currentmodel.name] = self._currentmodel return self._currentmodel def rename_model(self, new_name, old_name, rename_old=False): if new_name == old_name: return False else: if rename_old and new_name in self.models: self._rename_samename(new_name) result = self.models[old_name].rename(new_name) if result: self.models[new_name] = self.models.pop(old_name) return True else: return False def _rename_samename(self, name): backupname = self._backupnamer.get_next(self.models, prefix=name) if self.rename_model(backupname, name): warnings.warn("Existing model '%s' renamed to '%s'" % (name, backupname)) else: raise ValueError("Failed to create %s", name) @property def models(self): return self._models @property def currentmodel(self): return self._currentmodel @currentmodel.setter def currentmodel(self, model): self._currentmodel = model @property def currentspace(self): return self.currentmodel.currentspace def open_model(self, path, name): with open(path, "rb") as file: model = pickle.load(file) model._impl.restore_state(self) if name is not None: if not is_valid_name(name): raise ValueError("Invalid name '%s'." % name) newname = name or model.name if newname in self.models: self._rename_samename(newname) if name is not None: if not model._impl.rename(name): raise RuntimeError("must not happen") self.models[newname] = model._impl self._currentmodel = model._impl return model def close_model(self, model): del self.models[model.name] if self._currentmodel is model: self._currentmodel = None def get_object(self, name): """Retrieve an object by its absolute name.""" parts = name.split(".") model_name = parts.pop(0) return self.models[model_name].get_object(".".join(parts))
class ModelImpl(EditableSpaceContainerImpl, Impl): if_cls = Model def __init__(self, *, system, name): Impl.__init__(self, system=system) EditableSpaceContainerImpl.__init__(self) self.cellgraph = DependencyGraph() self.lexdep = DependencyGraph() # Lexical dependency self.spacegraph = SpaceGraph() self.currentspace = None if not name: self.name = system._modelnamer.get_next(system.models) elif is_valid_name(name): self.name = name else: raise ValueError("Invalid name '%s'." % name) data = {"__builtins__": builtins} self._global_refs = RefDict(self, data=data) self._spaces = ImplDict(self, SpaceView) self._dynamic_bases = {} self._dynamic_bases_inverse = {} self._dynamic_base_namer = AutoNamer("__Space") self._namespace = ImplChainMap(self, BaseView, [self._spaces, self._global_refs]) self.allow_none = False self.lazy_evals = self._namespace def rename(self, name): """Rename self. Must be called only by its system.""" if is_valid_name(name): if name not in self.system.models: self.name = name return True # Rename success else: # Model name already exists return False else: raise ValueError("Invalid name '%s'." % name) def clear_descendants(self, source, clear_source=True): """Clear values and nodes calculated from `source`.""" removed = self.cellgraph.clear_descendants(source, clear_source) for node in removed: del node[OBJ].data[node[KEY]] # TODO # def clear_lexdescendants(self, refnode): # """Clear values of cells that refer to `ref`.""" def clear_obj(self, obj): """Clear values and nodes of `obj` and their dependants.""" removed = self.cellgraph.clear_obj(obj) for node in removed: del node[OBJ].data[node[KEY]] def repr_self(self, add_params=True): return self.name def repr_parent(self): return "" @property def model(self): return self @Impl.doc.setter def doc(self, value): self._doc = value @property def global_refs(self): return self._global_refs.get_updated() @property def namespace(self): return self._namespace.get_updated() def close(self): self.system.close_model(self) def save(self, filepath): self.update_lazyevals() with open(filepath, "wb") as file: pickle.dump(self.interface, file, protocol=4) def get_object(self, name): """Retrieve an object by a dotted name relative to the model.""" parts = name.split(".") space = self.spaces[parts.pop(0)] if parts: return space.get_object(".".join(parts)) else: return space # ---------------------------------------------------------------------- # Serialization by pickle state_attrs = ([ "name", "cellgraph", "lexdep", "_namespace", "_global_refs", "_dynamic_bases", "_dynamic_bases_inverse", "_dynamic_base_namer", "spacegraph", ] + BaseSpaceContainerImpl.state_attrs + Impl.state_attrs) assert len(state_attrs) == len(set(state_attrs)) def __getstate__(self): state = { key: value for key, value in self.__dict__.items() if key in self.state_attrs } graphs = { name: graph for name, graph in state.items() if isinstance(graph, DependencyGraph) } for gname, graph in graphs.items(): mapping = {} for node in graph: name = node[OBJ].get_fullname(omit_model=True) if node_has_key(node): mapping[node] = (name, node[KEY]) else: mapping[node] = name state[gname] = nx.relabel_nodes(graph, mapping) return state def __setstate__(self, state): self.__dict__.update(state) def restore_state(self, system): """Called after unpickling to restore some attributes manually.""" Impl.restore_state(self, system) BaseSpaceContainerImpl.restore_state(self, system) mapping = {} for node in self.cellgraph: if isinstance(node, tuple): name, key = node else: name, key = node, None cells = self.get_object(name) mapping[node] = get_node(cells, key, None) self.cellgraph = nx.relabel_nodes(self.cellgraph, mapping) def del_space(self, name): space = self.spaces[name] self.spaces.del_item(name) def _set_space(self, space): if space.name in self.spaces: self.del_space(space.name) elif space.name in self.global_refs: raise KeyError("Name '%s' already already assigned" % self.name) self.spaces.set_item(space.name, space) def del_ref(self, name): self.global_refs.del_item(name) def get_attr(self, name): if name in self.spaces: return self.spaces[name].interface elif name in self.global_refs: return get_interfaces(self.global_refs[name]) else: raise AttributeError("Model '{0}' does not have '{1}'".format( self.name, name)) def set_attr(self, name, value): if name in self.spaces: raise KeyError("Space named '%s' already exist" % self.name) self.global_refs.set_item(name, ReferenceImpl(self, name, value)) def del_attr(self, name): if name in self.spaces: self.del_space(name) elif name in self.global_refs: self.del_ref(name) else: raise KeyError("Name '%s' not defined" % name) def get_dynamic_base(self, bases: tuple): """Create of get a base space for a tuple of bases""" try: return self._dynamic_bases_inverse[bases] except KeyError: name = self._dynamic_base_namer.get_next(self._dynamic_bases) base = self._new_space(name=name) self.spacegraph.add_space(base) self._dynamic_bases[name] = base self._dynamic_bases_inverse[bases] = base base.add_bases(bases) return base
def test_get_next_with_prefix(): existing_names = ["model_BAK1", "model_BAK2", "model_BAK3"] autonamer = AutoNamer("_BAK") assert autonamer.get_next(existing_names, "model") == "model_BAK4"
class ModelImpl(TraceManager, EditableSpaceContainerImpl, Impl): interface_cls = Model __cls_stateattrs = [ "_namespace", "_global_refs", "_dynamic_bases", "_dynamic_bases_inverse", "_dynamic_base_namer", "spacemgr", "currentspace", "datarefmgr" ] def __init__(self, *, system, name): if not name: name = system._modelnamer.get_next(system.models) elif not is_valid_name(name): raise ValueError("Invalid name '%s'." % name) Impl.__init__(self, system=system, parent=None, name=name) EditableSpaceContainerImpl.__init__(self) TraceManager.__init__(self) self.spacemgr = SpaceManager(self) self.currentspace = None self._global_refs = RefDict(self) self._global_refs.set_item("__builtins__", builtins) self._named_spaces = SpaceDict(self) self._dynamic_bases = SpaceDict(self) self._all_spaces = ImplChainMap( self, SpaceView, [self._named_spaces, self._dynamic_bases]) self._dynamic_bases_inverse = {} self._dynamic_base_namer = AutoNamer("__Space") self._namespace = ImplChainMap(self, BaseView, [self._named_spaces, self._global_refs]) self.allow_none = False self.lazy_evals = self._namespace self.datarefmgr = DataClientReferenceManager() def rename(self, name): """Rename self. Must be called only by its system.""" if is_valid_name(name): if name not in self.system.models: self.name = name return True # Rename success else: # Model name already exists return False else: raise ValueError("Invalid name '%s'." % name) def repr_self(self, add_params=True): return self.name def repr_parent(self): return "" @Impl.doc.setter def doc(self, value): self._doc = value @property def global_refs(self): return self._global_refs.fresh @property def namespace(self): return self._namespace.fresh def close(self): self.system.close_model(self) def get_impl_from_name(self, name): """Retrieve an object by a dotted name relative to the model.""" parts = name.split(".") space = self.spaces[parts.pop(0)] if parts: return space.get_impl_from_name(".".join(parts)) else: return space # ---------------------------------------------------------------------- # Serialization by pickle def __getstate__(self): state = { key: value for key, value in self.__dict__.items() if key in self.stateattrs } graphs = { name: graph for name, graph in state.items() if isinstance(graph, TraceGraph) } for gname, graph in graphs.items(): mapping = {} for node in graph: name = node[OBJ].namedid if node_has_key(node): mapping[node] = (name, node[KEY]) else: mapping[node] = name state[gname] = nx.relabel_nodes(graph, mapping) return state def __setstate__(self, state): self.__dict__.update(state) def restore_state(self, datapath=None): """Called after unpickling to restore some attributes manually.""" BaseSpaceContainerImpl.restore_state(self) for client in self.datarefmgr.clients: self.system.iomanager.register_client(client, model=self.interface, datapath=datapath) mapping = {} for node in self.tracegraph: if isinstance(node, tuple): name, key = node else: name, key = node, None cells = self.get_impl_from_name(name) mapping[node] = get_node(cells, key, None) self.tracegraph = nx.relabel_nodes(self.tracegraph, mapping) def del_space(self, name): space = self.spaces[name] self.spacemgr.del_defined_space(self, name) if space is self.currentspace: self.currentspace = None def del_ref(self, name): ref = self.global_refs[name] self.model.clear_attr_referrers(ref) ref.on_delete() self.global_refs.delete_item(name) def change_ref(self, name, value): ref = self.global_refs[name] self.model.clear_attr_referrers(ref) ref.change_value(value, False) def get_attr(self, name): if name in self.spaces: return self.spaces[name].interface elif name in self.global_refs: return get_interfaces(self.global_refs[name]) else: raise AttributeError("Model '{0}' does not have '{1}'".format( self.name, name)) def set_attr(self, name, value): if name in self.spaces: raise KeyError("Space named '%s' already exist" % self.name) elif name in self.global_refs: self.change_ref(name, value) else: ref = ReferenceImpl(self, name, value, container=self._global_refs, set_item=False) self._global_refs.add_item(name, ref) def del_attr(self, name): if name in self.spaces: self.del_space(name) elif name in self.global_refs: self.del_ref(name) else: raise KeyError("Name '%s' not defined" % name) # ---------------------------------------------------------------------- # Dynamic base manager def get_dynamic_base(self, bases: tuple): """Create of get a base space for a tuple of bases""" try: return self._dynamic_bases_inverse[bases] except KeyError: name = self._dynamic_base_namer.get_next(self._dynamic_bases) base = self.spacemgr.new_space(self, name=name, bases=bases, prefix="__", container=self._dynamic_bases) self._dynamic_bases_inverse[bases] = base return base
class System: orig_settings = { "sys.recursionlimit": sys.getrecursionlimit(), "showwarning": warnings.showwarning } def __init__(self, maxdepth=None, setup_shell=False): self.configure_python() self.executor = Executor(self, maxdepth) self.callstack = self.executor.callstack self._modelnamer = AutoNamer("Model") self._backupnamer = AutoNamer("_BAK") self.currentmodel = None self._models = {} self.serializing = None self._recalc_dependents = False if setup_shell: if is_ipython(): self.is_ipysetup = False self.setup_ipython() else: self.shell = None self.is_ipysetup = False else: self.is_ipysetup = False def setup_ipython(self): """Monkey patch shell's error handler. This method is to monkey-patch the showtraceback method of IPython's InteractiveShell to __IPYTHON__ is not detected when starting an IPython kernel, so this method is called from start_kernel in spyder-modelx. """ if self.is_ipysetup: return from ipykernel.kernelapp import IPKernelApp self.shell = IPKernelApp.instance().shell # None in PyCharm console if not self.shell and is_ipython(): self.shell = get_ipython() if self.shell: shell_class = type(self.shell) shell_class.default_showtraceback = shell_class.showtraceback shell_class.showtraceback = custom_showtraceback self.is_ipysetup = True else: raise RuntimeError("IPython shell not found.") def restore_ipython(self): """Restore default IPython showtraceback""" if not self.is_ipysetup: return shell_class = type(self.shell) shell_class.showtraceback = shell_class.default_showtraceback del shell_class.default_showtraceback self.is_ipysetup = False def configure_python(self): """Configure Python settings for modelx The error handler is configured later. """ sys.setrecursionlimit(10**6) warnings.showwarning = custom_showwarning def restore_python(self): """Restore Python settings to the original states""" orig = self.orig_settings sys.setrecursionlimit(orig["sys.recursionlimit"]) if "sys.tracebacklimit" in orig: sys.tracebacklimit = orig["sys.tracebacklimit"] else: if hasattr(sys, "tracebacklimit"): del sys.tracebacklimit if "showwarning" in orig: warnings.showwarning = orig["showwarning"] orig.clear() def new_model(self, name=None): if name in self.models: self._rename_samename(name) self.currentmodel = ModelImpl(system=self, name=name) self.models[self.currentmodel.name] = self.currentmodel return self.currentmodel def rename_model(self, new_name, old_name, rename_old=False): if new_name == old_name: return False else: if rename_old and new_name in self.models: self._rename_samename(new_name) result = self.models[old_name].rename(new_name) if result: self.models[new_name] = self.models.pop(old_name) return True else: return False def _rename_samename(self, name): backupname = self._backupnamer.get_next(self.models, prefix=name) if self.rename_model(backupname, name): warnings.warn("Existing model '%s' renamed to '%s'" % (name, backupname)) else: raise ValueError("Failed to create %s", name) @property def models(self): return self._models @property def currentspace(self): if self.currentmodel: return self.currentmodel.currentspace else: return None def get_curspace(self): """Get or create current space""" if self.currentspace: return self.currentspace else: if self.currentmodel: m = self.currentmodel else: m = self.new_model() # self.new_model sets current_model m.currentspace = m.new_space() return m.currentspace def open_model(self, path, name): with open(path, "rb") as file: model = pickle.load(file) model._impl.restore_state(self) if name is not None: if not is_valid_name(name): raise ValueError("Invalid name '%s'." % name) newname = name or model.name if newname in self.models: self._rename_samename(newname) if name is not None: if not model._impl.rename(name): raise RuntimeError("must not happen") self.models[newname] = model._impl self.currentmodel = model._impl return model def close_model(self, model): del self.models[model.name] if self.currentmodel is model: self.currentmodel = None def get_object(self, name): """Retrieve an object by its absolute name.""" parts = name.split(".") obj = self.models[parts.pop(0)].interface while parts: attr = parts.pop(0) obj = getattr(obj, attr) return obj def get_object_from_tupleid(self, tupleid): """Retrieve an object from tuple id.""" obj = None for key in tupleid: if isinstance(key, str): if obj: obj = getattr(obj, key) else: obj = self.models[key].interface elif isinstance(key, tuple): obj = obj.__call__(*key) else: raise ValueError return obj # ---------------------------------------------------------------------- # Call stack tracing def _is_stacktrace_active(self): return isinstance(self.callstack, TraceableCallStack) def start_stacktrace(self, maxlen): if self._is_stacktrace_active(): return False if self.callstack.is_empty(): self.callstack = self.executor.callstack = TraceableCallStack( maxdepth=self.callstack.maxdepth, maxlen=maxlen) warnings.warn("call stack trace activated") else: raise RuntimeError("callstack not empy") return True def stop_stacktrace(self): if not self._is_stacktrace_active(): return False if self.callstack.is_empty(): self.callstack = self.executor.callstack = CallStack( maxdepth=self.callstack.maxdepth) warnings.warn("call stack trace deactivated") else: raise RuntimeError("callstack not empy") return True def get_stacktrace(self): if self._is_stacktrace_active(): return list(self.callstack.tracestack) else: raise RuntimeError("call stack trace not active") def clear_stacktrace(self): if self._is_stacktrace_active(): self.callstack.tracestack.clear() else: raise RuntimeError("call stack trace not active")