Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions modelx/serialize/serializer_6.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import modelx as mx
from modelx.core.system import mxsys
from modelx.core.model import Model
from modelx.core.base import Interface
from modelx.core.base import Interface, null_impl
from modelx.core.util import (
abs_to_rel, rel_to_abs, abs_to_rel_tuple, rel_to_abs_tuple)
from modelx.core.api import _new_cells_keep_source
Expand Down Expand Up @@ -837,9 +837,13 @@ class InterfaceRefEncoder(BaseEncoder):
@classmethod
def condition(cls, ref, writer):
value = ref.value
return isinstance(value, Interface) and value._is_valid()
return isinstance(value, Interface)

def encode(self):
if not self.target.value._is_valid():
return '("Interface", (None, "%s"))' % (
type(self.target.value).__name__)

idtuple = TupleID(abs_to_rel_tuple(
self.target.value._idtuple,
self.parent._idtuple
Expand All @@ -862,7 +866,10 @@ def pickle_value(self):
idtuple.pickle_args(self.writer.pickledata, self.writer.assign_id)

def instruct(self):
return Instruction(self.pickle_value)
if self.target.value._is_valid():
return Instruction(self.pickle_value)
else:
return None


class LiteralEncoder(BaseEncoder):
Expand Down Expand Up @@ -1751,10 +1758,34 @@ class InterfaceDecoder(TupleDecoder):
DECTYPE = "Interface"

def decode(self):
return rel_to_abs(self.value[1], self.obj.fullname)
keys = self.value[1]
if (isinstance(keys, tuple)
and len(keys) == 2
and keys[0] is None):
return None
else:
return rel_to_abs(keys, self.obj.fullname)

def restore(self):
keys = self.value[1]
if (isinstance(keys, tuple)
and len(keys) == 2
and keys[0] is None):
from modelx.core.cells import Cells
from modelx.core.macro import Macro
from modelx.core.space import DynamicSpace, ItemSpace, UserSpace

interface_types = {
cls.__name__: cls for cls in (
Cells, DynamicSpace, ItemSpace, Macro, Model, UserSpace
)
}
type_name = keys[1]
if type_name not in interface_types:
raise ValueError(
"unknown null interface type: %s" % type_name)
cls = interface_types[type_name]
return cls.__new__(cls, null_impl)
if any(isinstance(k, int) and not self.reader.find_pickledata(k)[0]
for k in keys):
self._warn_lost_ref()
Expand Down
26 changes: 26 additions & 0 deletions modelx/tests/serialize/test_serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,32 @@ def test_null_object(tmp_path, write_method):
testutil.compare_model(m, m2)


@pytest.mark.parametrize("version", [6, 7, 8])
def test_null_object_is_serialized_as_interface(
tmp_path, version, close_new_models):
m = mx.new_model()
space = m.new_space("Space")
cells = space.new_cells("foo")
space.relref(deleted=cells)

del space.foo
path = tmp_path / "model"
mx.write_model(m, path, version=version)
m.close()

source = (path / "Space" / "__init__.py").read_text(encoding="utf-8")
assert 'deleted = ("Interface", (None, "Cells"))' in source
assert not (path / "_data" / "data.pickle").exists()

restored = mx.read_model(path)
assert not restored.Space.deleted._is_valid()
assert type(restored.Space.deleted) is type(cells)
# A relative ref cannot resolve against a deleted target. This matches
# the previous pickle path, which restored the ref in auto mode.
assert restored.Space._get_object(
"deleted", as_proxy=True).refmode == "auto"


@pytest.mark.parametrize("write_method", ["write_model", "zip_model"])
def test_false_value(tmp_path, write_method):
# https://github.com/fumitoh/modelx/issues/39
Expand Down