from ..core import StructuredNode


class InflateConflict(Exception):
    def __init__(self, cls, key, value, nid):
        self.cls_name = cls.__name__
        self.property_name = key
        self.value = value
        self.nid = nid

    def __str__(self):
        return """Found conflict with node {0}, has property '{1}' with value '{2}'
            although class {3} already has a property '{1}'""".format(
            self.nid, self.property_name, self.value, self.cls_name)


class DeflateConflict(InflateConflict):
    def __init__(self, cls, key, value, nid):
        self.cls_name = cls.__name__
        self.property_name = key
        self.value = value
        self.nid = nid if nid else '(unsaved)'

    def __str__(self):
        return """Found trying to set property '{1}' with value '{2}' on node {0}
            although class {3} already has a property '{1}'""".format(
            self.nid, self.property_name, self.value, self.cls_name)


class SemiStructuredNode(StructuredNode):
    """
    A base class allowing properties to be stored on a node that aren't specified in it's definition.
    Conflicting properties are avoided through the DeflateConflict exception::

        class Person(SemiStructuredNode):
            name = StringProperty()
            age = IntegerProperty()

            def hello(self):
                print("Hi my names " + self.name)

        tim = Person(name='Tim', age=8, weight=11).save()
        tim.hello = "Hi"
        tim.save() # DeflateConflict
    """
    __abstract_node__ = True

    def __init__(self, *args, **kwargs):
        super(SemiStructuredNode, self).__init__(*args, **kwargs)

    @classmethod
    def inflate(cls, node):
        props = {}
        for key, prop in cls.defined_properties(aliases=False, rels=False).items():
            if key in node.properties:
                props[key] = prop.inflate(node.properties[key])
            elif prop.has_default:
                props[key] = prop.default_value()
            else:
                props[key] = None
        # handle properties not defined on the class
        for free_key in [key for key in node.properties if key not in props]:
            if hasattr(cls, free_key):
                raise InflateConflict(cls, free_key, node.properties[free_key], node._id)
            props[free_key] = node.properties[free_key]

        snode = cls(**props)
        snode._id = node._id
        return snode

    @classmethod
    def deflate(cls, node_props, obj=None):
        deflated = super(SemiStructuredNode, cls).deflate(node_props, obj)
        for key in [k for k in node_props if k not in deflated]:
            if hasattr(cls, key):
                raise DeflateConflict(cls, key, deflated[key], obj._id)
        node_props.update(deflated)
        return node_props

@@ -1691,24 +1691,162 @@
 cls, node):%0A
+        # support lazy loading%0A        if isinstance(node, int):%0A            snode = cls()%0A            snode._id = node%0A        else:%0A    
         prop
@@ -1852,16 +1852,20 @@
 ps = %7B%7D%0A
+    
         
@@ -1885,23 +1885,21 @@
  in cls.
-defined
+__all
 _propert
@@ -1905,45 +1905,16 @@
 ties
-(aliases=False, rels=False).items():%0A
+__:%0A    
     
@@ -1948,16 +1948,20 @@
 erties:%0A
+    
         
@@ -2014,18 +2014,28 @@
 ies%5Bkey%5D
-)%0A
+, node)%0A    
         
@@ -2061,16 +2061,20 @@
 efault:%0A
+    
         
@@ -2119,32 +2119,36 @@
 e()%0A            
+    
 else:%0A          
@@ -2145,32 +2145,36 @@
                 
+  
+  
 props%5Bkey%5D = Non
@@ -2175,16 +2175,20 @@
  = None%0A
+    
         
@@ -2236,24 +2236,28 @@
 ass%0A        
+   
+ 
 for free_key
@@ -2318,32 +2318,36 @@
 s%5D:%0A            
+    
 if hasattr(cls, 
@@ -2353,24 +2353,28 @@
  free_key):%0A
+    
             
@@ -2459,24 +2459,28 @@
             
+    
 props%5Bfree_k
@@ -2512,32 +2512,36 @@
 e_key%5D%0A%0A        
+    
 snode = cls(**pr
@@ -2545,32 +2545,36 @@
 *props)%0A        
+ 
+   
 snode._id = node
@@ -2578,16 +2578,17 @@
 ode._id%0A
+%0A
         
@@ -2659,16 +2659,34 @@
 obj=None
+, skip_empty=False
 ):%0A     
@@ -2753,16 +2753,39 @@
 ops, obj
+, skip_empty=skip_empty
 )%0A      

update SemiStructuredNode to be consistent with StructuredNode inflate/deflate