mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-04-29 18:27:23 +02:00
[core][graphIO] Improve node type version handling
* Deserialization: Replace the logic that defaulted the node type version to "0.0" when unspecified, and assume that unspecified version on a node is targetting current node type version. * Serialization: Only serialize node type versions for which a version info is available. * Test suites: * Add helper context manager to manually override the version of a given node type. * Add new unit tests to cover version conflicts handling is various scenarios.
This commit is contained in:
parent
d9e59e330a
commit
87fbcee06d
7 changed files with 98 additions and 9 deletions
|
@ -326,12 +326,13 @@ class Graph(BaseObject):
|
||||||
return graphContent
|
return graphContent
|
||||||
|
|
||||||
def _deserializeNode(self, nodeData: dict, nodeName: str, fromGraph: "Graph"):
|
def _deserializeNode(self, nodeData: dict, nodeName: str, fromGraph: "Graph"):
|
||||||
# Retrieve version from
|
# Retrieve version info from:
|
||||||
# 1. nodeData: node saved from a CompatibilityNode
|
# 1. nodeData: node saved from a CompatibilityNode
|
||||||
# 2. nodesVersion in file header: node saved from a Node
|
# 2. nodesVersion in file header: node saved from a Node
|
||||||
# 3. fallback behavior: default to "0.0"
|
# If unvailable, the "version" field will not be set in `nodeData`.
|
||||||
if "version" not in nodeData:
|
if "version" not in nodeData:
|
||||||
nodeData["version"] = fromGraph._getNodeTypeVersionFromHeader(nodeData["nodeType"], "0.0")
|
if version := fromGraph._getNodeTypeVersionFromHeader(nodeData["nodeType"]):
|
||||||
|
nodeData["version"] = version
|
||||||
inTemplate = fromGraph.header.get(GraphIO.Keys.Template, False)
|
inTemplate = fromGraph.header.get(GraphIO.Keys.Template, False)
|
||||||
node = nodeFactory(nodeData, nodeName, inTemplate=inTemplate)
|
node = nodeFactory(nodeData, nodeName, inTemplate=inTemplate)
|
||||||
self._addNode(node, nodeName)
|
self._addNode(node, nodeName)
|
||||||
|
|
|
@ -100,7 +100,9 @@ class GraphSerializer:
|
||||||
"""Get registered versions of each node types in `nodes`, excluding CompatibilityNode instances."""
|
"""Get registered versions of each node types in `nodes`, excluding CompatibilityNode instances."""
|
||||||
nodeTypes = set([node.nodeDesc.__class__ for node in self.nodes if isinstance(node, Node)])
|
nodeTypes = set([node.nodeDesc.__class__ for node in self.nodes if isinstance(node, Node)])
|
||||||
nodeTypesVersions = {
|
nodeTypesVersions = {
|
||||||
nodeType.__name__: meshroom.core.nodeVersion(nodeType, "0.0") for nodeType in nodeTypes
|
nodeType.__name__: version
|
||||||
|
for nodeType in nodeTypes
|
||||||
|
if (version := meshroom.core.nodeVersion(nodeType)) is not None
|
||||||
}
|
}
|
||||||
# Sort them by name (to avoid random order changing from one save to another).
|
# Sort them by name (to avoid random order changing from one save to another).
|
||||||
return dict(sorted(nodeTypesVersions.items()))
|
return dict(sorted(nodeTypesVersions.items()))
|
||||||
|
|
|
@ -1608,7 +1608,8 @@ class CompatibilityNode(BaseNode):
|
||||||
# Make a deepcopy of nodeDict to handle CompatibilityNode duplication
|
# Make a deepcopy of nodeDict to handle CompatibilityNode duplication
|
||||||
# and be able to change modified inputs (see CompatibilityNode.toDict)
|
# and be able to change modified inputs (see CompatibilityNode.toDict)
|
||||||
self.nodeDict = copy.deepcopy(nodeDict)
|
self.nodeDict = copy.deepcopy(nodeDict)
|
||||||
self.version = Version(self.nodeDict.get("version", None))
|
version = self.nodeDict.get("version")
|
||||||
|
self.version = Version(version) if version else None
|
||||||
|
|
||||||
self._inputs = self.nodeDict.get("inputs", {})
|
self._inputs = self.nodeDict.get("inputs", {})
|
||||||
self._internalInputs = self.nodeDict.get("internalInputs", {})
|
self._internalInputs = self.nodeDict.get("internalInputs", {})
|
||||||
|
|
|
@ -95,7 +95,10 @@ class _NodeCreator:
|
||||||
nodeCreatedFromCurrentVersion = self.version is None
|
nodeCreatedFromCurrentVersion = self.version is None
|
||||||
if nodeCreatedFromCurrentVersion:
|
if nodeCreatedFromCurrentVersion:
|
||||||
return True
|
return True
|
||||||
nodeTypeCurrentVersion = meshroom.core.nodeVersion(self.nodeDesc, "0.0")
|
nodeTypeCurrentVersion = meshroom.core.nodeVersion(self.nodeDesc)
|
||||||
|
# If the node type has not current version information, assume compatibility.
|
||||||
|
if nodeTypeCurrentVersion is None:
|
||||||
|
return True
|
||||||
return Version(self.version).major == Version(nodeTypeCurrentVersion).major
|
return Version(self.version).major == Version(nodeTypeCurrentVersion).major
|
||||||
|
|
||||||
def _checkDescriptionCompatibility(self) -> bool:
|
def _checkDescriptionCompatibility(self) -> bool:
|
||||||
|
|
|
@ -13,7 +13,7 @@ from meshroom.core.exception import GraphCompatibilityError, NodeUpgradeError
|
||||||
from meshroom.core.graph import Graph, loadGraph
|
from meshroom.core.graph import Graph, loadGraph
|
||||||
from meshroom.core.node import CompatibilityNode, CompatibilityIssue, Node
|
from meshroom.core.node import CompatibilityNode, CompatibilityIssue, Node
|
||||||
|
|
||||||
from .utils import registeredNodeTypes
|
from .utils import registeredNodeTypes, overrideNodeTypeVersion
|
||||||
|
|
||||||
|
|
||||||
SampleGroupV1 = [
|
SampleGroupV1 = [
|
||||||
|
@ -473,6 +473,36 @@ class TestGraphTemplateLoading:
|
||||||
|
|
||||||
loadGraph(graph.filepath, strictCompatibility=True)
|
loadGraph(graph.filepath, strictCompatibility=True)
|
||||||
|
|
||||||
|
class TestVersionConflict:
|
||||||
|
|
||||||
|
def test_loadingConflictingNodeVersionCreatesCompatibilityNodes(self, graphSavedOnDisk):
|
||||||
|
graph: Graph = graphSavedOnDisk
|
||||||
|
|
||||||
|
with registeredNodeTypes([SampleNodeV1]):
|
||||||
|
with overrideNodeTypeVersion(SampleNodeV1, "1.0"):
|
||||||
|
node = graph.addNewNode(SampleNodeV1.__name__)
|
||||||
|
graph.save()
|
||||||
|
|
||||||
|
with overrideNodeTypeVersion(SampleNodeV1, "2.0"):
|
||||||
|
otherGraph = Graph("")
|
||||||
|
otherGraph.load(graph.filepath)
|
||||||
|
|
||||||
|
assert len(otherGraph.compatibilityNodes) == 1
|
||||||
|
assert otherGraph.node(node.name).issue is CompatibilityIssue.VersionConflict
|
||||||
|
|
||||||
|
def test_loadingUnspecifiedNodeVersionAssumesCurrentVersion(self, graphSavedOnDisk):
|
||||||
|
graph: Graph = graphSavedOnDisk
|
||||||
|
|
||||||
|
with registeredNodeTypes([SampleNodeV1]):
|
||||||
|
graph.addNewNode(SampleNodeV1.__name__)
|
||||||
|
graph.save()
|
||||||
|
|
||||||
|
with overrideNodeTypeVersion(SampleNodeV1, "2.0"):
|
||||||
|
otherGraph = Graph("")
|
||||||
|
otherGraph.load(graph.filepath)
|
||||||
|
|
||||||
|
assert len(otherGraph.compatibilityNodes) == 0
|
||||||
|
|
||||||
|
|
||||||
class UidTestingNodeV1(desc.Node):
|
class UidTestingNodeV1(desc.Node):
|
||||||
inputs = [
|
inputs = [
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
|
import json
|
||||||
|
from textwrap import dedent
|
||||||
|
|
||||||
from meshroom.core import desc
|
from meshroom.core import desc
|
||||||
from meshroom.core.graph import Graph
|
from meshroom.core.graph import Graph
|
||||||
|
from meshroom.core.node import CompatibilityIssue
|
||||||
|
|
||||||
from .utils import registeredNodeTypes
|
from .utils import registeredNodeTypes, overrideNodeTypeVersion
|
||||||
|
|
||||||
|
|
||||||
class SimpleNode(desc.Node):
|
class SimpleNode(desc.Node):
|
||||||
|
@ -193,6 +197,21 @@ class TestImportGraphContent:
|
||||||
assert len(otherGraph.compatibilityNodes) == 2
|
assert len(otherGraph.compatibilityNodes) == 2
|
||||||
assert not compareGraphsContent(graph, otherGraph)
|
assert not compareGraphsContent(graph, otherGraph)
|
||||||
|
|
||||||
|
def test_importingDifferentNodeVersionCreatesCompatibilityNodes(self, graphSavedOnDisk):
|
||||||
|
graph: Graph = graphSavedOnDisk
|
||||||
|
|
||||||
|
with registeredNodeTypes([SimpleNode]):
|
||||||
|
with overrideNodeTypeVersion(SimpleNode, "1.0"):
|
||||||
|
node = graph.addNewNode(SimpleNode.__name__)
|
||||||
|
graph.save()
|
||||||
|
|
||||||
|
with overrideNodeTypeVersion(SimpleNode, "2.0"):
|
||||||
|
otherGraph = Graph("")
|
||||||
|
nodes = otherGraph.importGraphContentFromFile(graph.filepath)
|
||||||
|
|
||||||
|
assert len(nodes) == 1
|
||||||
|
assert len(otherGraph.compatibilityNodes) == 1
|
||||||
|
assert otherGraph.node(node.name).issue is CompatibilityIssue.VersionConflict
|
||||||
|
|
||||||
class TestGraphPartialSerialization:
|
class TestGraphPartialSerialization:
|
||||||
def test_emptyGraph(self):
|
def test_emptyGraph(self):
|
||||||
|
@ -297,3 +316,23 @@ class TestGraphCopy:
|
||||||
|
|
||||||
graphCopy = graph.copy()
|
graphCopy = graph.copy()
|
||||||
assert not compareGraphsContent(graph, graphCopy)
|
assert not compareGraphsContent(graph, graphCopy)
|
||||||
|
|
||||||
|
|
||||||
|
class TestImportGraphContentFromMinimalGraphData:
|
||||||
|
def test_nodeWithoutVersionInfoIsUpgraded(self):
|
||||||
|
graph = Graph("")
|
||||||
|
|
||||||
|
with (
|
||||||
|
registeredNodeTypes([SimpleNode]),
|
||||||
|
overrideNodeTypeVersion(SimpleNode, "2.0"),
|
||||||
|
):
|
||||||
|
sampleGraphContent = dedent("""
|
||||||
|
{
|
||||||
|
"SimpleNode_1": { "nodeType": "SimpleNode" }
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
graph._deserialize(json.loads(sampleGraphContent))
|
||||||
|
|
||||||
|
assert len(graph.nodes) == 1
|
||||||
|
assert len(graph.compatibilityNodes) == 0
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
from unittest.mock import patch
|
||||||
from typing import Type
|
from typing import Type
|
||||||
from meshroom.core import registerNodeType, unregisterNodeType
|
|
||||||
|
|
||||||
|
import meshroom
|
||||||
|
from meshroom.core import registerNodeType, unregisterNodeType
|
||||||
from meshroom.core import desc
|
from meshroom.core import desc
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
|
@ -13,3 +15,14 @@ def registeredNodeTypes(nodeTypes: list[Type[desc.Node]]):
|
||||||
|
|
||||||
for nodeType in nodeTypes:
|
for nodeType in nodeTypes:
|
||||||
unregisterNodeType(nodeType)
|
unregisterNodeType(nodeType)
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def overrideNodeTypeVersion(nodeType: Type[desc.Node], version: str):
|
||||||
|
"""Helper context manager to override the version of a given node type."""
|
||||||
|
unpatchedFunc = meshroom.core.nodeVersion
|
||||||
|
with patch.object(
|
||||||
|
meshroom.core,
|
||||||
|
"nodeVersion",
|
||||||
|
side_effect=lambda type: version if type is nodeType else unpatchedFunc(type),
|
||||||
|
):
|
||||||
|
yield
|
||||||
|
|
Loading…
Add table
Reference in a new issue