def networkx_to_data_dict(graph_nx,
                          node_shape_hint=None,
                          edge_shape_hint=None,
                          data_type_hint=np.float32):
  """Returns a data dict of Numpy data from a networkx graph.

  The networkx graph should be set up such that, for fixed shapes `node_shape`,
   `edge_shape` and `global_shape`:
    - `graph_nx.nodes(data=True)[i][-1]["features"]` is, for any node index i, a
      tensor of shape `node_shape`, or `None`;
    - `graph_nx.edges(data=True)[i][-1]["features"]` is, for any edge index i, a
      tensor of shape `edge_shape`, or `None`;
    - `graph_nx.edges(data=True)[i][-1]["index"]`, if present, defines the order
      in which the edges will be sorted in the resulting `data_dict`;
    - `graph_nx.graph["features"] is a tensor of shape `global_shape`, or
      `None`.

  The dictionary `type_hints` can provide hints of the "float" and "int" types
  for missing values.

  The output data is a sequence of data dicts with fields:
    NODES, EDGES, RECEIVERS, SENDERS, GLOBALS, N_NODE, N_EDGE.

  Args:
    graph_nx: A `networkx.OrderedMultiDiGraph`. The node keys must be sequential
      integer values following the order in which nodes are added to the graph
      starting from zero. That is `list(graph_nx.nodes)[i] == i`.
    node_shape_hint: (iterable of `int` or `None`, default=`None`) If the graph
      does not contain nodes, the trailing shape for the created `NODES` field.
      If `None` (the default), this field is left `None`. This is not used if
      `graph_nx` contains at least one node.
    edge_shape_hint: (iterable of `int` or `None`, default=`None`) If the graph
      does not contain edges, the trailing shape for the created `EDGES` field.
      If `None` (the default), this field is left `None`. This is not used if
      `graph_nx` contains at least one edge.
    data_type_hint: (numpy dtype, default=`np.float32`) If the `NODES` or
      `EDGES` fields are autocompleted, their type.

  Returns:
    The data `dict` of Numpy data.

  Raises:
    TypeError: If `graph_nx` is not an instance of networkx.
    KeyError: If `graph_nx` contains at least one node without the "features"
      key in its attribute dictionary, or at least one edge without the
      "features" key in its attribute dictionary.
    ValueError: If `graph_nx` contains at least one node with a `None`
      "features" attribute and one least one node with a non-`None` "features"
      attribute; or if `graph_nx` contains at least one edge with a `None`
      "features" attribute and one least one edge with a non-`None` "features"
      attribute.
    ValueError: If the nodes have keys that are not consistent with the order
      of the nodes.
  """
  nodes = None
  try:
    number_of_nodes = graph_nx.number_of_nodes()
  except ValueError as e:
    raise TypeError("Argument `graph_nx` of wrong type {}".format(
        type(graph_nx))) from e
  if number_of_nodes == 0:
    if node_shape_hint is not None:
      nodes = np.zeros([0] + list(node_shape_hint), dtype=data_type_hint)
  else:
    try:
      nodes_data = [
          data[GRAPH_NX_FEATURES_KEY]
          for node_i, (key, data) in enumerate(graph_nx.nodes(data=True))
          if _check_key(node_i, key) and data[GRAPH_NX_FEATURES_KEY] is not None
      ]
      if nodes_data:
        if len(nodes_data) != number_of_nodes:
          raise ValueError(
              "Either all the nodes should have features, or none of them")
        nodes = np.array(nodes_data)
    except KeyError as e:
      raise KeyError("Missing 'features' field from the graph nodes. "
                     "This could be due to the node having been silently added "
                     "as a consequence of an edge addition when creating the "
                     "networkx instance") from e

  edges = None
  number_of_edges = graph_nx.number_of_edges()
  if number_of_edges == 0:
    senders = np.zeros(0, dtype=np.int32)
    receivers = np.zeros(0, dtype=np.int32)
    if edge_shape_hint is not None:
      edges = np.zeros([0] + list(edge_shape_hint), dtype=data_type_hint)
  else:
    if "index" in list(graph_nx.edges(data=True))[0][2]:
      senders, receivers, edge_attr_dicts = zip(
          *sorted(graph_nx.edges(data=True), key=lambda x: x[2]["index"]))
    else:
      senders, receivers, edge_attr_dicts = zip(*graph_nx.edges(data=True))
    senders = np.array(senders, dtype=np.int32)
    receivers = np.array(receivers, dtype=np.int32)
    edges_data = [
        x[GRAPH_NX_FEATURES_KEY]
        for x in edge_attr_dicts
        if x[GRAPH_NX_FEATURES_KEY] is not None
    ]
    if edges_data:
      if len(edges_data) != number_of_edges:
        raise ValueError(
            "Either all the edges should have features, or none of them")
      edges = np.array(edges_data)

  globals_ = None
  if GRAPH_NX_FEATURES_KEY in graph_nx.graph:
    globals_ = graph_nx.graph[GRAPH_NX_FEATURES_KEY]

  return {
      NODES: nodes,
      EDGES: edges,
      RECEIVERS: receivers,
      SENDERS: senders,
      GLOBALS: globals_,
      N_NODE: number_of_nodes,
      N_EDGE: number_of_edges,
  }
