Source

pysyck / lib / syck / dumpers.py

"""
syck.dumpers is a high-level wrapper for the Syck YAML emitter.
Do not use it directly, use the module 'syck' instead.
"""

import _syck

try:
    import cStringIO as StringIO
except ImportError:
    import StringIO

__all__ = ['GenericDumper', 'Dumper',
    'emit', 'dump', 'emit_documents', 'dump_documents']

class GenericDumper(_syck.Emitter):
    """
    GenericDumper dumps native Python objects into YAML documents.
    """

    def dump(self, object):
        """Dumps the given Python object as a YAML document."""
        self.emit(self._convert(object, {}))

    def _convert(self, object, object_to_node):
        if id(object) in object_to_node and self.allow_aliases(object):
            return object_to_node[id(object)][1]
        node = self.represent(object)
        object_to_node[id(object)] = object, node
        if node.kind == 'seq':
            for index in range(len(node.value)):
                item = node.value[index]
                node.value[index] = self._convert(item, object_to_node)
        elif node.kind == 'map':
            if isinstance(node.value, dict):
                for key in node.value.keys():
                    value = node.value[key]
                    del node.value[key]
                    node.value[self._convert(key, object_to_node)] =    \
                            self._convert(value, object_to_node)
            elif isinstance(node.value, list):
                for index in range(len(node.value)):
                    key, value = node.value[index]
                    node.value[index] = (self._convert(key, object_to_node),
                            self._convert(value, object_to_node))
#        # Workaround against a Syck bug:
#        if node.kind == 'scalar' and node.style not in ['1quote', '2quote'] \
#                and node.value and node.value[-1] in [' ', '\t']:
#            node.style = '2quote'
        return node

    def represent(self, object):
        """Represents the given Python object as a 'Node'."""
        if isinstance(object, dict):
            return _syck.Map(object.copy(), tag="tag:yaml.org,2002:map")
        elif isinstance(object, list):
            return _syck.Seq(object[:], tag="tag:yaml.org,2002:seq")
        else:
            return _syck.Scalar(str(object), tag="tag:yaml.org,2002:str")

    def allow_aliases(self, object):
        """Checks whether the given object can be aliased."""
        return True

