import yaml
from test import assertEquals
from here import flushLeft
from math import sqrt

"""
DUMPING OBJECTS: > 
    YAML has several ways of persisting objects.  One view of an 
    object is that it's nothing more than it's dictionary.  By 
    default, YAML emits self.__dict__ from objects.
"""

class SimpleObject:
    def __init__(self):
        self.x = 100
        self.y = 20
    
    def xyz(self):
        return self.x + self.y + self.z

def testDumpingDictionary():
    obj = SimpleObject()
    obj.z = 3
    output = yaml.dump(obj)
    expected = simpleObjectYaml()
    assertEquals(output, expected)

def simpleObjectYaml():
    return flushLeft("""
        --- !!__main__.SimpleObject
        x: 100
        y: 20
        z: 3
        """)
    

testDumpingDictionary()

"""
LOADING OBJECTS: > 
    Of course, we would expect to be able to load that
    YAML right back into Python, and we can.  You get 
    back an object of the intended class, with all of 
    the normal methods.  Be aware, though, that if your 
    class creates methods on the fly, or if it does 
    other trickery, then you might get unexpected results.
"""

def testLoadingDictionary():
    obj = yaml.load(simpleObjectYaml()).next()
    assertEquals(obj.xyz(), 123)

testLoadingDictionary()

"""
CUSTOM DUMPING: >
    Some times you want more control over how you dump
    objects in YAML.  You might not want to dump all 
    members of the object, for example.  Also, you may 
    not want to export the module name.
"""

class Triangle:
    def __init__(self,x,y):
        self.x = x
        self.y = y
        self._hypotneuse = sqrt(x*x + y*y)

    def to_yaml(self):
        # hide the hypotneuse attribute, it's private;
        # also use inches instead of feet
        view = {
            'x_inches': self.x * 12,
            'y_inches': self.y * 12
        }
        return (view, '!!triangle_in_inches')

def testDumpTriangle():
    triangle = Triangle(3,4)
    assertEquals(yaml.dump(triangle), triangleYaml())

def triangleYaml():
    return flushLeft("""
        --- !!triangle_in_inches
        x_inches: 36
        y_inches: 48
        """)

testDumpTriangle()

"""
CUSTOM LOADING: >
    You have control over the YAML load process too.  Normally
    YAML resolves private types for you automatically, but you
    can override its behavior.
"""

class MyResolver:
    def resolveType(self, data, typestring):
        if typestring == '!!triangle_in_inches':
            x = data['x_inches'] / 12
            y = data['y_inches'] / 12
            return Triangle(x,y)
        else:
            raise 'Private type %s not supported' % typestring

def testCustomLoad():
    obj = yaml.load(triangleYaml(),MyResolver()).next()
    assertEquals(obj._hypotneuse, 5.0)

testCustomLoad()

"""
USING FROM_YAML(): >
    Another way to customize the YAML loading process is to 
    supply a from_yaml() method.  For example, you might want
    the loading of an object to have some kind of a side effect.
    Or, you may need to calculate some value that is not part of 
    the attribute.
"""

class TestConfig:
    lastTester = None
    def from_yaml(self, args):
        self.tester = args['tester']
        self.hitcount = args['hitcount'] + 1
        TestConfig.lastTester = self.tester
        return self

def testFromYaml():
    yamlData = flushLeft("""
        --- !!__main__.TestConfig
        tester: showell
        hitcount: 42
        """)
    obj = yaml.load(yamlData).next()
    assertEquals(obj.hitcount, 43)
    assertEquals(TestConfig.lastTester, 'showell')

testFromYaml()
