# Path: regraph/exceptions.py
# class ReGraphError(ReGraphException):
#     """Exception for errors in ReGraph."""
# 
# class GraphError(ReGraphException):
#     """Class for errors in graph transformation with primitives."""
# 
# class GraphAttrsWarning(ReGraphWarning):
#     """Class for warnings about empty attrs dict."""
# 
# Path: regraph/utils.py
# def load_nodes_from_json(j_data):
#     """Load nodes from json-like dict."""
#     loaded_nodes = []
#     if "nodes" in j_data.keys():
#         j_nodes = j_data["nodes"]
#         for node in j_nodes:
#             if "id" in node.keys():
#                 node_id = node["id"]
#             else:
#                 raise ReGraphError(
#                     "Error loading graph: node id is not specified!")
#             attrs = None
#             if "attrs" in node.keys():
#                 attrs = json_dict_to_attrs(node["attrs"])
#             loaded_nodes.append((node_id, attrs))
#     else:
#         raise ReGraphError(
#             "Error loading graph: no nodes specified!")
#     return loaded_nodes
# 
# def load_edges_from_json(j_data):
#     """Load edges from json-like dict."""
#     loaded_edges = []
#     if "edges" in j_data.keys():
#         j_edges = j_data["edges"]
#         for edge in j_edges:
#             if "from" in edge.keys():
#                 s_node = edge["from"]
#             else:
#                 raise ReGraphError(
#                     "Error loading graph: edge source is not specified!")
#             if "to" in edge.keys():
#                 t_node = edge["to"]
#             else:
#                 raise ReGraphError(
#                     "Error loading graph: edge target is not specified!")
#             if "attrs" in edge.keys():
#                 attrs = json_dict_to_attrs(edge["attrs"])
#                 loaded_edges.append((s_node, t_node, attrs))
#             else:
#                 loaded_edges.append((s_node, t_node))
#     return loaded_edges
# 
# def generate_new_id(collection, basename):
#     """Generate unique id for a node."""
#     node_id = basename
#     i = 1
#     while node_id in collection:
#         node_id = "{}_{}".format(basename, i)
#         i += 1
#     return node_id
# 
# def normalize_attrs(attrs):
#     """Normalize node attributes."""
#     if attrs is not None:
#         for k, v in list(attrs.items()):
#             if not isinstance(v, AttributeSet):
#                 attrs[k] = FiniteSet(v)
#                 if attrs[k].is_empty():
#                     del attrs[k]
#     return
# 
# def safe_deepcopy_dict(d):
#     """Util for safe deepcopy of a dict.
# 
#     Solves the issue with 'TypeError: can't pickle dict_items objects'
#     of the default 'copy.deepcopy'.
#     """
#     try:
#         new_d = copy.deepcopy(d)
#     except TypeError:
#         new_d = dict()
#         for k, v in d.items():
#             new_d[k] = copy.deepcopy(list(v.items()))
#     return new_d
# 
# def set_attrs(old_attrs, attrs, normalize=True, update=True):
#     if normalize:
#         normalize_attrs(attrs)
# 
#     for key in attrs:
#         old_attrs[key] = attrs[key]
#     if update:
#         keys_to_remove = set()
#         for key in old_attrs:
#             if key not in attrs:
#                 keys_to_remove.add(key)
#         for key in keys_to_remove:
#             del old_attrs[key]
#     return old_attrs
# 
# def add_attrs(old_attrs, attrs, normalize=True):
#     if normalize:
#         normalize_attrs(attrs)
#     for key in attrs:
#         if key in old_attrs:
#             old_attrs[key] = old_attrs[key].union(attrs[key])
#         else:
#             old_attrs[key] = attrs[key]
# 
# def remove_attrs(old_attrs, attrs, normalize=True):
#     if normalize:
#         normalize_attrs(attrs)
#     for key, value in attrs.items():
#         if key in old_attrs:
#             new_set = old_attrs[key].difference(value)
#             if not new_set:
#                 del old_attrs[key]
#             else:
#                 old_attrs[key] = new_set
# 
# def merge_attributes(attr1, attr2, method="union"):
#     """Merge two dictionaries of attributes."""
#     if method == "union":
#         return attrs_union(attr1, attr2)
#     elif method == "intersection":
#         return attrs_intersection(attr1, attr2)
#     else:
#         raise ReGraphError("Merging method %s is not defined!" % method)
# 
# def keys_by_value(dictionary, val):
#     """Get keys of a dictionary by a value."""
#     res = []
#     for key, value in dictionary.items():
#         if value == val:
#             res.append(key)
#     return res