class Dumper(GenericDumper):
    """
    Dumper dumps native Python objects into YAML documents.
    """

    INF = 1e300000
    inf_value = repr(INF)
    neginf_value = repr(-INF)
    nan_value = repr(INF/INF)

    def find_representer(self, object):
        """
        For the given object, find a method that can represent it as a 'Node'
        object.

        If the type of the object has the form 'package.module.type',
        find_representer() returns the method 'represent_package_module_type'.
        If this method does not exist, it checks the base types.
        """
        for object_type in type(object).__mro__:
            if object_type.__module__ == '__builtin__':
                name = object_type.__name__
            else:
                name = '%s.%s' % (object_type.__module__, object_type.__name__)
            method = 'represent_' + name.replace('.', '_')
            if hasattr(self, method):
                return getattr(self, method)

    def represent(self, object):
        """Represents the given Python object as a 'Node'."""
        representer = self.find_representer(object)
        if representer:
            return representer(object)
        else:
            return super(Dumper, self).represent(object)

    def represent_object(self, object):
        return _syck.Scalar(repr(object), tag="tag:yaml.org,2002:str")

    def represent_NoneType(self, object):
        return _syck.Scalar('~', tag="tag:yaml.org,2002:null")

    def represent_bool(self, object):
        return _syck.Scalar(repr(object), tag="tag:yaml.org,2002:bool")

    def represent_str(self, object):
        try:
            return _syck.Scalar(object.encode('ascii'), tag="tag:yaml.org,2002:str")
        except UnicodeDecodeError:
            try:
                return _syck.Scalar(unicode(object, 'utf-8').encode('utf-8'),
                        tag="tag:python.yaml.org,2002:str")
            except UnicodeDecodeError:
                return _syck.Scalar(object.encode('base64'),
                        tag="tag:yaml.org,2002:binary")

    def represent_unicode(self, object):
        try:
            return _syck.Scalar(object.encode('ascii'), tag="tag:python.yaml.org,2002:unicode")
        except UnicodeEncodeError:
            return _syck.Scalar(object.encode('utf-8'), tag="tag:yaml.org,2002:str")

    def represent_list(self, object):
        return _syck.Seq(object[:], tag="tag:yaml.org,2002:seq")

    def represent_dict(self, object):
        return _syck.Map(object.copy(), tag="tag:yaml.org,2002:map")

    def represent_int(self, object):
        return _syck.Scalar(repr(object), tag="tag:yaml.org,2002:int")

    def represent_float(self, object):
        value = repr(object)
        if value == self.inf_value:
            value = '.inf'
        elif value == self.neginf_value:
            value = '-.inf'
        elif value == self.nan_value:
            value = '.nan'
        return _syck.Scalar(value, tag="tag:yaml.org,2002:float")

    def represent_complex(self, object):
        if object.real != 0.0:
            value = '%s+%sj' % (repr(object.real), repr(object.imag))
        else:
            value = '%sj' % repr(object.imag)
        return _syck.Scalar(value, tag="tag:python.yaml.org,2002:complex")

    def represent_sets_Set(self, object):
        return _syck.Seq(list(object), tag="tag:yaml.org,2002:set")
    represent_set = represent_sets_Set

    def represent_datetime_datetime(self, object):
        return _syck.Scalar(object.isoformat(), tag="tag:yaml.org,2002:timestamp")

    def represent_long(self, object):
        return _syck.Scalar(repr(object), tag="tag:python.yaml.org,2002:long")

    def represent_tuple(self, object):
        return _syck.Seq(list(object), tag="tag:python.yaml.org,2002:tuple")

    def represent_type(self, object):
        name = '%s.%s' % (object.__module__, object.__name__)
        return _syck.Scalar('', tag="tag:python.yaml.org,2002:name:"+name)
    represent_classobj = represent_type
    represent_class = represent_type
    # TODO: Python 2.2 does not provide the module name of a function
    represent_function = represent_type
    represent_builtin_function_or_method = represent_type

    def represent_module(self, object):
        return _syck.Scalar('', tag="tag:python.yaml.org,2002:module:"+object.__name__)

    def represent_instance(self, object):
        cls = object.__class__
        class_name = '%s.%s' % (cls.__module__, cls.__name__)
        args = ()
        state = {}
        if hasattr(object, '__getinitargs__'):
            args = object.__getinitargs__()
        if hasattr(object, '__getstate__'):
            state = object.__getstate__()
        elif not hasattr(object, '__getinitargs__'):
            state = object.__dict__.copy()
        if not args and isinstance(state, dict):
            return _syck.Map(state.copy(),
                    tag="tag:python.yaml.org,2002:object:"+class_name)
        value = {}
        if args:
            value['args'] = list(args)
        if state or not isinstance(state, dict):
            value['state'] = state
        return _syck.Map(value,
                tag="tag:python.yaml.org,2002:new:"+class_name)

    def represent_object(self, object): # Do you understand this? I don't.
        cls = type(object)
        class_name = '%s.%s' % (cls.__module__, cls.__name__)
        args = ()
        state = {}
        if cls.__reduce__ is type.__reduce__:
            if hasattr(object, '__reduce_ex__'):
                reduce = object.__reduce_ex__(2)
                args = reduce[1][1:]
            else:
                reduce = object.__reduce__()
            if len(reduce) > 2:
                state = reduce[2]
            if state is None:
                state = {}
            if not args and isinstance(state, dict):
                return _syck.Map(state.copy(),
                        tag="tag:python.yaml.org,2002:object:"+class_name)
            if not state and isinstance(state, dict):
                return _syck.Seq(list(args),
                        tag="tag:python.yaml.org,2002:new:"+class_name)
            value = {}
            if args:
                value['args'] = list(args)
            if state or not isinstance(state, dict):
                value['state'] = state
            return _syck.Map(value,
                    tag="tag:python.yaml.org,2002:new:"+class_name)
        else:
            reduce = object.__reduce__()
            cls = reduce[0]
            class_name = '%s.%s' % (cls.__module__, cls.__name__)
            args = reduce[1]
            state = None
            if len(reduce) > 2:
                state = reduce[2]
            if state is None:
                state = {}
            if not state and isinstance(state, dict):
                return _syck.Seq(list(args),
                        tag="tag:python.yaml.org,2002:apply:"+class_name)
            value = {}
            if args:
                value['args'] = list(args)
            if state or not isinstance(state, dict):
                value['state'] = state
            return _syck.Map(value,
                    tag="tag:python.yaml.org,2002:apply:"+class_name)

    def represent__syck_Node(self, object):
        object_type = type(object)
        type_name = '%s.%s' % (object_type.__module__, object_type.__name__)
        state = []
        if hasattr(object_type, '__slotnames__'):
            for name in object_type.__slotnames__:
                value = getattr(object, name)
                if value:
                    state.append((name, value))
        return _syck.Map(state,
                tag="tag:python.yaml.org,2002:object:"+type_name)

    def allow_aliases(self, object):
        """Checks whether the given object can be aliased."""
        if object is None or type(object) in [int, bool, float]:
            return False
        if type(object) is str and (not object or object.isalnum()):
            return False
        if type(object) is tuple and not object:
            return False
        return True

def emit(node, output=None, Dumper=Dumper, **parameters):
    """
    Emits the given node to the output.

    If output is None, returns the produced YAML document.
    """
    if output is None:
        dumper = Dumper(StringIO.StringIO(), **parameters)
    else:
        dumper = Dumper(output, **parameters)
    dumper.emit(node)
    if output is None:
        return dumper.output.getvalue()

def dump(object, output=None, Dumper=Dumper, **parameters):
    """
    Dumps the given object to the output.

    If output is None, returns the produced YAML document.
    """
    if output is None:
        dumper = Dumper(StringIO.StringIO(), **parameters)
    else:
        dumper = Dumper(output, **parameters)
    dumper.dump(object)
    if output is None:
        return dumper.output.getvalue()

def emit_documents(nodes, output=None, Dumper=Dumper, **parameters):
    """
    Emits the list of nodes to the output.
    
    If output is None, returns the produced YAML document.
    """
    if output is None:
        dumper = Dumper(StringIO.StringIO(), **parameters)
    else:
        dumper = Dumper(output, **parameters)
    for node in nodes:
        dumper.emit(node)
    if output is None:
        return dumper.output.getvalue()

def dump_documents(objects, output=None, Dumper=Dumper, **parameters):
    """
    Dumps the list of objects to the output.
    
    If output is None, returns the produced YAML document.
    """
    if output is None:
        dumper = Dumper(StringIO.StringIO(), **parameters)
    else:
        dumper = Dumper(output, **parameters)
    for object in objects:
        dumper.dump(object)
    if output is None:
        return dumper.output.getvalue()