コード例 #1
0
ファイル: test_util.py プロジェクト: dustindall/modelx
def simplenamer():
    autonamer = AutoNamer("Cells")

    for i in range(10):
        autonamer.get_next([])

    return autonamer
コード例 #2
0
ファイル: system.py プロジェクト: edmang/modelx
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
コード例 #3
0
ファイル: system.py プロジェクト: dustindall/modelx
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))
コード例 #4
0
ファイル: model.py プロジェクト: alebaran/modelx
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
コード例 #5
0
ファイル: test_util.py プロジェクト: dustindall/modelx
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"
コード例 #6
0
ファイル: model.py プロジェクト: actuarial-tools/modelx
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
コード例 #7
0
ファイル: system.py プロジェクト: rahmanifard/modelx
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")