def deserialize(data, graph, parser_classes, dkey='root', rnid=None): """ Deserialize the items in an ordered list to nodes. The same method is also used to deserialize Python tuple and set types by using the ParseListType as base class. Lists, tuples and sets are the same from a graph perspective as mutability and order are not considered in graphs although it could be. :param data: list data to deserialize :type data: :py:list :param graph: graph to add nodes to :type graph: :graphit:GraphAxis :param parser_classes: parser classes used to deserialize list items :param dkey: list node key_tag :type dkey: :py:str :param rnid: nid of node to connect list node to :type rnid: :py:int """ nid = graph.add_node(dkey, format=return_instance_type(data), type='array') if rnid: graph.add_edge(rnid, nid) # Deserialize all list items in order for i, value in enumerate(data, start=1): parser = parser_classes.get(return_instance_type(value), parser_classes['fallback']) p = parser() p.deserialize(value, graph, parser_classes, dkey='item-{0}'.format(i), rnid=nid)
def deserialize(data, graph, parser_classes, dkey='root', rnid=None): nid = graph.add_node(dkey, format=return_instance_type(data), type='object') if rnid: graph.add_edge(rnid, nid) for key, value in sorted(data.items(), key=lambda x: str(x[0])): parser = parser_classes.get(return_instance_type(value), parser_classes['fallback']) p = parser() p.deserialize(value, graph, parser_classes, dkey=key, rnid=nid)
def deserialize(data, graph, parser_classes, dkey='root', rnid=None): nid = graph.add_node(dkey, format=return_instance_type(data), type='array') if rnid: graph.add_edge(rnid, nid) for i, value in enumerate(data, start=1): parser = parser_classes.get(return_instance_type(value), ParseListItem) p = parser() p.deserialize(value, graph, parser_classes, dkey='item-{0}'.format(i), rnid=nid)
def deserialize(data, graph, parser_classes, dkey=None, rnid=None): """ Deserialize Python primitives or objects to nodes :param data: data to deserialize :param graph: graph to add nodes to :type graph: :graphit:GraphAxis :param parser_classes: parser classes used to deserialize list items :param dkey: data node key_tag :type dkey: :py:str :param rnid: nid of node to connect list node to :type rnid: :py:int """ dtype = 'primative' if isinstance(data, PY_PRIMITIVES) else 'object' nid = graph.add_node(dkey, format=type(data).__name__, type=dtype) if rnid: graph.add_edge(rnid, nid) if not isinstance(data, PY_DATA_OBJECTS) and hasattr(data, '__iter__'): for i, value in enumerate(data, start=1): parser = parser_classes.get(return_instance_type(value), parser_classes['fallback']) p = parser() p.deserialize(value, graph, parser_classes, dkey='item-{0}'.format(i), rnid=nid) else: node = graph.getnodes(nid) node.set(graph.data.value_tag, data)
def deserialize(data, graph, parser_classes, dkey='root', rnid=None): """ Level based deserialize key/value pairs in a dictionary All key/value pairs at the same level in a (nested) dictionary for for which the value itself is not a dictionary are stored in the same node. :param data: dictionary data to deserialize :type data: :py:dict :param graph: graph to add nodes to :type graph: :graphit:GraphAxis :param parser_classes: parser classes used to deserialize list items :param dkey: dictionary item node key_tag :type dkey: :py:str :param rnid: nid of node to connect list node to :type rnid: :py:int """ nid = graph.add_node(dkey, format=return_instance_type(data), type='object') if rnid: graph.add_edge(rnid, nid) for key, value in sorted(data.items(), key=lambda x: str(x[0])): if isinstance(value, dict): parser = parser_classes['dict'] p = parser() p.deserialize(value, graph, parser_classes, dkey=key, rnid=nid) else: graph.nodes[nid][key] = value
def deserialize(data, graph, parser_classes, dkey='root', rnid=None): """ Deserialize key/value pairs in a dictionary to nodes :param data: list data to deserialize :type data: :py:list :param graph: graph to add nodes to :type graph: :graphit:GraphAxis :param parser_classes: parser classes used to deserialize list items :param dkey: list node key_tag :type dkey: :py:str :param rnid: nid of node to connect list node to :type rnid: :py:int """ nid = graph.add_node(dkey, format=return_instance_type(data), type='object') if rnid: graph.add_edge(rnid, nid) for key, value in sorted(data.items(), key=lambda x: str(x[0])): parser = parser_classes.get(return_instance_type(value), parser_classes['fallback']) p = parser() p.deserialize(value, graph, parser_classes, dkey=key, rnid=nid)
def read_pydata(data, graph=None, parser_classes=None, level=0): """ Parse (hierarchical) python data structures to a graph Many data formats are first parsed to a python structure before they are converted to a graph using the `read_pydata` function. The function supports any object that is an instance of, or behaves as, a Python dictionary, list, tuple or set and converts these (nested) structures to graph nodes and edges for connectivity. Data is stored in nodes using the node and edge 'key_tag' and 'value_tag' attributes in the Graph class. Data type and format information are also stored as part of the nodes to enable reconstruction of the Python data structure on export using the `write_pydata` function. Changing type and format on a node or edge allows for customized data export. Parsing of data structures to nodes and edges is handled by parser classes that need to define the methods `deserialize` for reading and `serialize` for writing. In `write_pydata` these classes are registered with the ORM to fully customize the use of the `serialize` method. In the `read_pydata` function the ORM cannot be used because the nodes/edges themselves do not yet exist. Instead they are provided as a dictionary through the `parser_classes` argument. The dictionary defines the string representation of the Python data type as key and parser class as value. Parser customization is important as Python data structures can be represented as a graph structure in different ways. This is certainly true for dictionaries where key/value pairs can be part of the node attributes, as separate nodes or as a combination of the two. `read_pydata` has quick support for two scenario's using the `level` argument: * level 0: every dictionary key/value pair is represented as a node regardless of its position in the nested data structure * level 1: all keys at the same level in the hierarchy that have a primitive type value are stored as part of the node attributes. If the `graph` is empty, the first node added to the graph is assigned as root node. If the `graph` is not empty, new nodes and edges will be added to it as subgraph. Edge connections between the two will have to be made afterwards. :param data: Python (hierarchical) data structure :param graph: GraphAxis object to import dictionary data in :type graph: :graphit:GraphAxis :param parser_classes: parser class definition for different Python data types. Updates default classes for level 0 or 1 :type parser_classes: :py:dict :param level: dictionary parsing mode :type level: :py:int :return: GraphAxis object :rtype: :graphit:GraphAxis """ # User defined or default GraphAxis object if graph is None: graph = GraphAxis() elif not isinstance(graph, GraphAxis): raise GraphitException('Unsupported graph type {0}'.format(type(graph))) # Determine parser classes to use based on level assert level in (0, 1), GraphitException('Unsupported level {0}. Required to be 0 or 1'.format(level)) if level == 0: parser_class_dict = copy.copy(ORMDEFS_LEVEL0) else: parser_class_dict = copy.copy(ORMDEFS_LEVEL1) # Update parser_class_dict with custom classes if any if isinstance(parser_classes, dict): parser_class_dict.update(parser_classes) # Define root if graph.empty(): graph.root = graph.data.nodeid # Start recursive parsing by calling the `deserialize` method on the parser object parser = parser_class_dict.get(return_instance_type(data), parser_class_dict['fallback']) p = parser() p.deserialize(data, graph, parser_class_dict) return graph