Przeglądaj źródła

Implemented State

Andrei Bondarenko 4 lat temu
rodzic
commit
046266bfa4

+ 27 - 25
.gitignore

@@ -10,6 +10,7 @@ __pycache__/
 # Distribution / packaging
 .Python
 env/
+venv/
 build/
 develop-eggs/
 dist/
@@ -59,34 +60,35 @@ docs/_build/
 target/
 
 # ---> macOS
-.DS_Store
-.AppleDouble
-.LSOverride
-
-# Icon must end with two \r
-Icon
-
-
-# Thumbnails
-._*
-
-# Files that might appear in the root of a volume
-.DocumentRevisions-V100
-.fseventsd
-.Spotlight-V100
-.TemporaryItems
-.Trashes
-.VolumeIcon.icns
-
-# Directories potentially created on remote AFP share
-.AppleDB
-.AppleDesktop
-Network Trash Folder
-Temporary Items
-.apdisk
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
 
 # ---> VisualStudioCode
 .settings
+.vscode/settings.json
 
 
 # ---> Linux

+ 9 - 1
README.md

@@ -1,3 +1,11 @@
 # MV2
 
-This repository contains the code for my take on (a part of) the [Modelverse](https://msdl.uantwerpen.be/git/yentl/modelverse) for my Master's thesis.
+This repository contains the code for my take on (a part of) the [Modelverse](https://msdl.uantwerpen.be/git/yentl/modelverse) for my Master's thesis.
+
+## Development packages
+
+Some packages were used during development, but are not needed for succesful runtime (e.g. linter, autoformatter). These can be found under `requirements_dev.txt`.
+
+## Mandatory packages
+
+Python packages required to succesfully run/test the code in this repository can be found under `requirements.txt`.

+ 3 - 0
requirements.txt

@@ -0,0 +1,3 @@
+pytest==6.2.4
+neo4j==4.3.4
+rdflib==6.0.0

+ 0 - 0
state/__init__.py


+ 296 - 0
state/base.py

@@ -0,0 +1,296 @@
+from abc import ABC, abstractmethod
+from typing import Any, List, Tuple, Optional, Union
+from uuid import UUID, uuid4
+
+primitive_types = (int, float, str, bool)
+INTEGER = ("Integer",)
+FLOAT = ("Float",)
+STRING = ("String",)
+BOOLEAN = ("Boolean",)
+TYPE = ("Type",)
+type_values = (INTEGER, FLOAT, STRING, BOOLEAN, TYPE)
+
+
+Node = UUID
+Edge = UUID
+Element = Union[Node, Edge]
+
+
+class State(ABC):
+    """
+    Abstract base class for MvS CRUD interface defined in:
+    http://msdl.cs.mcgill.ca/people/yentl/files/thesis.pdf
+    This code is based on:
+    https://msdl.uantwerpen.be/git/yentl/modelverse/src/master/state/modelverse_state
+    """
+
+    @staticmethod
+    def new_id() -> UUID:
+        """
+        Generates a new UUID
+        """
+        return uuid4()
+
+    @staticmethod
+    def is_valid_datavalue(value: Any) -> bool:
+        """
+        Checks whether value type is supported.
+
+        Args:
+            value: value whose type needs to be checked
+
+        Returns:
+            True if value type is supported, False otherwise.
+        """
+        if isinstance(value, tuple) and value in type_values:
+            return True
+        if not isinstance(value, primitive_types):
+            return False
+        elif isinstance(value, int) and not (-2**63 <= value <= 2**63 - 1):
+            return False
+        return True
+
+    def purge(self):
+        """
+        Implements a garbage collection routine for implementations that don't have automatic garbage collection.
+        """
+        pass
+
+    # =========================================================================
+    # CREATE
+    # =========================================================================
+
+    @abstractmethod
+    def create_node(self) -> Node:
+        """
+        Creates node.
+
+        Returns:
+            The created node.
+        """
+        pass
+
+    @abstractmethod
+    def create_edge(self, source: Element, target: Element) -> Optional[Edge]:
+        """
+        Creates edge. Source and target elements should already exist.
+
+        Args:
+            source: source element of edge
+            target: target element of edge
+
+        Returns:
+            The created edge, None if source or target element doesn't exist.
+        """
+        pass
+
+    @abstractmethod
+    def create_nodevalue(self, value: Any) -> Optional[Node]:
+        """
+        Creates node containing value.
+
+        Args:
+            value: value to assign to new node
+
+        Returns:
+            The created node, None if type of value is not supported.
+        """
+        pass
+
+    @abstractmethod
+    def create_dict(self, source: Element, value: Any, target: Element) -> None:
+        """
+        Creates named edge between two graph elements.
+
+        Args:
+            source: source element of edge
+            value: edge label
+            target: target element of edge
+
+        Returns:
+            Nothing.
+        """
+        pass
+
+    # =========================================================================
+    # READ
+    # =========================================================================
+
+    @abstractmethod
+    def read_root(self) -> Node:
+        """
+        Reads state's root node.
+
+        Returns:
+            The state's root node.
+        """
+        pass
+
+    @abstractmethod
+    def read_value(self, node: Node) -> Optional[Any]:
+        """
+        Reads value of given node.
+
+        Args:
+            node: node whose value to read
+
+        Returns:
+            I node exists, value stored in node, else None.
+        """
+        pass
+
+    @abstractmethod
+    def read_outgoing(self, elem: Element) -> Optional[List[Edge]]:
+        """
+        Retrieves edges whose source is given element.
+        Args:
+            elem: source element of edges to retrieve
+
+        Returns:
+            If elem exists, list of edges whose source is elem, else None.
+        """
+        pass
+
+    @abstractmethod
+    def read_incoming(self, elem: Element) -> Optional[List[Edge]]:
+        """
+        Retrieves edges whose target is given element.
+        Args:
+            elem: target element of edges to retrieve
+
+        Returns:
+            If elem exists, list of edges whose target is elem, else None.
+        """
+        pass
+
+    @abstractmethod
+    def read_edge(self, edge: Edge) -> Tuple[Optional[Node], Optional[Node]]:
+        """
+        Reads source and target of given edge.
+
+        Args:
+            edge: edge whose source and target to read
+
+        Returns:
+            If edge exists, tuple containing source (first) and target (second) node, else (None, None)
+        """
+        pass
+
+    @abstractmethod
+    def read_dict(self, elem: Element, value: Any) -> Optional[Element]:
+        """
+        Reads element connected to given element through edge with label = value.
+
+        Args:
+            elem: source element
+            value: edge label
+
+        Returns:
+            If elem doesn't exist or no edge is found with given label, None, else target element of edge  with label = value originating from source.
+        """
+        pass
+
+    @abstractmethod
+    def read_dict_keys(self, elem: Element) -> Optional[List[Element]]:
+        """
+        Reads labels of outgoing edges starting in given node.
+
+        Args:
+            elem: source element
+
+        Returns:
+            If elem exists, list of (unique) edge labels, else None.
+        """
+        pass
+
+    @abstractmethod
+    def read_dict_edge(self, elem: Element, value: Any) -> Optional[Edge]:
+        """
+        Reads edge between two elements connected through edge with label = value.
+
+        Args:
+            elem: source element
+            value: edge label
+
+        Returns:
+            If elem doesn't exist or no edge is found with given label, None, else edge with label = value originating from source.
+        """
+        pass
+
+    @abstractmethod
+    def read_dict_node(self, elem: Element, value_node: Node) -> Optional[Element]:
+        """
+        Reads element connected to given element through edge with label node = value_node.
+
+        Args:
+            elem: source element
+            value_node: edge label node
+
+        Returns:
+            If elem exists, target element of edge with label stored in value_node originating from elem, else None.
+        """
+        pass
+
+    @abstractmethod
+    def read_dict_node_edge(self, elem: Element, value_node: Node) -> Optional[Edge]:
+        """
+        Reads edge connecting two elements through edge with label node = value_node.
+
+        Args:
+            elem: source element
+            value_node: edge label node
+
+        Returns:
+            If elem exists, edge with label node = value_node, originating from source, else None.
+        """
+        pass
+
+    @abstractmethod
+    def read_reverse_dict(self, elem: Element, value: Any) -> Optional[List[Element]]:
+        """
+        Retrieves a list of all elements that have an outgoing edge, having label = value, towards the passed element.
+
+        Args:
+            elem: target element
+            value: edge label
+
+        Returns:
+            If elem exists, list of elements with an outgoing edge with label = value towards elem, else None.
+        """
+        pass
+
+    # =========================================================================
+    # UPDATE
+    # =========================================================================
+    """
+    Updates are done by performing subsequent CREATE and DELETE operations:
+    http://msdl.cs.mcgill.ca/people/yentl/files/thesis.pdf
+    """
+
+    # =========================================================================
+    # DELETE
+    # =========================================================================
+
+    @abstractmethod
+    def delete_node(self, node: Node) -> None:
+        """
+        Deletes given node from state graph.
+        Args:
+            node: node to be deleted
+
+        Returns:
+            None
+        """
+        pass
+
+    @abstractmethod
+    def delete_edge(self, edge: Edge) -> None:
+        """
+        Deletes given edge from state graph.
+        Args:
+            edge: edge to be deleted
+
+        Returns:
+            None
+        """
+        pass

+ 53 - 0
state/devstate.py

@@ -0,0 +1,53 @@
+from state.pystate import PyState
+from uuid import UUID
+
+
+class DevState(PyState):
+    """
+    Version of PyState that allows dumping to .dot files
+    + node id's are generated sequentially to make writing tests easier
+    """
+
+    free_id = 0
+
+    def __init__(self):
+        super().__init__()
+
+    @staticmethod
+    def new_id() -> UUID:
+        DevState.free_id += 1
+        return UUID(int=DevState.free_id - 1)
+
+    def dump(self, path: str, png_path: str = None):
+        """Dumps the whole MV graph to a graphviz .dot-file
+
+        Args:
+            path (str): path for .dot-file
+            png_path (str, optional): path for .png image generated from the .dot-file. Defaults to None.
+        """
+        with open(path, "w") as f:
+            f.write("digraph main {\n")
+            for n in sorted(self.nodes):
+                if n in self.values:
+                    x = self.values[n]
+                    if isinstance(x, tuple):
+                        x = f"{x[0]}"
+                    else:
+                        x = repr(x)
+                    f.write("\"a_%s\" [label=\"%s\"];\n" % (
+                        n.int, x.replace('"', '\\"')))
+                else:
+                    f.write("\"a_%s\" [label=\"\"];\n" % n)
+            for i, e in sorted(list(self.edges.items())):
+                f.write("\"a_%s\" [label=\"e_%s\" shape=point];\n" % (i.int, i.int))
+                f.write("\"a_%s\" -> \"a_%s\" [arrowhead=none];\n" % (e[0].int, i.int))
+                f.write("\"a_%s\" -> \"a_%s\";\n" % (i.int, e[1].int))
+            f.write("}")
+
+        if png_path is not None:
+            # generate png from dot-file
+            bashCommand = f"dot -Tpng {path} -o {png_path}"
+            import subprocess
+            process = subprocess.Popen(
+                bashCommand.split(), stdout=subprocess.PIPE)
+            output, error = process.communicate()

+ 301 - 0
state/neo4jstate.py

@@ -0,0 +1,301 @@
+from typing import Any, Optional, List, Tuple, Callable, Generator
+from neo4j import GraphDatabase
+from ast import literal_eval
+
+from .base import State, Edge, Node, Element, UUID
+
+
+
+class Neo4jState(State):
+    def __init__(self, uri="bolt://localhost:7687", user="neo4j", password="tests"):
+        self.driver = GraphDatabase.driver(uri, auth=(user, password))
+        self.root = self.create_node()
+
+    def close(self, *, clear=False):
+        if clear:
+            self._run_and_return(self._clear)
+        self.driver.close()
+
+    def _run_and_return(self, query: Callable, **kwargs):
+        with self.driver.session() as session:
+            result = session.write_transaction(query, **kwargs)
+            return result
+
+    @staticmethod
+    def _clear(tx):
+        tx.run("MATCH (n) "
+               "DETACH DELETE n")
+
+    @staticmethod
+    def _existence_check(tx, eid, label="Element"):
+        result = tx.run(f"MATCH (elem:{label}) "
+                        "WHERE elem.id = $eid "
+                        "RETURN elem.id",
+                        eid=eid)
+        try:
+            return result.single()[0]
+        except TypeError:
+            # No node found for nid
+            # ergo, no edge created
+            return None
+
+    def create_node(self) -> Node:
+        def query(tx, nid):
+            result = tx.run("CREATE (n:Element:Node) "
+                            "SET n.id = $nid "
+                            "RETURN n.id",
+                            nid=nid)
+            return result.single()[0]
+
+        node = self._run_and_return(query, nid=str(self.new_id()))
+        return UUID(node) if node is not None else None
+
+    def create_edge(self, source: Element, target: Element) -> Optional[Edge]:
+        def query(tx, eid, sid, tid):
+            result = tx.run("MATCH (source), (target) "
+                            "WHERE source.id = $sid AND target.id = $tid "
+                            "CREATE (source) -[:Source]-> (e:Element:Edge) -[:Target]-> (target) "
+                            "SET e.id = $eid "
+                            "RETURN e.id",
+                            eid=eid, sid=sid, tid=tid)
+            try:
+                return result.single()[0]
+            except TypeError:
+                # No node found for sid and/or tid
+                # ergo, no edge created
+                return None
+
+        edge = self._run_and_return(query, eid=str(self.new_id()), sid=str(source), tid=str(target))
+        return UUID(edge) if edge is not None else None
+
+    def create_nodevalue(self, value: Any) -> Optional[Node]:
+        def query(tx, nid, val):
+            result = tx.run("CREATE (n:Element:Node) "
+                            "SET n.id = $nid, n.value = $val "
+                            "RETURN n.id",
+                            nid=nid, val=val)
+            return result.single()[0]
+
+        if not self.is_valid_datavalue(value):
+            return None
+
+        node = self._run_and_return(query, nid=str(self.new_id()), val=repr(value))
+        return UUID(node) if node is not None else None
+
+    def create_dict(self, source: Element, value: Any, target: Element) -> Optional[Tuple[Edge, Edge, Node]]:
+        if not self.is_valid_datavalue(value):
+            return None
+
+        edge_node = self.create_edge(source, target)
+        val_node = self.create_nodevalue(value)
+        if edge_node is not None and val_node is not None:
+            self.create_edge(edge_node, val_node)
+
+    def read_root(self) -> Node:
+        return self.root
+
+    def read_value(self, node: Node) -> Optional[Any]:
+        def query(tx, nid):
+            result = tx.run("MATCH (n:Node) "
+                            "WHERE n.id = $nid "
+                            "RETURN n.value",
+                            nid=nid)
+            try:
+                return result.single()[0]
+            except TypeError:
+                # No node found for nid
+                return None
+
+        value = self._run_and_return(query, nid=str(node))
+        return literal_eval(value) if value is not None else None
+
+    def read_outgoing(self, elem: Element) -> Optional[List[Edge]]:
+        def query(tx, eid):
+            result = tx.run("MATCH (elem:Element) -[:Source]-> (e:Edge) "
+                            "WHERE elem.id = $eid "
+                            "RETURN e.id",
+                            eid=eid)
+            return result.value()
+
+        source_exists = self._run_and_return(self._existence_check, eid=str(elem)) is not None
+        if source_exists:
+            result = self._run_and_return(query, eid=str(elem))
+            return [UUID(x) for x in result] if result is not None else None
+
+    def read_incoming(self, elem: Element) -> Optional[List[Edge]]:
+        def query(tx, eid):
+            result = tx.run("MATCH (elem:Element) <-[:Target]- (e:Edge) "
+                            "WHERE elem.id = $eid "
+                            "RETURN e.id",
+                            eid=eid)
+            return result.value()
+
+        target_exists = self._run_and_return(self._existence_check, eid=str(elem)) is not None
+        if target_exists:
+            result = self._run_and_return(query, eid=str(elem))
+            return [UUID(x) for x in result] if result is not None else None
+
+    def read_edge(self, edge: Edge) -> Tuple[Optional[Node], Optional[Node]]:
+        def query(tx, eid):
+            result = tx.run("MATCH (src) -[:Source]-> (e:Edge) -[:Target]-> (tgt)"
+                            "WHERE e.id = $eid "
+                            "RETURN src.id, tgt.id",
+                            eid=eid)
+            return result.single()
+
+        edge_exists = self._run_and_return(self._existence_check, eid=str(edge), label="Edge") is not None
+        if edge_exists:
+            try:
+                src, tgt = self._run_and_return(query, eid=str(edge))
+                return UUID(src), UUID(tgt)
+            except TypeError:
+                return None, None
+        else:
+            return None, None
+
+    def read_dict(self, elem: Element, value: Any) -> Optional[Element]:
+        def query(tx, eid, label_value):
+            result = tx.run("MATCH (src) -[:Source]-> (e:Edge) -[:Target]-> (tgt), "
+                            "(e) -[:Source]-> (:Edge) -[:Target]-> (label)"
+                            "WHERE src.id = $eid "
+                            "AND label.value = $val "
+                            "RETURN tgt.id",
+                            eid=eid, val=label_value)
+            try:
+                return result.single()[0]
+            except TypeError:
+                # No edge found with given label
+                return None
+
+        elem_exists = self._run_and_return(self._existence_check, eid=str(elem)) is not None
+        if elem_exists:
+            if isinstance(value, UUID):
+                return None
+            result = self._run_and_return(query, eid=str(elem), label_value=repr(value))
+            return UUID(result) if result is not None else None
+
+    def read_dict_keys(self, elem: Element) -> Optional[List[Any]]:
+        def query(tx, eid):
+            result = tx.run("MATCH (src) -[:Source]-> (e:Edge) -[:Target]-> (), "
+                            "(e) -[:Source]-> (:Edge) -[:Target]-> (label)"
+                            "WHERE src.id = $eid "
+                            "RETURN label.id",
+                            eid=eid)
+            try:
+                return result.value()
+            except TypeError:
+                # No edge found with given label
+                return None
+
+        elem_exists = self._run_and_return(self._existence_check, eid=str(elem)) is not None
+        if elem_exists:
+            result = self._run_and_return(query, eid=str(elem))
+            return [UUID(x) for x in result if x is not None]
+
+    def read_dict_edge(self, elem: Element, value: Any) -> Optional[Edge]:
+        def query(tx, eid, label_value):
+            result = tx.run("MATCH (src) -[:Source]-> (e:Edge) -[:Target]-> (), "
+                            "(e) -[:Source]-> (:Edge) -[:Target]-> (label)"
+                            "WHERE src.id = $eid "
+                            "AND label.value = $val "
+                            "RETURN e.id",
+                            eid=eid, val=label_value)
+            try:
+                return result.single()[0]
+            except TypeError:
+                # No edge found with given label
+                return None
+
+        elem_exists = self._run_and_return(self._existence_check, eid=str(elem)) is not None
+        if elem_exists:
+            result = self._run_and_return(query, eid=str(elem), label_value=repr(value))
+            return UUID(result) if result is not None else None
+
+    def read_dict_node(self, elem: Element, value_node: Node) -> Optional[Element]:
+        def query(tx, eid, label_id):
+            result = tx.run("MATCH (src) -[:Source]-> (e:Edge) -[:Target]-> (tgt), "
+                            "(e) -[:Source]-> (:Edge) -[:Target]-> (label)"
+                            "WHERE src.id = $eid "
+                            "AND label.id = $lid "
+                            "RETURN tgt.id",
+                            eid=eid, lid=label_id)
+            try:
+                return result.single()[0]
+            except TypeError:
+                # No edge found with given label
+                return None
+
+        elem_exists = self._run_and_return(self._existence_check, eid=str(elem)) is not None
+        if elem_exists:
+            result = self._run_and_return(query, eid=str(elem), label_id=str(value_node))
+            return UUID(result) if result is not None else None
+
+    def read_dict_node_edge(self, elem: Element, value_node: Node) -> Optional[Edge]:
+        def query(tx, eid, label_id):
+            result = tx.run("MATCH (src) -[:Source]-> (e:Edge) -[:Target]-> (), "
+                            "(e) -[:Source]-> (:Edge) -[:Target]-> (label)"
+                            "WHERE src.id = $eid "
+                            "AND label.id = $lid "
+                            "RETURN e.id",
+                            eid=eid, lid=label_id)
+            try:
+                return result.single()[0]
+            except TypeError:
+                # No edge found with given label
+                return None
+
+        elem_exists = self._run_and_return(self._existence_check, eid=str(elem)) is not None
+        if elem_exists:
+            result = self._run_and_return(query, eid=str(elem), label_id=str(value_node))
+            return UUID(result) if result is not None else None
+
+    def read_reverse_dict(self, elem: Element, value: Any) -> Optional[List[Element]]:
+        def query(tx, eid, label_value):
+            result = tx.run("MATCH (src) -[:Source]-> (e:Edge) -[:Target]-> (tgt), "
+                            "(e) -[:Source]-> (:Edge) -[:Target]-> (label)"
+                            "WHERE tgt.id = $eid "
+                            "AND label.value = $val "
+                            "RETURN src.id",
+                            eid=eid, val=label_value)
+            try:
+                return result.value()
+            except TypeError:
+                # No edge found with given label
+                return None
+
+        elem_exists = self._run_and_return(self._existence_check, eid=str(elem)) is not None
+        if elem_exists:
+            result = self._run_and_return(query, eid=str(elem), label_value=repr(value))
+            return [UUID(x) for x in result if x is not None]
+
+    def delete_node(self, node: Node) -> None:
+        def query(tx, nid):
+            result = tx.run("MATCH (n:Node) "
+                            "WHERE n.id = $nid "
+                            "OPTIONAL MATCH (n) -- (e:Edge) "
+                            "DETACH DELETE n "
+                            "RETURN e.id",
+                            nid=nid)
+            return result.value()
+
+        to_be_deleted = self._run_and_return(query, nid=str(node))
+        to_be_deleted = [UUID(x) for x in to_be_deleted if x is not None]
+        for edge in to_be_deleted:
+            self.delete_edge(edge)
+
+    def delete_edge(self, edge: Edge) -> None:
+        def query(tx, eid):
+            result = tx.run("MATCH (e1:Edge) "
+                            "WHERE e1.id = $eid "
+                            "OPTIONAL MATCH (e1) -- (e2:Edge) "
+                            "WHERE (e1) -[:Source]-> (e2) "
+                            "OR (e1) <-[:Target]- (e2) "
+                            "DETACH DELETE e1 "
+                            "RETURN e2.id",
+                            eid=eid)
+            return result.value()
+
+        to_be_deleted = self._run_and_return(query, eid=str(edge))
+        to_be_deleted = [UUID(x) for x in to_be_deleted if x is not None]
+        for edge in to_be_deleted:
+            self.delete_edge(edge)

+ 286 - 0
state/pystate.py

@@ -0,0 +1,286 @@
+from typing import Any, List, Tuple, Optional
+
+from state.base import State, Node, Edge, Element
+
+
+class PyState(State):
+    """
+    State interface implemented using Python data structures.
+
+    This code is based on:
+    https://msdl.uantwerpen.be/git/yentl/modelverse/src/master/state/modelverse_state/main.py
+    """
+    def __init__(self):
+        self.edges = {}
+        self.outgoing = {}
+        self.incoming = {}
+        self.values = {}
+        self.nodes = set()
+        # Set used for garbage collection
+        self.GC = True
+        self.to_delete = set()
+
+        self.cache = {}
+        self.cache_node = {}
+
+        self.root = self.create_node()
+
+    def create_node(self) -> Node:
+        new_id = self.new_id()
+        self.nodes.add(new_id)
+        return new_id
+
+    def create_edge(self, source: Element, target: Element) -> Optional[Edge]:
+        if source not in self.edges and source not in self.nodes:
+            return None
+        elif target not in self.edges and target not in self.nodes:
+            return None
+        else:
+            new_id = self.new_id()
+            self.outgoing.setdefault(source, set()).add(new_id)
+            self.incoming.setdefault(target, set()).add(new_id)
+            self.edges[new_id] = (source, target)
+            if source in self.edges:
+                # We are creating something dict_readable
+                # Fill in the cache already!
+                dict_source, dict_target = self.edges[source]
+                if target in self.values:
+                    self.cache.setdefault(dict_source, {})[self.values[target]] = source
+                self.cache_node.setdefault(dict_source, {})[target] = source
+            return new_id
+
+    def create_nodevalue(self, value: Any) -> Optional[Node]:
+        if not self.is_valid_datavalue(value):
+            return None
+        new_id = self.new_id()
+        self.values[new_id] = value
+        self.nodes.add(new_id)
+        return new_id
+
+    def create_dict(self, source: Element, value: Any, target: Element) -> None:
+        if source not in self.nodes and source not in self.edges:
+            return None
+        elif target not in self.nodes and target not in self.edges:
+            return None
+        elif not self.is_valid_datavalue(value):
+            return None
+        else:
+            n = self.create_nodevalue(value)
+            e = self.create_edge(source, target)
+            assert n is not None and e is not None
+            e2 = self.create_edge(e, n)
+            self.cache.setdefault(source, {})[value] = e
+            self.cache_node.setdefault(source, {})[n] = e
+
+    def read_root(self) -> Node:
+        return self.root
+
+    def read_value(self, node: Node) -> Any:
+        if node in self.values:
+            return self.values[node]
+        else:
+            return None
+
+    def read_outgoing(self, elem: Element) -> Optional[List[Edge]]:
+        if elem in self.edges or elem in self.nodes:
+            if elem in self.outgoing:
+                return list(self.outgoing[elem])
+            else:
+                return []
+        else:
+            return None
+
+    def read_incoming(self, elem: Element) -> Optional[List[Edge]]:
+        if elem in self.edges or elem in self.nodes:
+            if elem in self.incoming:
+                return list(self.incoming[elem])
+            else:
+                return []
+        else:
+            return None
+
+    def read_edge(self, edge: Edge) -> Tuple[Optional[Element], Optional[Element]]:
+        if edge in self.edges:
+            return self.edges[edge][0], self.edges[edge][1]
+        else:
+            return None, None
+
+    def read_dict(self, elem: Element, value: Any) -> Optional[Element]:
+        e = self.read_dict_edge(elem, value)
+        if e is None:
+            return None
+        else:
+            return self.edges[e][1]
+
+    def read_dict_keys(self, elem: Element) -> Optional[List[Element]]:
+        if elem not in self.nodes and elem not in self.edges:
+            return None
+
+        result = []
+        # NOTE: cannot just use the cache here, as some keys in the cache might not actually exist;
+        # we would have to check all of them anyway
+        if elem in self.outgoing:
+            for e1 in self.outgoing[elem]:
+                if e1 in self.outgoing:
+                    for e2 in self.outgoing[e1]:
+                        result.append(self.edges[e2][1])
+        return result
+
+    def read_dict_edge(self, elem: Element, value: Any) -> Optional[Edge]:
+        try:
+            first = self.cache[elem][value]
+            # Got hit, so validate
+            if (self.edges[first][0] == elem) and (value in [self.values[self.edges[i][1]]
+                                                             for i in self.outgoing[first]
+                                                             if self.edges[i][1] in self.values]):
+                return first
+            # Hit but invalid now
+            del self.cache[elem][value]
+            return None
+        except KeyError:
+            return None
+
+    def read_dict_node(self, elem: Element, value_node: Node) -> Optional[Element]:
+        e = self.read_dict_node_edge(elem, value_node)
+        if e is None:
+            return None
+        else:
+            self.cache_node.setdefault(elem, {})[value_node] = e
+            return self.edges[e][1]
+
+    def read_dict_node_edge(self, elem: Element, value_node: Node) -> Optional[Edge]:
+        try:
+            first = self.cache_node[elem][value_node]
+            # Got hit, so validate
+            if (self.edges[first][0] == elem) and \
+               (value_node in [self.edges[i][1] for i in self.outgoing[first]]):
+                return first
+            # Hit but invalid now
+            del self.cache_node[elem][value_node]
+            return None
+        except KeyError:
+            return None
+
+    def read_reverse_dict(self, elem: Element, value: Any) -> Optional[List[Element]]:
+        if elem not in self.nodes and elem not in self.edges:
+            return None
+        # Get all outgoing links
+        matches = []
+        if elem in self.incoming:
+            for e1 in self.incoming[elem]:
+                # For each link, we read the links that might link to a data value
+                if e1 in self.outgoing:
+                    for e2 in self.outgoing[e1]:
+                        # Now read out the target of the link
+                        target = self.edges[e2][1]
+                        # And access its value
+                        if target in self.values and self.values[target] == value:
+                            # Found a match
+                            matches.append(e1)
+        return [self.edges[e][0] for e in matches]
+
+    def delete_node(self, node: Node) -> None:
+        if node == self.root:
+            return
+        elif node not in self.nodes:
+            return
+
+        self.nodes.remove(node)
+
+        if node in self.values:
+            del self.values[node]
+
+        s = set()
+        if node in self.outgoing:
+            for e in self.outgoing[node]:
+                s.add(e)
+            del self.outgoing[node]
+        if node in self.incoming:
+            for e in self.incoming[node]:
+                s.add(e)
+            del self.incoming[node]
+
+        for e in s:
+            self.delete_edge(e)
+
+        if node in self.outgoing:
+            del self.outgoing[node]
+        if node in self.incoming:
+            del self.incoming[node]
+
+    def delete_edge(self, edge: Edge) -> None:
+        if edge not in self.edges:
+            return
+
+        s, t = self.edges[edge]
+        if t in self.incoming:
+            self.incoming[t].remove(edge)
+        if s in self.outgoing:
+            self.outgoing[s].remove(edge)
+
+        del self.edges[edge]
+
+        s = set()
+        if edge in self.outgoing:
+            for e in self.outgoing[edge]:
+                s.add(e)
+        if edge in self.incoming:
+            for e in self.incoming[edge]:
+                s.add(e)
+
+        for e in s:
+            self.delete_edge(e)
+
+        if edge in self.outgoing:
+            del self.outgoing[edge]
+        if edge in self.incoming:
+            del self.incoming[edge]
+
+        if self.GC and (t in self.incoming and not self.incoming[t]) and (t not in self.edges):
+            # Remove this node as well
+            # Edges aren't deleted like this, as they might have a reachable target and source!
+            # If they haven't, they will be removed because the source was removed.
+            self.to_delete.add(t)
+
+    def purge(self):
+        while self.to_delete:
+            t = self.to_delete.pop()
+            if t in self.incoming and not self.incoming[t]:
+                self.delete_node(t)
+
+        values = set(self.edges)
+        values.update(self.nodes)
+        visit_list = [self.root]
+
+        while visit_list:
+            elem = visit_list.pop()
+            if elem in values:
+                # Remove it from the leftover values
+                values.remove(elem)
+                if elem in self.edges:
+                    visit_list.extend(self.edges[elem])
+                if elem in self.outgoing:
+                    visit_list.extend(self.outgoing[elem])
+                if elem in self.incoming:
+                    visit_list.extend(self.incoming[elem])
+
+        dset = set()
+        for key in self.cache:
+            if key not in self.nodes and key not in self.edges:
+                dset.add(key)
+        for key in dset:
+            del self.cache[key]
+
+        dset = set()
+        for key in self.cache_node:
+            if key not in self.nodes and key not in self.edges:
+                dset.add(key)
+        for key in dset:
+            del self.cache_node[key]
+
+        # All remaining elements are to be purged
+        if len(values) > 0:
+            while values:
+                v = values.pop()
+                if v in self.nodes:
+                    self.delete_node(v)

+ 276 - 0
state/rdfstate.py

@@ -0,0 +1,276 @@
+from typing import Any, List, Tuple, Optional, Generator
+from rdflib import Graph, Namespace, URIRef, Literal
+from rdflib.plugins.sparql import prepareQuery
+import json
+
+from .base import State
+
+# Define graph datasctructures used by implementation
+# Use NewType to create distinct type or just create a type alias
+Element = URIRef
+Node = URIRef
+Edge = URIRef
+
+
+class RDFState(State):
+    def __init__(self, namespace_uri="http://modelverse.mv/#"):
+        self.graph = Graph()
+        self.namespace_uri = namespace_uri
+        self.mv = Namespace(namespace_uri)
+        self.graph.bind("MV", self.mv)
+        self.prepared_queries = {
+            "read_value": """
+                            SELECT ?value
+                            WHERE {
+                                ?var1 MV:hasValue ?value .
+                            }
+                          """,
+            "read_outgoing": """
+                            SELECT ?link
+                            WHERE {
+                                ?link MV:hasSource ?var1 .
+                            }
+                            """,
+            "read_incoming": """
+                            SELECT ?link
+                            WHERE {
+                                ?link MV:hasTarget ?var1 .
+                            }
+                            """,
+            "read_edge": """
+                            SELECT ?source ?target
+                            WHERE {
+                                ?var1 MV:hasSource ?source ;
+                                      MV:hasTarget ?target .
+                            }
+                            """,
+            "read_dict_keys": """
+                            SELECT ?key
+                            WHERE {
+                                ?main_edge MV:hasSource ?var1 .
+                                ?attr_edge MV:hasSource ?main_edge ;
+                                           MV:hasTarget ?key .
+                            }
+                            """,
+            "read_dict_node": """
+                            SELECT ?value_node
+                            WHERE {
+                                ?main_edge MV:hasSource ?var1 ;
+                                           MV:hasTarget ?value_node .
+                                ?attr_edge MV:hasSource ?main_edge ;
+                                           MV:hasTarget ?var2 .
+                            }
+                            """,
+            "read_dict_node_edge": """
+                            SELECT ?main_edge
+                            WHERE {
+                                ?main_edge MV:hasSource ?var1 .
+                                ?attr_edge MV:hasSource ?main_edge ;
+                                           MV:hasTarget ?var2 .
+                            }
+                            """,
+            "delete_node": """
+                            SELECT ?edge
+                            WHERE {
+                                { ?edge MV:hasTarget ?var1 . }
+                                UNION
+                                { ?edge MV:hasSource ?var1 . }
+                            }
+                            """,
+            "delete_edge": """
+                            SELECT ?edge
+                            WHERE {
+                                { ?edge MV:hasTarget ?var1 . }
+                                UNION
+                                { ?edge MV:hasSource ?var1 . }
+                            }
+                            """,
+        }
+        self.garbage = set()
+
+        for k, v in list(self.prepared_queries.items()):
+            self.prepared_queries[k] = prepareQuery(self.prepared_queries[k], initNs={"MV": self.mv})
+
+        self.root = self.create_node()
+
+    def create_node(self) -> Node:
+        return URIRef(self.namespace_uri + str(self.new_id()))
+
+    def create_edge(self, source: Element, target: Element) -> Optional[Edge]:
+        if not isinstance(source, URIRef):
+            return None
+        elif not isinstance(target, URIRef):
+            return None
+        edge = URIRef(self.namespace_uri + str(self.new_id()))
+        self.graph.add((edge, self.mv.hasSource, source))
+        self.graph.add((edge, self.mv.hasTarget, target))
+        return edge
+
+    def create_nodevalue(self, value: Any) -> Optional[Node]:
+        if not self.is_valid_datavalue(value):
+            return None
+        node = URIRef(self.namespace_uri + str(self.new_id()))
+        if isinstance(value, tuple):
+            value = {"Type": value[0]}
+        self.graph.add((node, self.mv.hasValue, Literal(json.dumps(value))))
+        return node
+
+    def create_dict(self, source: Element, value: Any, target: Element) -> Optional[Tuple[Edge, Edge, Node]]:
+        if not isinstance(source, URIRef):
+            return
+        if not isinstance(target, URIRef):
+            return
+        if not self.is_valid_datavalue(value):
+            return
+
+        n = self.create_nodevalue(value)
+        e = self.create_edge(source, target)
+        self.create_edge(e, n)
+
+    def read_root(self) -> Node:
+        return self.root
+
+    def read_value(self, node: Node) -> Optional[Any]:
+        if not isinstance(node, URIRef) or not (node, None, None) in self.graph:
+            return None
+        result = self.graph.query(self.prepared_queries["read_value"], initBindings={"var1": node})
+        if len(result) == 0:
+            return None
+        result = json.loads(list(result)[0][0])
+        return result if not isinstance(result, dict) else (result["Type"],)
+
+    def read_outgoing(self, elem: Element) -> Optional[List[Edge]]:
+        if not isinstance(elem, URIRef) or elem in self.garbage:
+            return None
+        result = self.graph.query(self.prepared_queries["read_outgoing"], initBindings={"var1": elem})
+        return [i[0] for i in result]
+
+    def read_incoming(self, elem: Element) -> Optional[List[Edge]]:
+        if not isinstance(elem, URIRef) or elem in self.garbage:
+            return None
+        result = self.graph.query(self.prepared_queries["read_incoming"], initBindings={"var1": elem})
+        return [i[0] for i in result]
+
+    def read_edge(self, edge: Edge) -> Tuple[Optional[Node], Optional[Node]]:
+        if not isinstance(edge, URIRef) or not (edge, None, None) in self.graph:
+            return None, None
+        result = self.graph.query(self.prepared_queries["read_edge"], initBindings={"var1": edge})
+        if len(result) == 0:
+            return None, None
+        else:
+            return list(result)[0][0], list(result)[0][1]
+
+    def read_dict(self, elem: Element, value: Any) -> Optional[Element]:
+        if not isinstance(elem, URIRef):
+            return None
+        q = f"""
+            SELECT ?value_node
+            WHERE {{
+                ?main_edge MV:hasSource <{elem}> ;
+                           MV:hasTarget ?value_node .
+                ?attr_edge MV:hasSource ?main_edge ;
+                           MV:hasTarget ?attr_node .
+                ?attr_node MV:hasValue '{json.dumps(value)}' .
+            }}
+            """
+        result = self.graph.query(q)
+        if len(result) == 0:
+            return None
+        return list(result)[0][0]
+
+    def read_dict_keys(self, elem: Element) -> Optional[List[Any]]:
+        if not isinstance(elem, URIRef):
+            return None
+        result = self.graph.query(self.prepared_queries["read_dict_keys"], initBindings={"var1": elem})
+        return [i[0] for i in result]
+
+    def read_dict_edge(self, elem: Element, value: Any) -> Optional[Edge]:
+        if not isinstance(elem, URIRef):
+            return None
+        result = self.graph.query(
+            f"""
+            SELECT ?main_edge
+            WHERE {{
+                ?main_edge MV:hasSource <{elem}> ;
+                           MV:hasTarget ?value_node .
+                ?attr_edge MV:hasSource ?main_edge ;
+                           MV:hasTarget ?attr_node .
+                ?attr_node MV:hasValue '{json.dumps(value)}' .
+            }}
+            """)
+        if len(result) == 0:
+            return None
+        return list(result)[0][0]
+
+    def read_dict_node(self, elem: Element, value_node: Node) -> Optional[Element]:
+        if not isinstance(elem, URIRef):
+            return None
+        if not isinstance(value_node, URIRef):
+            return None
+        result = self.graph.query(
+            self.prepared_queries["read_dict_node"], initBindings={"var1": elem, "var2": value_node}
+        )
+        if len(result) == 0:
+            return None
+        return list(result)[0][0]
+
+    def read_dict_node_edge(self, elem: Element, value_node: Node) -> Optional[Edge]:
+        if not isinstance(elem, URIRef):
+            return None
+        if not isinstance(value_node, URIRef):
+            return None
+        result = self.graph.query(
+            self.prepared_queries["read_dict_node_edge"], initBindings={"var1": elem, "var2": value_node}
+        )
+        if len(result) == 0:
+            return None
+        return list(result)[0][0]
+
+    def read_reverse_dict(self, elem: Element, value: Any) -> Optional[List[Element]]:
+        if not isinstance(elem, URIRef):
+            return None
+        result = self.graph.query(
+            f"""
+            SELECT ?source_node
+            WHERE {{
+                ?main_edge MV:hasTarget <{elem}> ;
+                           MV:hasSource ?source_node .
+                ?attr_edge MV:hasSource ?main_edge ;
+                           MV:hasTarget ?value_node .
+                ?value_node MV:hasValue '{json.dumps(value)}' .
+            }}
+            """)
+
+        return [i[0] for i in result]
+
+    def delete_node(self, node: Node) -> None:
+        if node == self.root:
+            return
+        if not isinstance(node, URIRef):
+            return
+        # Check whether node isn't an edge
+        if (node, self.mv.hasSource, None) in self.graph or (node, self.mv.hasTarget, None) in self.graph:
+            return
+        # Remove its value if it exists
+        self.graph.remove((node, None, None))
+        # Get all edges connecting this
+        result = self.graph.query(self.prepared_queries["delete_node"], initBindings={"var1": node})
+        # ... and remove them
+        for e in result:
+            self.delete_edge(e[0])
+        self.garbage.add(node)
+
+    def delete_edge(self, edge: Edge) -> None:
+        if not isinstance(edge, URIRef):
+            return
+        # Check whether edge is actually an edge
+        if not ((edge, self.mv.hasSource, None) in self.graph and (edge, self.mv.hasTarget, None) in self.graph):
+            return
+        # Remove its links
+        self.graph.remove((edge, None, None))
+        # Get all edges connecting this
+        result = self.graph.query(self.prepared_queries["delete_edge"], initBindings={"var1": edge})
+        # ... and remove them
+        for e in result:
+            self.delete_edge(e[0])
+        self.garbage.add(edge)

+ 0 - 0
state/test/__init__.py


+ 2 - 0
state/test/conftest.py

@@ -0,0 +1,2 @@
+import pytest
+from .fixtures.state import state

+ 0 - 0
state/test/fixtures/__init__.py


+ 19 - 0
state/test/fixtures/state.py

@@ -0,0 +1,19 @@
+import pytest
+from state.pystate import PyState
+from state.rdfstate import RDFState
+from state.neo4jstate import Neo4jState
+
+
+@pytest.fixture(params=[
+    (PyState,),
+    (RDFState, "http://example.org/#"),
+    (Neo4jState,)
+])
+def state(request):
+    if len(request.param) > 1:
+        state = request.param[0](*request.param[1:])
+    else:
+        state = request.param[0]()
+    yield state
+    if isinstance(state, Neo4jState):
+        state.close(clear=True)

+ 41 - 0
state/test/test_create_dict.py

@@ -0,0 +1,41 @@
+import pytest
+
+
+@pytest.mark.usefixtures("state")
+def test_create_dict_simple(state):
+    id1 = state.create_node()
+    id2 = state.create_node()
+    assert id1 is not None
+    assert id2 is not None
+
+    n = state.create_dict(id1, "abc", id2)
+    assert n is None
+
+    v = state.read_dict(id1, "abc")
+    assert v == id2
+
+
+@pytest.mark.usefixtures("state")
+def test_create_dict_no_source(state):
+    id1 = 100000
+    id2 = state.create_node()
+    assert id2 is not None
+
+    n = state.create_dict(id1, "abc", id2)
+    assert n is None
+
+    v = state.read_dict(id1, "abc")
+    assert v is None
+
+
+@pytest.mark.usefixtures("state")
+def test_create_dict_no_target(state):
+    id2 = 100000
+    id1 = state.create_node()
+    assert id1 is not None
+
+    n = state.create_dict(id1, "abc", id2)
+    assert n is None
+
+    v = state.read_dict(id1, "abc")
+    assert v is None

+ 144 - 0
state/test/test_create_edge.py

@@ -0,0 +1,144 @@
+import pytest
+
+
+@pytest.mark.usefixtures("state")
+def test_create_edge_invalid_source(state):
+    a = -1
+    b = state.create_node()
+    assert b is not None
+
+    e = state.create_edge(a, b)
+    assert e is None
+
+
+@pytest.mark.usefixtures("state")
+def test_create_edge_invalid_target(state):
+    b = -1
+    a = state.create_node()
+    assert a is not None
+
+    e = state.create_edge(a, b)
+    assert e is None
+
+
+@pytest.mark.usefixtures("state")
+def test_create_edge_invalid_both(state):
+    a = -1
+    b = -1
+    e = state.create_edge(a, b)
+    assert e is None
+
+
+@pytest.mark.usefixtures("state")
+def test_create_edge_node_to_node(state):
+    a = state.create_node()
+    assert a is not None
+    b = state.create_node()
+    assert b is not None
+
+    edge = state.create_edge(a, b)
+    assert edge is not None
+
+
+@pytest.mark.usefixtures("state")
+def test_create_edge_multiple(state):
+    a = state.create_node()
+    assert a is not None
+    b = state.create_node()
+    assert b is not None
+
+    edge1 = state.create_edge(a, b)
+    assert edge1 is not None
+
+    edge2 = state.create_edge(a, b)
+    assert edge2 is not None
+
+    assert edge1 != edge2
+
+
+@pytest.mark.usefixtures("state")
+def test_create_edge_many(state):
+    v = set()
+    for i in range(1000):
+        a = state.create_node()
+        assert a is not None
+        b = state.create_node()
+        assert b is not None
+
+        edge = state.create_edge(a, b)
+        assert edge is not None
+
+        v.add(edge)
+    assert len(v) == 1000
+
+
+@pytest.mark.usefixtures("state")
+def test_create_edge_edge_to_node(state):
+    a = state.create_node()
+    assert a is not None
+    b = state.create_node()
+    assert b is not None
+
+    edge1 = state.create_edge(a, b)
+    assert edge1 is not None
+
+    edge2 = state.create_edge(edge1, b)
+    assert edge2 is not None
+
+    assert edge1 != edge2
+
+
+@pytest.mark.usefixtures("state")
+def test_create_edge_node_to_edge(state):
+    a = state.create_node()
+    assert a is not None
+    b = state.create_node()
+    assert b is not None
+
+    edge1 = state.create_edge(a, b)
+    assert edge1 is not None
+
+    edge2 = state.create_edge(a, edge1)
+    assert edge2 is not None
+
+    assert edge1 != edge2
+
+
+@pytest.mark.usefixtures("state")
+def test_create_edge_edge_to_edge(state):
+    a = state.create_node()
+    assert a is not None
+    b = state.create_node()
+    assert b is not None
+
+    edge1 = state.create_edge(a, b)
+    assert edge1 is not None
+
+    edge2 = state.create_edge(a, b)
+    assert edge2 is not None
+
+    assert edge1 != edge2
+
+    edge3 = state.create_edge(edge1, edge2)
+    assert edge3 is not None
+
+
+@pytest.mark.usefixtures("state")
+def test_create_edge_loop_node(state):
+    a = state.create_node()
+    assert a is not None
+
+    edge = state.create_edge(a, a)
+    assert edge is not None
+
+
+@pytest.mark.usefixtures("state")
+def test_create_edge_loop_edge(state):
+    a = state.create_node()
+    assert a is not None
+
+    edge1 = state.create_edge(a, a)
+    assert edge1 is not None
+
+    edge2 = state.create_edge(edge1, edge1)
+    assert edge2 is not None

+ 22 - 0
state/test/test_create_node.py

@@ -0,0 +1,22 @@
+import pytest
+
+
+@pytest.mark.usefixtures("state")
+def test_create_node_different_id_simple(state):
+    id1 = state.create_node()
+    assert id1 is not None
+    id2 = state.create_node()
+    assert id2 is not None
+
+    assert id1 != id2
+
+
+@pytest.mark.usefixtures("state")
+def test_create_node_different_id_long(state):
+    results = set()
+    for i in range(1000):
+        v = state.create_node()
+        assert v is not None
+        results.add(v)
+
+    assert len(results) == 1000

+ 173 - 0
state/test/test_create_nodevalue.py

@@ -0,0 +1,173 @@
+import pytest
+
+
+@pytest.mark.usefixtures("state")
+def test_create_nodevalue_different_id_simple(state):
+    id1 = state.create_nodevalue(1)
+    id2 = state.create_nodevalue(1)
+
+    assert id1 is not None
+    assert id2 is not None
+    assert id1 != id2
+
+
+@pytest.mark.usefixtures("state")
+def test_create_nodevalue_read(state):
+    id1 = state.create_nodevalue(1)
+    assert id1 is not None
+    val = state.read_value(id1)
+    assert val == 1
+
+
+@pytest.mark.usefixtures("state")
+def test_create_nodevalue_integer_ib_zero(state):
+    # Nicely within range
+    v = set()
+    size = 0
+    for i in range(-10, 10):
+        id1 = state.create_nodevalue(i)
+        assert id1 is not None
+        size += 1
+        v.add(id1)
+    assert len(v) == size
+
+
+@pytest.mark.usefixtures("state")
+def test_create_nodevalue_boolean(state):
+    id1 = state.create_nodevalue(True)
+    id2 = state.create_nodevalue(False)
+
+    assert id1 is not None
+    assert id2 is not None
+    assert id1 != id2
+
+
+@pytest.mark.usefixtures("state")
+def test_create_nodevalue_boolean_same(state):
+    id1 = state.create_nodevalue(True)
+    id2 = state.create_nodevalue(True)
+
+    assert id1 is not None
+    assert id2 is not None
+    assert id1 != id2
+
+
+@pytest.mark.usefixtures("state")
+def test_create_nodevalue_float_keeps_type(state):
+    id1 = state.create_nodevalue(0.0)
+    assert id1 is not None
+
+    v = state.read_value(id1)
+    assert type(v) == float
+    assert v == 0.0
+
+
+@pytest.mark.usefixtures("state")
+def test_create_nodevalue_string_empty(state):
+    id1 = state.create_nodevalue("")
+    assert id1 is not None
+
+    v = state.read_value(id1)
+    assert type(v) == str
+    assert v == ""
+
+
+@pytest.mark.usefixtures("state")
+def test_create_nodevalue_string_normal(state):
+    id1 = state.create_nodevalue("ABC")
+    assert id1 is not None
+
+    v = state.read_value(id1)
+    assert type(v) == str
+    assert v == "ABC"
+
+
+@pytest.mark.usefixtures("state")
+def test_create_nodevalue_string_not_parsed(state):
+    id1 = state.create_nodevalue("1")
+    assert id1 is not None
+
+    v = state.read_value(id1)
+    assert type(v) == str
+    assert v == "1"
+
+    id1 = state.create_nodevalue("1.0")
+    assert id1 is not None
+
+    v = state.read_value(id1)
+    assert type(v) == str
+    assert v == "1.0"
+
+    id1 = state.create_nodevalue("-1.0")
+    assert id1 is not None
+
+    v = state.read_value(id1)
+    assert type(v) == str
+    assert v == "-1.0"
+
+    id1 = state.create_nodevalue("True")
+    assert id1 is not None
+
+    v = state.read_value(id1)
+    assert type(v) == str
+    assert v == "True"
+
+
+@pytest.mark.usefixtures("state")
+def test_create_nodevalue_junk(state):
+    class Unknown(object):
+        pass
+
+    n = state.create_nodevalue(Unknown())
+    assert n is None
+
+
+@pytest.mark.usefixtures("state")
+def test_create_nodevalue_type_type(state):
+    id1 = state.create_nodevalue(("Type",))
+    assert id1 is not None
+
+    v = state.read_value(id1)
+    assert v == ("Type",)
+
+
+@pytest.mark.usefixtures("state")
+def test_create_nodevalue_integer_type(state):
+    id1 = state.create_nodevalue(("Integer",))
+    assert id1 is not None
+
+    v = state.read_value(id1)
+    assert v == ("Integer",)
+
+
+@pytest.mark.usefixtures("state")
+def test_create_nodevalue_float_type(state):
+    id1 = state.create_nodevalue(("Float",))
+    assert id1 is not None
+
+    v = state.read_value(id1)
+    assert v == ("Float",)
+
+
+@pytest.mark.usefixtures("state")
+def test_create_nodevalue_boolean_type(state):
+    id1 = state.create_nodevalue(("Boolean",))
+    assert id1 is not None
+
+    v = state.read_value(id1)
+    assert v == ("Boolean",)
+
+
+@pytest.mark.usefixtures("state")
+def test_create_nodevalue_string_type(state):
+    id1 = state.create_nodevalue(("String",))
+    assert id1 is not None
+
+    v = state.read_value(id1)
+    assert v == ("String",)
+
+
+@pytest.mark.usefixtures("state")
+def test_create_nodevalue_invalid_type(state):
+    id1 = state.create_nodevalue(("Class",))
+    assert id1 is None

+ 396 - 0
state/test/test_delete_edge.py

@@ -0,0 +1,396 @@
+import pytest
+
+
+@pytest.mark.usefixtures("state")
+def test_delete_edge_no_exists(state):
+    e = state.delete_edge(1)
+    assert e is None
+
+
+@pytest.mark.usefixtures("state")
+def test_delete_edge_node(state):
+    a = state.create_node()
+    assert a is not None
+
+    e = state.delete_edge(a)
+    assert e is None
+
+
+@pytest.mark.usefixtures("state")
+def test_delete_edge_nodevalue(state):
+    a = state.create_nodevalue(1)
+    assert a is not None
+
+    e = state.delete_edge(a)
+    assert e is None
+
+
+@pytest.mark.usefixtures("state")
+def test_delete_edge_normal(state):
+    a = state.create_nodevalue(1)
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    n = state.delete_edge(c)
+    assert n is None
+
+    l = state.read_outgoing(a)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(a)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_outgoing(b)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(b)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_delete_edge_remove_recursive(state):
+    a = state.create_nodevalue(1)
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    d = state.create_edge(c, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+
+    n = state.delete_edge(c)
+    assert n is None
+
+    l = state.read_value(a)
+    assert l == 1
+
+    l = state.read_outgoing(a)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(a)
+    assert l is not None
+    assert set(l) == set([])
+
+    s, t = state.read_edge(c)
+    assert s is None
+    assert t is None
+
+    s, t = state.read_edge(d)
+    assert s is None
+    assert t is None
+
+    l = state.read_outgoing(b)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_delete_edge_remove_edge_recursive_deep(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_node()
+    d = state.create_edge(a, b)
+    e = state.create_edge(d, c)
+    f = state.create_node()
+    g = state.create_edge(f, e)
+    h = state.create_edge(b, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+    assert f is not None
+    assert g is not None
+    assert h is not None
+
+    n = state.delete_edge(d)
+    assert n is None
+
+    l = state.read_outgoing(a)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(a)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_outgoing(b)
+    assert l is not None
+    assert set(l) == set([h])
+
+    l = state.read_incoming(b)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_outgoing(c)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(c)
+    assert l is not None
+    assert set(l) == set([h])
+
+    s, t = state.read_edge(d)
+    assert s is None
+    assert t is None
+
+    s, t = state.read_edge(e)
+    assert s is None
+    assert t is None
+
+    s, t = state.read_edge(g)
+    assert s is None
+    assert t is None
+
+    l = state.read_outgoing(f)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(f)
+    assert l is not None
+    assert set(l) == set([])
+
+    s, t = state.read_edge(h)
+    assert s == b
+    assert t == c
+
+
+@pytest.mark.usefixtures("state")
+def test_delete_edge_remove_edge_recursive_steps(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_node()
+    d = state.create_edge(a, b)
+    e = state.create_edge(d, c)
+    f = state.create_node()
+    g = state.create_edge(f, e)
+    h = state.create_edge(b, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+    assert f is not None
+    assert g is not None
+    assert h is not None
+
+    n = state.delete_edge(g)
+    assert n is None
+
+    l = state.read_outgoing(a)
+    assert l is not None
+    assert set(l) == set([d])
+
+    l = state.read_incoming(a)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_outgoing(b)
+    assert l is not None
+    assert set(l) == set([h])
+
+    l = state.read_incoming(b)
+    assert l is not None
+    assert set(l) == set([d])
+
+    l = state.read_outgoing(c)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(c)
+    assert l is not None
+    assert set(l) == set([h, e])
+
+    s, t = state.read_edge(d)
+    assert s == a
+    assert t == b
+
+    l = state.read_outgoing(d)
+    assert l is not None
+    assert set(l) == set([e])
+
+    l = state.read_incoming(d)
+    assert l is not None
+    assert set(l) == set([])
+
+    s, t = state.read_edge(e)
+    assert s == d
+    assert t == c
+
+    l = state.read_outgoing(e)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(e)
+    assert l is not None
+    assert set(l) == set([])
+
+    s, t = state.read_edge(g)
+    assert s is None
+    assert t is None
+
+    l = state.read_outgoing(g)
+    assert l is None
+
+    l = state.read_incoming(g)
+    assert l is None
+
+    l = state.read_outgoing(f)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(f)
+    assert l is not None
+    assert set(l) == set([])
+
+    s, t = state.read_edge(h)
+    assert s == b
+    assert t == c
+
+    n = state.delete_edge(e)
+    assert n is None
+
+    l = state.read_outgoing(a)
+    assert l is not None
+    assert set(l) == set([d])
+
+    l = state.read_incoming(a)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_outgoing(b)
+    assert l is not None
+    assert set(l) == set([h])
+
+    l = state.read_incoming(b)
+    assert l is not None
+    assert set(l) == set([d])
+
+    l = state.read_outgoing(c)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(c)
+    assert l is not None
+    assert set(l) == set([h])
+
+    s, t = state.read_edge(d)
+    assert s == a
+    assert t == b
+
+    l = state.read_outgoing(d)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(d)
+    assert l is not None
+    assert set(l) == set([])
+
+    s, t = state.read_edge(e)
+    assert s is None
+    assert t is None
+
+    l = state.read_outgoing(e)
+    assert l is None
+
+    l = state.read_incoming(e)
+    assert l is None
+
+    s, t = state.read_edge(g)
+    assert s is None
+    assert t is None
+
+    l = state.read_outgoing(g)
+    assert l is None
+
+    l = state.read_incoming(g)
+    assert l is None
+
+    l = state.read_outgoing(f)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(f)
+    assert l is not None
+    assert set(l) == set([])
+
+    s, t = state.read_edge(h)
+    assert s == b
+    assert t == c
+
+    n = state.delete_edge(d)
+    assert n is None
+
+    l = state.read_outgoing(a)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(a)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_outgoing(b)
+    assert l is not None
+    assert set(l) == set([h])
+
+    l = state.read_incoming(b)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_outgoing(c)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(c)
+    assert l is not None
+    assert set(l) == set([h])
+
+    s, t = state.read_edge(d)
+    assert s is None
+    assert t is None
+
+    l = state.read_outgoing(d)
+    assert l is None
+
+    l = state.read_incoming(d)
+    assert l is None
+
+    s, t = state.read_edge(e)
+    assert s is None
+    assert t is None
+
+    l = state.read_outgoing(e)
+    assert l is None
+
+    l = state.read_incoming(e)
+    assert l is None
+
+    s, t = state.read_edge(g)
+    assert s is None
+    assert t is None
+
+    l = state.read_outgoing(g)
+    assert l == None
+
+    l = state.read_incoming(g)
+    assert l == None
+
+    l = state.read_outgoing(f)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(f)
+    assert l is not None
+    assert set(l) == set([])
+
+    s, t = state.read_edge(h)
+    assert s == b
+    assert t == c

+ 227 - 0
state/test/test_delete_node.py

@@ -0,0 +1,227 @@
+import pytest
+
+
+@pytest.mark.usefixtures("state")
+def test_delete_node_no_exists(state):
+    n = state.delete_node(-1)
+    assert n is None
+
+
+@pytest.mark.usefixtures("state")
+def test_delete_node_no_value(state):
+    a = state.create_node()
+    assert a is not None
+
+    n = state.delete_node(a)
+    assert n is None
+
+
+@pytest.mark.usefixtures("state")
+def test_delete_node_value(state):
+    a = state.create_nodevalue(1)
+    assert a is not None
+
+    d = state.read_value(a)
+    assert d == 1
+
+    n = state.delete_node(a)
+    assert n is None
+
+    d = state.read_value(a)
+    assert d is None
+
+
+@pytest.mark.usefixtures("state")
+def test_delete_node_edge(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    n = state.delete_node(c)
+    assert n is None
+
+
+@pytest.mark.usefixtures("state")
+def test_delete_node_remove_edge_outgoing(state):
+    a = state.create_nodevalue(1)
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    n = state.delete_node(a)
+    assert n is None
+
+    d = state.read_value(a)
+    assert d is None
+
+    s, t = state.read_edge(c)
+    assert s is None
+    assert t is None
+
+    d = state.read_outgoing(b)
+    assert d is not None
+    assert set(d) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_delete_node_remove_edge_incoming(state):
+    a = state.create_nodevalue(1)
+    b = state.create_node()
+    c = state.create_edge(b, a)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    n = state.delete_node(a)
+    assert n is None
+
+    d = state.read_value(a)
+    assert d is None
+
+    s, t = state.read_edge(c)
+    assert s is None
+    assert t is None
+
+    d = state.read_outgoing(b)
+    assert d is not None
+    assert set(d) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_delete_node_remove_edge_both(state):
+    a = state.create_nodevalue(1)
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    e = state.create_node()
+    f = state.create_edge(e, a)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert e is not None
+    assert f is not None
+
+    n = state.delete_node(a)
+    assert n is None
+
+    d = state.read_value(a)
+    assert d is None
+
+    s, t = state.read_edge(c)
+    assert s is None
+    assert t is None
+
+    d = state.read_incoming(b)
+    assert d is not None
+    assert set(d) == set([])
+
+    s, t = state.read_edge(f)
+    assert s is None
+    assert t is None
+
+    d = state.read_outgoing(e)
+    assert d is not None
+    assert set(d) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_delete_node_remove_edge_recursive(state):
+    a = state.create_nodevalue(1)
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    d = state.create_edge(c, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+
+    n = state.delete_node(a)
+    assert n is None
+
+    d = state.read_value(a)
+    assert d is None
+
+    s, t = state.read_edge(c)
+    assert s is None
+    assert t is None
+
+    s, t = state.read_edge(d)
+    assert s is None
+    assert t is None
+
+    d = state.read_outgoing(b)
+    assert d is not None
+    assert set(d) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_delete_node_remove_edge_recursive_deep(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_node()
+    d = state.create_edge(a, b)
+    e = state.create_edge(d, c)
+    f = state.create_node()
+    g = state.create_edge(f, e)
+    h = state.create_edge(b, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+    assert f is not None
+    assert g is not None
+    assert h is not None
+
+    n = state.delete_node(a)
+    assert n is None
+
+    l = state.read_outgoing(a)
+    assert l is None
+
+    l = state.read_incoming(a)
+    assert l is None
+
+    l = state.read_outgoing(b)
+    assert l is not None
+    assert set(l) == set([h])
+
+    l = state.read_incoming(b)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_outgoing(c)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(c)
+    assert l is not None
+    assert set(l) == set([h])
+
+    s, t = state.read_edge(d)
+    assert s is None
+    assert t is None
+
+    s, t = state.read_edge(e)
+    assert s is None
+    assert t is None
+
+    s, t = state.read_edge(g)
+    assert s is None
+    assert t is None
+
+    l = state.read_outgoing(f)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(f)
+    assert l is not None
+    assert set(l) == set([])
+
+    s, t = state.read_edge(h)
+    assert s == b
+    assert t == c

+ 94 - 0
state/test/test_read_dict.py

@@ -0,0 +1,94 @@
+import pytest
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_no_exists(state):
+    assert state.read_dict(-1, "abc") is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_not_found_node(state):
+    a = state.create_node()
+    assert a is not None
+
+    # Passing data is not enforced, as the data will be interpreted if necessary
+    assert state.read_dict(a, "abc") is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_not_found_nodevalue(state):
+    a = state.create_nodevalue(1)
+    assert a is not None
+
+    # Passing data is not enforced, as the data will be interpreted if necessary
+    assert state.read_dict(a, "abc") is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_not_found_edge(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    # Passing data is not enforced, as the data will be interpreted if necessary
+    assert state.read_dict(c, "abc") is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_no_primitive(state):
+    a = state.create_node()
+    assert a is not None
+
+    # Passing data is not enforced, as the data will be interpreted if necessary
+    assert state.read_dict(a, a) is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_node_simple(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_nodevalue("f")
+    d = state.create_edge(a, b)
+    e = state.create_edge(d, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    l = state.read_dict(a, "f")
+    assert l == b
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_node_multi(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_nodevalue("f")
+    d = state.create_edge(a, b)
+    e = state.create_edge(d, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    g = state.create_node()
+    h = state.create_nodevalue("k")
+    i = state.create_edge(a, g)
+    j = state.create_edge(i, h)
+    assert g is not None
+    assert h is not None
+    assert i is not None
+    assert j is not None
+
+    l = state.read_dict(a, "f")
+    assert l == b
+
+    l = state.read_dict(a, "k")
+    assert l == g
+
+    assert state.read_dict(a, "l") is None

+ 94 - 0
state/test/test_read_dict_edge.py

@@ -0,0 +1,94 @@
+import pytest
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_edge_no_exists(state):
+    assert state.read_dict_edge(-1, "abc") is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_edge_not_found_node(state):
+    a = state.create_node()
+    assert a is not None
+
+    # Passing data is not enforced, as the data will be interpreted if necessary
+    assert state.read_dict_edge(a, "abc") is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_edge_not_found_nodevalue(state):
+    a = state.create_nodevalue(1)
+    assert a is not None
+
+    # Passing data is not enforced, as the data will be interpreted if necessary
+    assert state.read_dict_edge(a, "abc") is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_edge_not_found_edge(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    # Passing data is not enforced, as the data will be interpreted if necessary
+    assert state.read_dict_edge(c, "abc") is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_edge_no_primitive(state):
+    a = state.create_node()
+    assert a is not None
+
+    # Passing data is not enforced, as the data will be interpreted if necessary
+    assert state.read_dict_edge(a, a) is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_edge_node_simple(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_nodevalue("f")
+    d = state.create_edge(a, b)
+    e = state.create_edge(d, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    l = state.read_dict_edge(a, "f")
+    assert l == d
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_edge_node_multi(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_nodevalue("f")
+    d = state.create_edge(a, b)
+    e = state.create_edge(d, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    g = state.create_node()
+    h = state.create_nodevalue("k")
+    i = state.create_edge(a, g)
+    j = state.create_edge(i, h)
+    assert g is not None
+    assert h is not None
+    assert i is not None
+    assert j is not None
+
+    l = state.read_dict_edge(a, "f")
+    assert l == d
+
+    l = state.read_dict_edge(a, "k")
+    assert l == i
+
+    assert state.read_dict_edge(a, "l") is None

+ 51 - 0
state/test/test_read_dict_keys.py

@@ -0,0 +1,51 @@
+import pytest
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_keys_no_exists(state):
+    assert state.read_dict_keys(100000) is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_keys_simple(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_nodevalue("f")
+    d = state.create_edge(a, b)
+    e = state.create_edge(d, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    l = state.read_dict_keys(a)
+    assert l is not None
+    assert set(l) == set([c])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_keys_multi(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_nodevalue("f")
+    d = state.create_edge(a, b)
+    e = state.create_edge(d, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    g = state.create_node()
+    h = state.create_nodevalue("k")
+    i = state.create_edge(a, g)
+    j = state.create_edge(i, h)
+    assert g is not None
+    assert h is not None
+    assert i is not None
+    assert j is not None
+
+    l = state.read_dict_keys(a)
+    assert l is not None
+    assert set(l) == set([c, h])

+ 74 - 0
state/test/test_read_dict_node.py

@@ -0,0 +1,74 @@
+import pytest
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_node_no_exists(state):
+    assert state.read_dict_node(-1, "abc") is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_node_not_found_edge(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    # Passing data is not enforced, as the data will be interpreted if necessary
+    assert state.read_dict_node(c, "abc") is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_node_no_primitive(state):
+    a = state.create_node()
+    assert a is not None
+
+    # Passing data is not enforced, as the data will be interpreted if necessary
+    assert state.read_dict_node(a, a) is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_node_node_simple(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_node()
+    d = state.create_edge(a, b)
+    e = state.create_edge(d, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    l = state.read_dict_node(a, c)
+    assert l == b
+
+
+@pytest.mark.usefixtures("state")
+def test_read_dict_node_multi(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_node()
+    d = state.create_edge(a, b)
+    e = state.create_edge(d, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    g = state.create_node()
+    h = state.create_node()
+    i = state.create_edge(a, g)
+    j = state.create_edge(i, h)
+    assert g is not None
+    assert h is not None
+    assert i is not None
+    assert j is not None
+
+    l = state.read_dict_node(a, c)
+    assert l == b
+
+    l = state.read_dict_node(a, h)
+    assert l == g

+ 136 - 0
state/test/test_read_edge.py

@@ -0,0 +1,136 @@
+import pytest
+
+
+@pytest.mark.usefixtures("state")
+def test_read_edge_node(state):
+    b = state.create_node()
+    assert b is not None
+
+    s, t = state.read_edge(b)
+    assert s is None
+    assert t is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_edge_no_exists(state):
+    s, t = state.read_edge(-1)
+    assert s is None
+    assert t is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_edge_nodevalue(state):
+    b = state.create_nodevalue(1)
+    assert b is not None
+
+    s, t = state.read_edge(b)
+    assert s is None
+    assert t is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_edge_normal(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    s, t = state.read_edge(c)
+    assert s == a
+    assert t == b
+
+
+@pytest.mark.usefixtures("state")
+def test_read_edge_edge_to_edge(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    d = state.create_edge(a, b)
+    e = state.create_edge(c, d)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    s, t = state.read_edge(c)
+    assert s == a
+    assert t == b
+
+    s, t = state.read_edge(d)
+    assert s == a
+    assert t == b
+
+    s, t = state.read_edge(e)
+    assert s == c
+    assert t == d
+
+
+@pytest.mark.usefixtures("state")
+def test_read_edge_edge_to_node(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    d = state.create_edge(c, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+
+    s, t = state.read_edge(c)
+    assert s == a
+    assert t == b
+
+    s, t = state.read_edge(d)
+    assert s == c
+    assert t == b
+
+
+@pytest.mark.usefixtures("state")
+def test_read_edge_node_to_edge(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    d = state.create_edge(b, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+
+    s, t = state.read_edge(c)
+    assert s == a
+    assert t == b
+
+    s, t = state.read_edge(d)
+    assert s == b
+    assert t == c
+
+
+@pytest.mark.usefixtures("state")
+def test_read_edge_node_to_nodevalue(state):
+    a = state.create_node()
+    b = state.create_nodevalue(1)
+    c = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    s, t = state.read_edge(c)
+    assert s == a
+    assert t == b
+
+
+@pytest.mark.usefixtures("state")
+def test_read_edge_nodevalue_to_nodevalue(state):
+    a = state.create_nodevalue(1)
+    b = state.create_nodevalue(1)
+    c = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    s, t = state.read_edge(c)
+    assert s == a
+    assert t == b

+ 275 - 0
state/test/test_read_incoming.py

@@ -0,0 +1,275 @@
+import pytest
+
+
+@pytest.mark.usefixtures("state")
+def test_read_incoming_node_none(state):
+    b = state.create_node()
+    assert b is not None
+
+    l = state.read_incoming(b)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_incoming_node_one(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    l = state.read_incoming(a)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(b)
+    assert l is not None
+    assert set(l) == set([c])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_incoming_node_multi(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    d = state.create_edge(a, b)
+    e = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    l = state.read_incoming(a)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(b)
+    assert l is not None
+    assert set(l) == set([c, d, e])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_incoming_node_multi_others_unaffected(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    d = state.create_edge(a, b)
+    e = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    f = state.create_node()
+    assert f is not None
+
+    l = state.read_incoming(a)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(b)
+    assert l is not None
+    assert set(l) == set([c, d, e])
+
+    l = state.read_incoming(f)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_incoming_edge_none(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    l = state.read_incoming(c)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_incoming_edge_one(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    d = state.create_edge(c, a)
+    e = state.create_edge(a, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    l = state.read_incoming(c)
+    assert l is not None
+    assert set(l) == set([e])
+
+    l = state.read_incoming(d)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(e)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_incoming_edge_multi(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    d = state.create_edge(a, c)
+    e = state.create_edge(b, c)
+    f = state.create_edge(d, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+    assert f is not None
+
+    l = state.read_incoming(b)
+    assert l is not None
+    assert set(l) == set([c])
+
+    l = state.read_incoming(c)
+    assert l is not None
+    assert set(l) == set([d, e, f])
+
+    l = state.read_incoming(d)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(e)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(f)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_incoming_nodevalue_none(state):
+    b = state.create_nodevalue(1)
+    assert b is not None
+
+    l = state.read_incoming(b)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_incoming_nodevalue_one(state):
+    a = state.create_nodevalue(1)
+    b = state.create_node()
+    c = state.create_edge(b, a)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    l = state.read_incoming(a)
+    assert l is not None
+    assert set(l) == set([c])
+
+    l = state.read_incoming(b)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_incoming_nodevalue_multi(state):
+    a = state.create_nodevalue(1)
+    b = state.create_node()
+    c = state.create_edge(b, a)
+    d = state.create_edge(b, a)
+    e = state.create_edge(b, a)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    l = state.read_incoming(a)
+    assert l is not None
+    assert set(l) == set([c, d, e])
+
+    l = state.read_incoming(b)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_incoming_nodevalue_multi_others_unaffected(state):
+    a = state.create_nodevalue(1)
+    b = state.create_node()
+    c = state.create_edge(b, a)
+    d = state.create_edge(b, a)
+    e = state.create_edge(b, a)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    f = state.create_nodevalue(1)
+    assert f is not None
+
+    l = state.read_incoming(a)
+    assert l is not None
+    assert set(l) == set([c, d, e])
+
+    l = state.read_incoming(b)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_incoming(f)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_incoming_node_deleted(state):
+    b = state.create_node()
+    assert b is not None
+
+    n = state.delete_node(b)
+    assert n is None
+
+    l = state.read_incoming(b)
+    assert l is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_incoming_nodevalue_deleted(state):
+    b = state.create_nodevalue(1)
+    assert b is not None
+
+    n = state.delete_node(b)
+    assert n is None
+
+    l = state.read_incoming(b)
+    assert l is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_incoming_edge_deleted(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    n = state.delete_edge(c)
+    assert n is None
+
+    l = state.read_incoming(c)
+    assert l is None

+ 265 - 0
state/test/test_read_outgoing.py

@@ -0,0 +1,265 @@
+import pytest
+
+
+@pytest.mark.usefixtures("state")
+def test_read_outgoing_node_none(state):
+    b = state.create_node()
+    assert b is not None
+
+    l = state.read_outgoing(b)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_outgoing_node_one(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    l = state.read_outgoing(a)
+    assert l is not None
+    assert set(l) == set([c])
+
+    l = state.read_outgoing(b)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_outgoing_node_multi(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    d = state.create_edge(a, b)
+    e = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    l = state.read_outgoing(a)
+    assert l is not None
+    assert set(l) == set([c, d, e])
+
+    l = state.read_outgoing(b)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_outgoing_node_multi_others_unaffected(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    d = state.create_edge(a, b)
+    e = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    f = state.create_node()
+    assert f is not None
+
+    l = state.read_outgoing(a)
+    assert l is not None
+    assert set(l) == set([c, d, e])
+
+    l = state.read_outgoing(b)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_outgoing(f)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_outgoing_edge_none(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    l = state.read_outgoing(c)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_outgoing_edge_one(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    d = state.create_edge(c, a)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+
+    l = state.read_outgoing(c)
+    assert l is not None
+    assert set(l) == set([d])
+
+    l = state.read_outgoing(d)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_outgoing_edge_multi(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    d = state.create_edge(c, a)
+    e = state.create_edge(c, b)
+    f = state.create_edge(c, d)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+    assert f is not None
+
+    l = state.read_outgoing(c)
+    assert l is not None
+    assert set(l) == set([d, e, f])
+
+    l = state.read_outgoing(d)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_outgoing(e)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_outgoing(f)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_outgoing_nodevalue_none(state):
+    b = state.create_nodevalue(1)
+    assert b is not None
+
+    l = state.read_outgoing(b)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_outgoing_nodevalue_one(state):
+    a = state.create_nodevalue(1)
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    l = state.read_outgoing(a)
+    assert l is not None
+    assert set(l) == set([c])
+
+    l = state.read_outgoing(b)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_outgoing_nodevalue_multi(state):
+    a = state.create_nodevalue(1)
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    d = state.create_edge(a, b)
+    e = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    l = state.read_outgoing(a)
+    assert l is not None
+    assert set(l) == set([c, d, e])
+
+    l = state.read_outgoing(b)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_outgoing_nodevalue_multi_others_unaffected(state):
+    a = state.create_nodevalue(1)
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    d = state.create_edge(a, b)
+    e = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    f = state.create_nodevalue(1)
+    assert f is not None
+
+    l = state.read_outgoing(a)
+    assert l is not None
+    assert set(l) == set([c, d, e])
+
+    l = state.read_outgoing(b)
+    assert l is not None
+    assert set(l) == set([])
+
+    l = state.read_outgoing(f)
+    assert l is not None
+    assert set(l) == set([])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_outgoing_node_deleted(state):
+    b = state.create_node()
+    assert b is not None
+
+    n = state.delete_node(b)
+    assert n is None
+
+    l = state.read_outgoing(b)
+    assert l is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_outgoing_nodevalue_deleted(state):
+    b = state.create_nodevalue(1)
+    assert b is not None
+
+    n = state.delete_node(b)
+    assert n is None
+
+    l = state.read_outgoing(b)
+    assert l is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_outgoing_edge_deleted(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    n = state.delete_edge(c)
+    assert n is None
+
+    l = state.read_outgoing(c)
+    assert l is None

+ 165 - 0
state/test/test_read_reverse_dict.py

@@ -0,0 +1,165 @@
+import pytest
+
+
+@pytest.mark.usefixtures("state")
+def test_read_reverse_dict_no_exists(state):
+    l = state.read_reverse_dict(-1, "abc")
+    assert l is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_reverse_dict_not_found_node(state):
+    a = state.create_node()
+    assert a is not None
+
+    # Passing data is not enforced, as the data will be interpreted if necessary
+    l = state.read_reverse_dict(a, "abc")
+    assert l == []
+
+
+@pytest.mark.usefixtures("state")
+def test_read_reverse_dict_not_found_nodevalue(state):
+    a = state.create_nodevalue(1)
+    assert a is not None
+
+    # Passing data is not enforced, as the data will be interpreted if necessary
+    l = state.read_reverse_dict(a, "abc")
+    assert l == []
+
+
+@pytest.mark.usefixtures("state")
+def test_read_reverse_dict_not_found_edge(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_edge(a, b)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+
+    # Passing data is not enforced, as the data will be interpreted if necessary
+    l = state.read_reverse_dict(c, "abc")
+    assert l == []
+
+
+@pytest.mark.usefixtures("state")
+def test_read_reverse_dict_no_primitive(state):
+    a = state.create_node()
+    assert a is not None
+
+    # Passing data is not enforced, as the data will be interpreted if necessary
+    l = state.read_reverse_dict(a, a)
+    assert l == []
+
+
+@pytest.mark.usefixtures("state")
+def test_read_reverse_dict_node_simple(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_nodevalue("f")
+    d = state.create_edge(a, b)
+    e = state.create_edge(d, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    l = state.read_reverse_dict(b, "f")
+    assert set(l) == set([a])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_reverse_dict_no_match(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_nodevalue("g")
+    d = state.create_edge(a, b)
+    e = state.create_edge(d, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    l = state.read_reverse_dict(b, "f")
+    assert l == []
+
+
+@pytest.mark.usefixtures("state")
+def test_read_reverse_dict_node_multi(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_nodevalue("f")
+    d = state.create_edge(a, b)
+    e = state.create_edge(d, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    g = state.create_node()
+    h = state.create_nodevalue("k")
+    i = state.create_edge(a, g)
+    j = state.create_edge(i, h)
+    assert g is not None
+    assert h is not None
+    assert i is not None
+    assert j is not None
+
+    l = state.read_reverse_dict(b, "f")
+    assert set(l) == set([a])
+
+    l = state.read_reverse_dict(g, "k")
+    assert set(l) == set([a])
+
+    l = state.read_reverse_dict(a, "l")
+    assert l == []
+
+
+@pytest.mark.usefixtures("state")
+def test_read_reverse_dict_node_multi_ambiguous(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_nodevalue("f")
+    d = state.create_edge(b, a)
+    e = state.create_edge(d, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    g = state.create_node()
+    h = state.create_nodevalue("f")
+    i = state.create_edge(g, a)
+    j = state.create_edge(i, h)
+    assert g is not None
+    assert h is not None
+    assert i is not None
+    assert j is not None
+
+    l = state.read_reverse_dict(a, "f")
+    assert set(l) == set([b, g])
+
+
+@pytest.mark.usefixtures("state")
+def test_read_reverse_dict_node_uncertain(state):
+    a = state.create_node()
+    b = state.create_node()
+    c = state.create_nodevalue("f")
+    d = state.create_edge(a, b)
+    e = state.create_edge(d, c)
+    assert a is not None
+    assert b is not None
+    assert c is not None
+    assert d is not None
+    assert e is not None
+
+    h = state.create_nodevalue("g")
+    i = state.create_edge(d, h)
+    assert h is not None
+    assert i is not None
+
+    l = state.read_reverse_dict(b, "f")
+    assert set(l) == set([a])

+ 88 - 0
state/test/test_read_value.py

@@ -0,0 +1,88 @@
+import pytest
+
+
+@pytest.mark.usefixtures("state")
+def test_read_value_different_id_simple(state):
+    id1 = state.create_nodevalue(1)
+    id2 = state.create_nodevalue(2)
+    assert id1 is not None
+    assert id2 is not None
+
+    v1 = state.read_value(id1)
+    v2 = state.read_value(id2)
+    assert v1 == 1
+    assert v2 == 2
+
+
+@pytest.mark.usefixtures("state")
+def test_read_value_integer_ib_negative(state):
+    # Just within range
+    for i in range(-2 ** 63, -2 ** 63 + 10):
+        id1 = state.create_nodevalue(i)
+        assert id1 is not None
+
+        v = state.read_value(id1)
+        assert v == i
+
+
+@pytest.mark.usefixtures("state")
+def test_read_value_integer_ib_zero(state):
+    # Nicely within range
+    for i in range(-10, 10):
+        id1 = state.create_nodevalue(i)
+        assert id1 is not None
+
+        v = state.read_value(id1)
+        assert v == i
+
+
+@pytest.mark.usefixtures("state")
+def test_read_value_integer_ib_positive(state):
+    # Just within range
+    for i in range(2 ** 63 - 10, 2 ** 63):
+        id1 = state.create_nodevalue(i)
+        assert id1 is not None
+
+        v = state.read_value(id1)
+        assert v == i
+
+
+@pytest.mark.usefixtures("state")
+def test_read_value_boolean(state):
+    id1 = state.create_nodevalue(True)
+    id2 = state.create_nodevalue(False)
+    assert id1 is not None
+    assert id2 is not None
+
+    v1 = state.read_value(id1)
+    v2 = state.read_value(id2)
+    assert v1 == True
+    assert v2 == False
+
+
+@pytest.mark.usefixtures("state")
+def test_read_nodevalue_boolean_same(state):
+    id1 = state.create_nodevalue(True)
+    id2 = state.create_nodevalue(True)
+    assert id1 is not None
+    assert id2 is not None
+
+    v1 = state.read_value(id1)
+    v2 = state.read_value(id2)
+    assert v1 == True
+    assert v2 == True
+
+
+@pytest.mark.usefixtures("state")
+def test_read_value_no_exist(state):
+    v1 = state.read_value(100000)
+    assert v1 is None
+
+
+@pytest.mark.usefixtures("state")
+def test_read_value_no_value(state):
+    id1 = state.create_node()
+    assert id1 is not None
+
+    v1 = state.read_value(id1)
+    assert v1 is None