import json
import os
import warnings
from abc import ABC, abstractmethod
from regraph.exceptions import (ReGraphError,
                                GraphError,
                                GraphAttrsWarning,
                                )
from regraph.utils import (load_nodes_from_json,
                           load_edges_from_json,
                           generate_new_id,
                           normalize_attrs,
                           safe_deepcopy_dict,
                           set_attrs,
                           add_attrs,
                           remove_attrs,
                           merge_attributes,
                           keys_by_value,
                           )

        Raises
        ------
        GraphError
            If an edge between `s` and `t` does not exist.
        """
        self.set_edge_attrs(s, t, attrs, normalize, update)

    def add_edge_attrs(self, s, t, attrs):
        """Add attributes of an edge in a graph.

        Parameters
        ----------
        s : hashable
            Source node id.
        t : hashable
            Target node id.
        attrs : dict
            Dictionary with attributes to remove.

        Raises
        ------
        GraphError
            If an edge between `s` and `t` does not exist.
        """
        if not self.exists_edge(s, t):
            raise GraphError(
                "Edge {}->{} does not exist".format(s, t))

        edge_attrs = safe_deepcopy_dict(self.get_edge(s, t))

# Path: regraph/exceptions.py
# class ReGraphError(ReGraphException):
#     """Exception for errors in ReGraph."""
# 
# class GraphError(ReGraphException):
#     """Class for errors in graph transformation with primitives."""
# 
# class GraphAttrsWarning(ReGraphWarning):
#     """Class for warnings about empty attrs dict."""
# 
# Path: regraph/utils.py
# def load_nodes_from_json(j_data):
#     """Load nodes from json-like dict."""
#     loaded_nodes = []
#     if "nodes" in j_data.keys():
#         j_nodes = j_data["nodes"]
#         for node in j_nodes:
#             if "id" in node.keys():
#                 node_id = node["id"]
#             else:
#                 raise ReGraphError(
#                     "Error loading graph: node id is not specified!")
#             attrs = None
#             if "attrs" in node.keys():
#                 attrs = json_dict_to_attrs(node["attrs"])
#             loaded_nodes.append((node_id, attrs))
#     else:
#         raise ReGraphError(
#             "Error loading graph: no nodes specified!")
#     return loaded_nodes
# 
# def load_edges_from_json(j_data):
#     """Load edges from json-like dict."""
#     loaded_edges = []
#     if "edges" in j_data.keys():
#         j_edges = j_data["edges"]
#         for edge in j_edges:
#             if "from" in edge.keys():
#                 s_node = edge["from"]
#             else:
#                 raise ReGraphError(
#                     "Error loading graph: edge source is not specified!")
#             if "to" in edge.keys():
#                 t_node = edge["to"]
#             else:
#                 raise ReGraphError(
#                     "Error loading graph: edge target is not specified!")
#             if "attrs" in edge.keys():
#                 attrs = json_dict_to_attrs(edge["attrs"])
#                 loaded_edges.append((s_node, t_node, attrs))
#             else:
#                 loaded_edges.append((s_node, t_node))
#     return loaded_edges
# 
# def generate_new_id(collection, basename):
#     """Generate unique id for a node."""
#     node_id = basename
#     i = 1
#     while node_id in collection:
#         node_id = "{}_{}".format(basename, i)
#         i += 1
#     return node_id
# 
# def normalize_attrs(attrs):
#     """Normalize node attributes."""
#     if attrs is not None:
#         for k, v in list(attrs.items()):
#             if not isinstance(v, AttributeSet):
#                 attrs[k] = FiniteSet(v)
#                 if attrs[k].is_empty():
#                     del attrs[k]
#     return
# 
# def safe_deepcopy_dict(d):
#     """Util for safe deepcopy of a dict.
# 
#     Solves the issue with 'TypeError: can't pickle dict_items objects'
#     of the default 'copy.deepcopy'.
#     """
#     try:
#         new_d = copy.deepcopy(d)
#     except TypeError:
#         new_d = dict()
#         for k, v in d.items():
#             new_d[k] = copy.deepcopy(list(v.items()))
#     return new_d
# 
# def set_attrs(old_attrs, attrs, normalize=True, update=True):
#     if normalize:
#         normalize_attrs(attrs)
# 
#     for key in attrs:
#         old_attrs[key] = attrs[key]
#     if update:
#         keys_to_remove = set()
#         for key in old_attrs:
#             if key not in attrs:
#                 keys_to_remove.add(key)
#         for key in keys_to_remove:
#             del old_attrs[key]
#     return old_attrs
# 
# def add_attrs(old_attrs, attrs, normalize=True):
#     if normalize:
#         normalize_attrs(attrs)
#     for key in attrs:
#         if key in old_attrs:
#             old_attrs[key] = old_attrs[key].union(attrs[key])
#         else:
#             old_attrs[key] = attrs[key]
# 
# def remove_attrs(old_attrs, attrs, normalize=True):
#     if normalize:
#         normalize_attrs(attrs)
#     for key, value in attrs.items():
#         if key in old_attrs:
#             new_set = old_attrs[key].difference(value)
#             if not new_set:
#                 del old_attrs[key]
#             else:
#                 old_attrs[key] = new_set
# 
# def merge_attributes(attr1, attr2, method="union"):
#     """Merge two dictionaries of attributes."""
#     if method == "union":
#         return attrs_union(attr1, attr2)
#     elif method == "intersection":
#         return attrs_intersection(attr1, attr2)
#     else:
#         raise ReGraphError("Merging method %s is not defined!" % method)
# 
# def keys_by_value(dictionary, val):
#     """Get keys of a dictionary by a value."""
#     res = []
#     for key, value in dictionary.items():
#         if value == val:
#             res.append(key)
#     return res


# Path: regraph/graphs.py
import json
import os
import warnings
from abc import ABC, abstractmethod
from regraph.exceptions import (ReGraphError,
                                GraphError,
                                GraphAttrsWarning,
                                )
from regraph.utils import (load_nodes_from_json,
                           load_edges_from_json,
                           generate_new_id,
                           normalize_attrs,
                           safe_deepcopy_dict,
                           set_attrs,
                           add_attrs,
                           remove_attrs,
                           merge_attributes,
                           keys_by_value,
                           )


        Raises
        ------
        GraphError
            If an edge between `s` and `t` does not exist.
        """
        self.set_edge_attrs(s, t, attrs, normalize, update)

    def add_edge_attrs(self, s, t, attrs):
        """Add attributes of an edge in a graph.

        Parameters
        ----------
        s : hashable
            Source node id.
        t : hashable
            Target node id.
        attrs : dict
            Dictionary with attributes to remove.

        Raises
        ------
        GraphError
            If an edge between `s` and `t` does not exist.
        """
        if not self.exists_edge(s, t):
            raise GraphError(
                "Edge {}->{} does not exist".format(s, t))

        edge_attrs = safe_deepcopy_dict(self.get_edge(s, t))

        add_attrs(edge_attrs, attrs, normalize=True)