from __future__ import unicode_literals

from builtins import object
import hashlib
import json


class Node(object):
    """Node base"""
    def __init__(self, parents, name, *args, **kwargs):
        parent_hashes = [parent._hash for parent in parents]
        assert len(parent_hashes) == len(set(parent_hashes)), 'Same node cannot be included as parent multiple times'
        self._parents = parents
        self._name = name
        self._args = args
        self._kwargs = kwargs
        self._update_hash()

    def __repr__(self):
        formatted_props = ['{}'.format(arg) for arg in self._args]
        formatted_props += ['{}={!r}'.format(key, self._kwargs[key]) for key in sorted(self._kwargs)]
        return '{}({})'.format(self._name, ','.join(formatted_props))

    def __hash__(self):
        return int(self._hash, base=16)

    def __eq__(self, other):
        return self._hash == other._hash

    def _update_hash(self):
        props = {'args': self._args, 'kwargs': self._kwargs}
        my_hash = hashlib.md5(json.dumps(props, sort_keys=True).encode('utf-8')).hexdigest()
        parent_hashes = [parent._hash for parent in self._parents]
        hashes = parent_hashes + [my_hash]
        self._hash = hashlib.md5(','.join(hashes).encode('utf-8')).hexdigest()


class InputNode(Node):
    """InputNode type"""
    def __init__(self, name, *args, **kwargs):
        super(InputNode, self).__init__(parents=[], name=name, *args, **kwargs)


class FilterNode(Node):
    """FilterNode"""
    def _get_filter(self):
        params_text = self._name
        arg_params = ['{}'.format(arg) for arg in self._args]
        kwarg_params = ['{}={}'.format(k, self._kwargs[k]) for k in sorted(self._kwargs)]
        params = arg_params + kwarg_params
        if params:
            params_text += '={}'.format(':'.join(params))
        return params_text


class OutputNode(Node):
    """OutputNode"""
    pass


class GlobalNode(Node):
    def __init__(self, parent, name, *args, **kwargs):
        assert isinstance(parent, OutputNode), 'Global nodes can only be attached after output nodes'
        super(GlobalNode, self).__init__([parent], name, *args, **kwargs)


def operator(node_classes={Node}, name=None):
    def decorator(func):
        func_name = name or func.__name__
        [setattr(node_class, func_name, func) for node_class in node_classes]
        return func
    return decorator

@@ -208,36 +208,36 @@
 t_hashes = %5B
+hash(
 parent
-._hash
+)
  for parent 
@@ -398,16 +398,42 @@
 parents%0A
+        self._hash = None%0A
         
@@ -509,36 +509,8 @@
 args
-%0A        self._update_hash()
 %0A%0A  
@@ -807,39 +807,88 @@
     
-return int(self._hash, base=16)
+if self._hash is None:%0A            self._update_hash()%0A        return self._hash
 %0A%0A  
@@ -933,33 +933,33 @@
 urn 
-self._hash == other._hash
+hash(self) == hash(other)
 %0A%0A  
@@ -1057,30 +1057,20 @@
     
-my_hash = hashlib.md5(
+props_str = 
 json
@@ -1106,32 +1106,72 @@
 .encode('utf-8')
+%0A        my_hash = hashlib.md5(props_str
 ).hexdigest()%0A  
@@ -1197,20 +1197,25 @@
  = %5B
-parent._hash
+str(hash(parent))
  for
@@ -1295,33 +1295,21 @@
     
-self._hash = hashlib.md5(
+hashes_str = 
 ','.
@@ -1336,28 +1336,114 @@
 'utf-8')
-).hexdigest(
+%0A        hash_str = hashlib.md5(hashes_str).hexdigest()%0A        self._hash = int(hash_str, base=16
 )%0A%0A%0Aclas

Clean up hashing