Source code for modelx.core.reference

# Copyright (c) 2017-2021 Fumito Hamamura <fumito.ham@gmail.com>

# This library is free software: you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation version 3.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library.  If not, see <http://www.gnu.org/licenses/>.

import sys
import builtins
import importlib
from types import ModuleType, FunctionType
import functools

from modelx.core.base import (
    add_stateattrs, Derivable, Impl, Interface)
from modelx.io.baseio import BaseDataClient
from modelx.core.node import ObjectNode, get_node, OBJ


# For backward compatibility with -v0.0.23
class _DummyBuiltins:
    pass


class _BasePickler:

    __slots__ = ("value",)

    def __init__(self, value):
        self.value = self.convert(value)

    @classmethod
    def condition(cls, value):
        raise NotImplementedError

    def convert(self, value):
        raise NotImplementedError

    def restore(self):
        raise NotImplementedError


class _ModulePickler(_BasePickler):

    __slots__ = ()

    @classmethod
    def condition(cls, value):
        return isinstance(value, ModuleType)

    def convert(self, value):
        for k, v in sys.modules.items():
            if value is v:
                return k
        raise ValueError("must not happen")

    def restore(self):
        return importlib.import_module(self.value)


class _HiddenDataClientPickler(_BasePickler):

    __slots__ = ()

    @classmethod
    def condition(cls, value):
        return hasattr(value, "_mx_dataclient")

    def convert(self, value):
        return value._mx_dataclient

    def restore(self):
        return self.value.value


@add_stateattrs
class ReferenceImpl(Derivable, Impl):

    picklers = []    # List of _BasePickler sub classes

    __cls_stateattrs = [
        "container",
        "refmode",
        "is_relative"
    ]

    @classmethod
    def get_class(cls, value):
        if isinstance(value, BaseDataClient):
            return DataClientReferenceImpl
        elif not isinstance(    # Avoid null object
                value, Interface) and hasattr(value, "_mx_dataclient"):
            return DataClientReferenceImpl
        else:
            return cls

    def __init__(self, parent, name, value, container, is_derived=False,
                 refmode=None, set_item=True):
        Impl.__init__(
            self,
            system=parent.system,
            parent=parent,
            name=name,
            interface=value)
        self.spacemgr = parent.spacemgr
        Derivable.__init__(self, is_derived)

        self.container = container
        if set_item:
            container.set_item(name, self)

        self.refmode = refmode
        if refmode == "absolute":
            self.is_relative = False
        else:   # 'auto' or 'relative'
            self.is_relative = True

        self.on_init(value)

    def on_init(self, value):
        pass

    def on_delete(self):
        pass

    def __getstate__(self):
        state = {
            key: value
            for key, value in self.__dict__.items()
            if key in self.stateattrs
        }
        value = state["interface"]

        for pickler in self.picklers:
            if pickler.condition(value):
                state["interface"] = pickler(value)
                break

        return state

    def __setstate__(self, state):

        if isinstance(state["interface"], _DummyBuiltins):
            # For backward compatibility with -v0.0.23
            state["interface"] = builtins
        else:
            if isinstance(state["interface"], _BasePickler):
                state["interface"] = state["interface"].restore()

        self.__dict__.update(state)

    def to_node(self):
        return ReferenceNode(get_node(self, None, None))

    def repr_parent(self):
        if self.parent.repr_parent():
            return self.parent.repr_parent() + "." + self.parent.repr_self()
        else:
            return self.parent.repr_self()

    def repr_self(self, add_params=True):
        return self.name

    @staticmethod
    def _get_members(other):
        return other.self_refs

    def has_interface(self):
        return (isinstance(self.interface, Interface)
                and self.interface._is_valid())

    def on_inherit(self, updater, bases):

        self.model.clear_obj(self)
        if bases[0].has_interface():

            if self.refmode == "absolute":
                self.interface = bases[0].interface
                self.is_relative = False
            else:
                is_relative, interface = updater.get_relative_interface(
                    self.parent,
                    bases[0])
                if self.refmode == "auto":
                    self.is_relative = is_relative
                    self.interface = interface
                elif self.refmode == "relative":
                    if is_relative:
                        self.is_relative = is_relative
                        self.interface = interface
                    else:
                        raise ValueError(
                            "Relative reference %s out of scope" %
                            self.get_fullname()
                        )
                else:
                    raise ValueError("must not happen")
        else:
            self.interface = bases[0].interface
        self.container.set_refresh()


ReferenceImpl.picklers.append(_ModulePickler)


class DataClientReferenceImpl(ReferenceImpl):

    picklers = [_HiddenDataClientPickler]

    def on_init(self, value):

        if isinstance(value, BaseDataClient):
            client = value
        else:
            client = value._mx_dataclient

        self.model.datarefmgr.add_reference(self, client)

    def on_delete(self):

        if isinstance(self.interface, BaseDataClient):
            client = self.interface
        else:
            client = self.interface._mx_dataclient

        self.model.datarefmgr.del_reference(self, client)


[docs]class ReferenceProxy: """Proxy to interface to References *Reference* objects are not exposed to the user, thus ReferenceProxy objects are used to interface to References. A proxy object to a Reference can be created and returned by the :func:`~modelx.get_object` function, by passing the full dotted name of the Reference to ``name``, and :obj:`True` to ``as_proxy``:: >>> mx.get_object("Model1.Space1.foo", as_proxy=True) Reference shares its ultimate base class with Model, Space and Cells classes, and below are attributes common among those classes. Attributes: name (str): The name of the Reference. fullname (str): The dotted name of the object. parent: The parent of the Reference. model: The Model that the Reference belongs to. .. seealso:: :func:`~modelx.get_object`, :class:`~ReferenceNode` """ __slots__ = ("_impl",) def __init__(self, impl): self._impl = impl def __getattr__(self, name): item = getattr(Interface, name) if isinstance(item, property): return item.fget(self) elif isinstance(item, FunctionType): return functools.partial(item, self) @property def value(self): """Returns referenced object""" return self._impl.interface @property def refmode(self): """Returns reference mode Returns a string representing the reference mode Returns: str: "auto", "absolute" or "relative" .. seealso:: :meth:`~modelx.core.space.UserSpace.set_ref`, :meth:`~modelx.core.space.UserSpace.absref`, :meth:`~modelx.core.space.UserSpace.relref` """ return self._impl.refmode @property def is_derived(self): # TODO: Rename this to _is_derived return self._impl.is_derived @property def _baseattrs(self): result = Interface._baseattrs.fget(self) result["type"] = "Reference" result["value_type"] = type(self.value).__name__ return result def _get_attrdict(self, extattrs=None, recursive=True): result = Interface._get_attrdict(self, extattrs, recursive) result["type"] = "Reference" result["value_type"] = type(self.value).__name__ if extattrs: Interface._get_attrdict_extra(self, result, extattrs, recursive) return result @property def _evalrepr(self): return Interface._evalrepr.fget(self)
[docs]class ReferenceNode(ObjectNode): @property def obj(self): """Return the ReferenceProxy object""" return ReferenceProxy(self._impl[OBJ])
[docs] def has_value(self): """Always returns :obj:`True` as Reference has value""" return True
@property def value(self): return self._impl[OBJ].interface