Improved generator version of nx.get_edge_attributes

    Args:
        on_missing (str): Strategy for handling nodes missing from G.
            Can be {'error', 'default', 'filter'}.  defaults to 'error'.
            is on_missing is not error, then we allow any edge even if the
            endpoints are not in the graph.
        on_keyerr (str): Strategy for handling keys missing from node dicts.
            Can be {'error', 'default', 'filter'}.  defaults to 'default'
            if default is specified, otherwise defaults to 'error'.

    Example:
        >>> # ENABLE_DOCTEST
        >>> from utool.util_graph import *  # NOQA
        >>> import utool as ut
        >>> G = nx.Graph([(1, 2), (2, 3), (3, 4)])
        >>> nx.set_edge_attributes(G, name='part', values={(1, 2): 'bar', (2, 3): 'baz'})
        >>> edges = [(1, 2), (2, 3), (3, 4), (4, 5)]
        >>> func = ut.partial(ut.nx_gen_edge_attrs, G, 'part', default=None)
        >>> #
        >>> assert len(list(func(on_missing='error', on_keyerr='default'))) == 3
        >>> assert len(list(func(on_missing='error', on_keyerr='filter'))) == 2
        >>> ut.assert_raises(KeyError, list, func(on_missing='error', on_keyerr='error'))
        >>> #
        >>> assert len(list(func(edges, on_missing='filter', on_keyerr='default'))) == 3
        >>> assert len(list(func(edges, on_missing='filter', on_keyerr='filter'))) == 2
        >>> ut.assert_raises(KeyError, list, func(edges, on_missing='filter', on_keyerr='error'))
        >>> #
        >>> assert len(list(func(edges, on_missing='default', on_keyerr='default'))) == 4
        >>> assert len(list(func(edges, on_missing='default', on_keyerr='filter'))) == 2
        >>> ut.assert_raises(KeyError, list, func(edges, on_missing='default', on_keyerr='error'))

def nx_gen_edge_attrs(G, key, edges=None, default=util_const.NoParam,
                      on_missing='error', on_keyerr='default'):
    """
    Improved generator version of nx.get_edge_attributes

    Args:
        on_missing (str): Strategy for handling nodes missing from G.
            Can be {'error', 'default', 'filter'}.  defaults to 'error'.
            is on_missing is not error, then we allow any edge even if the
            endpoints are not in the graph.
        on_keyerr (str): Strategy for handling keys missing from node dicts.
            Can be {'error', 'default', 'filter'}.  defaults to 'default'
            if default is specified, otherwise defaults to 'error'.

    Example:
        >>> # ENABLE_DOCTEST
        >>> from utool.util_graph import *  # NOQA
        >>> import utool as ut
        >>> G = nx.Graph([(1, 2), (2, 3), (3, 4)])
        >>> nx.set_edge_attributes(G, name='part', values={(1, 2): 'bar', (2, 3): 'baz'})
        >>> edges = [(1, 2), (2, 3), (3, 4), (4, 5)]
        >>> func = ut.partial(ut.nx_gen_edge_attrs, G, 'part', default=None)
        >>> #
        >>> assert len(list(func(on_missing='error', on_keyerr='default'))) == 3
        >>> assert len(list(func(on_missing='error', on_keyerr='filter'))) == 2
        >>> ut.assert_raises(KeyError, list, func(on_missing='error', on_keyerr='error'))
        >>> #
        >>> assert len(list(func(edges, on_missing='filter', on_keyerr='default'))) == 3
        >>> assert len(list(func(edges, on_missing='filter', on_keyerr='filter'))) == 2
        >>> ut.assert_raises(KeyError, list, func(edges, on_missing='filter', on_keyerr='error'))
        >>> #
        >>> assert len(list(func(edges, on_missing='default', on_keyerr='default'))) == 4
        >>> assert len(list(func(edges, on_missing='default', on_keyerr='filter'))) == 2
        >>> ut.assert_raises(KeyError, list, func(edges, on_missing='default', on_keyerr='error'))
    """
    if on_missing is None:
        on_missing = 'error'
    if default is util_const.NoParam and on_keyerr == 'default':
        on_keyerr = 'error'

    if edges is None:
        if G.is_multigraph():
            raise NotImplementedError('')
            # uvk_iter = G.edges(keys=True)
        else:
            edges = G.edges()
    # Generate `edge_data` edges and data dictionary
    if on_missing == 'error':
        edge_data = (((u, v), G.adj[u][v]) for u, v in edges)
    elif on_missing == 'filter':
        edge_data = (((u, v), G.adj[u][v]) for u, v in edges if G.has_edge(u, v))
    elif on_missing == 'default':
        edge_data = (((u, v), G.adj[u][v])
                     if G.has_edge(u, v) else ((u, v), {})
                     for u, v in edges)
    else:
        raise KeyError('on_missing={}'.format(on_missing))
    # Get `edge_attrs` desired value out of dictionary
    if on_keyerr == 'error':
        edge_attrs = ((e, d[key]) for e, d in edge_data)
    elif on_keyerr == 'filter':
        edge_attrs = ((e, d[key]) for e, d in edge_data if key in d)
    elif on_keyerr == 'default':
        edge_attrs = ((e, d.get(key, default)) for e, d in edge_data)
    else:
        raise KeyError('on_keyerr={}'.format(on_keyerr))
    return edge_attrs