Returns a data dict of Numpy data from a networkx graph.

  The networkx graph should be set up such that, for fixed shapes `node_shape`,
   `edge_shape` and `global_shape`:
    - `graph_nx.nodes(data=True)[i][-1]["features"]` is, for any node index i, a
      tensor of shape `node_shape`, or `None`;
    - `graph_nx.edges(data=True)[i][-1]["features"]` is, for any edge index i, a
      tensor of shape `edge_shape`, or `None`;
    - `graph_nx.edges(data=True)[i][-1]["index"]`, if present, defines the order
      in which the edges will be sorted in the resulting `data_dict`;
    - `graph_nx.graph["features"] is a tensor of shape `global_shape`, or
      `None`.

  The dictionary `type_hints` can provide hints of the "float" and "int" types
  for missing values.

  The output data is a sequence of data dicts with fields:
    NODES, EDGES, RECEIVERS, SENDERS, GLOBALS, N_NODE, N_EDGE.

  Args:
    graph_nx: A `networkx.OrderedMultiDiGraph`. The node keys must be sequential
      integer values following the order in which nodes are added to the graph
      starting from zero. That is `list(graph_nx.nodes)[i] == i`.
    node_shape_hint: (iterable of `int` or `None`, default=`None`) If the graph
      does not contain nodes, the trailing shape for the created `NODES` field.
      If `None` (the default), this field is left `None`. This is not used if
      `graph_nx` contains at least one node.
    edge_shape_hint: (iterable of `int` or `None`, default=`None`) If the graph
      does not contain edges, the trailing shape for the created `EDGES` field.
      If `None` (the default), this field is left `None`. This is not used if
      `graph_nx` contains at least one edge.
    data_type_hint: (numpy dtype, default=`np.float32`) If the `NODES` or
      `EDGES` fields are autocompleted, their type.

  Returns:
    The data `dict` of Numpy data.

  Raises:
    TypeError: If `graph_nx` is not an instance of networkx.
    KeyError: If `graph_nx` contains at least one node without the "features"
      key in its attribute dictionary, or at least one edge without the
      "features" key in its attribute dictionary.
    ValueError: If `graph_nx` contains at least one node with a `None`
      "features" attribute and one least one node with a non-`None` "features"
      attribute; or if `graph_nx` contains at least one edge with a `None`
      "features" attribute and one least one edge with a non-`None` "features"
      attribute.
    ValueError: If the nodes have keys that are not consistent with the order
      of the nodes.
  
graph_nets/utils_np.py