class_docstr: Universal Gremlin<>pandas/graphistry functionality across Gremlin connectors
  
  Currently serializes queries as strings instead of bytecode in order to support cosmosdb
docstr: Enrich nodes by matching g._node to gremlin nodes
  If no g._nodes table available, first synthesize g._nodes from g._edges
docstr_tok: ["Enrich","nodes","by","matching","g._node","to","gremlin","nodes","If","no","g._nodes","table","available",",","first","synthesize","g._nodes","from","g._edges"]
code: def fetch_nodes(
      self,
      batch_size=1000,
      dry_run=False,
      verbose=False,
      ignore_errors=False,
  ) -> Union[Plottable, List[str]]:
      """
      Enrich nodes by matching g._node to gremlin nodes
      If no g._nodes table available, first synthesize g._nodes from g._edges
      """
      g = self
      nodes_df = g._nodes
      node_id = g._node
      if node_id is None:
          node_id = "id"
          g = g.bind(node=node_id)
      if nodes_df is None:
          edges_df = g._edges
          if g._edges is None:
              raise Exception(
                  "Node enrichment requires either g._nodes or g._edges to be available"
              )
  
          if g._source is None or g._destination is None:
              raise Exception(
                  "Cannot infer nodes table without having set g._source and g._destination bindings"
              )
  
          nodes_df = pd.concat(
              [
                  edges_df[[g._source]]
                  .rename(columns={g._source: node_id})
                  .drop_duplicates(),
                  edges_df[[g._destination]]
                  .rename(columns={g._destination: node_id})
                  .drop_duplicates(),
              ],
              ignore_index=True,
              sort=False,
          )
  
      if node_id not in nodes_df:
          raise Exception(
              "Node id node in nodes table, excepted column", node_id
          )
  
      # Work in batches of 1000
      enriched_nodes_dfs = []
      dry_runs = []
      for start in range(0, len(nodes_df), batch_size):
          nodes_batch_df = nodes_df[start : (start + batch_size)]
          node_ids = ", ".join(
              [f'"{x}"' for x in nodes_batch_df[node_id].to_list()]
          )
          query = (
              f"g.V({node_ids}).elementMap()"  # TODO: alt for cosmos?
          )
          if dry_run:
              dry_runs.append(query)
              continue
          resultset = self.gremlin_run([query], throw=True)
          g2 = self.resultset_to_g(
              resultset, "nodes", verbose, ignore_errors
          )
          assert g2._nodes is not None
          enriched_nodes_dfs.append(g2._nodes)
      if dry_run:
          return dry_runs
      nodes2_df = pd.concat(
          enriched_nodes_dfs, sort=False, ignore_index=True
      )
      g2 = g.nodes(nodes2_df, node_id)
      return g2
  
imports: from typing import Any, Callable, Iterable, List, Optional, Set, Union, TYPE_CHECKING
  import os, pandas
  from .Plottable import Plottable
  from .util import setup_logger
label_desc: Use image node_id 8 for calling the GremlinMixin obj's underlying member method code with example usage: obj.fetch_nodes(batch_size, dry_run, verbose, ignore_errors) and returns: g2, dry_runs