﻿#!/usr/bin/python
# -*- coding: utf-8 -*-
# yamlrpcbasic.py
# Copyright (c) Peter Murphy 2007.
# Contains classes to represent procedure calls, return values, and errors.
#
# The classes here are designed for some compatibility with the JSON-RPC
# Draft Specification:
# http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html
#
# However, there are some differences.
#
# (a) Objects in YAML-RPC are given explicit tags, unlike JSON-RPC:
# (a.1) YAML RPC Calls are shown by the tag !yamlrpccall.
# (a.2) YAML RPC Returns are shown by the tag !yamlrpcret.
# (a.3) YAML RPC Errors are shown by the tag !yamlrpcerr.
# (a.4) Service Descriptions are shown by the tag !yamlrpcservdesc.
# (a.5) Procedure Descriptions are shown by the tag !yamlrpcspd.
# (a.6) Parameter Descriptions are shown by the tag !yamlrpcppd.
#
# (b) The type member of the Procedure Parameter Description should be a valid
# YAML tag, or a sequence of them. For example, if the type is "!!int", expect
# an integer. This is different from JSON-RPC, which has a delimited number of
# return types.
# 
# (c) In the JSON-RPC specification, Procedure Descriptions have a parameter
# called "return" which indicates the type of objects returned from a routine.
# In adapting this to YAML, I made some changes. Firstly, I renamed the
# parameter as "returntype". It makes it easier to code in Python (as "return"
# is a keyword). Secondly, values should be YAML tags, not procedure parameter
# descriptions. 
#
# (d) Other differences between YAML-RPC and JSON-RPC come from the differences
# in languages. Unlike JSON-RPC:
# (d.1) YAML-RPC permits objects to be referenced multiple times.
# (d.2) YAML-RPC permits cyclic links.
# (d.3) Typing can be set for objects by tags.
# (d.4) YAML-RPC allows information to be structured that may not be 
# representable in Python (or anywhere else). For example, a "black hat" may
# decide to break a Python installation by coding YAML like this, and sending
# it to a RPC-Server:
#
# --- !yamlrpccall
# id: null
# method: sum
# params: [{{key_is: a_map}: unrepresentable_in_Python}] # Danger! 
# version: '0.1'
#
# THE PRECEEDING IS SUBJECT TO CHANGE (AND CONSENSUS).

import operator;
import yaml;
from xmlrpclib import Binary;

# For YAML_RPC, we treat the Python unicode type as "!!str" as well.

def represent_unicode(dumper, data):
    return dumper.represent_scalar("tag:yaml.org,200:str", data);
def construct_unicode(loader, node):
    return unicode(loader.construct_scalar(node));
    
    
yaml.add_representer(unicode, represent_unicode);
yaml.add_constructor("tag:yaml.org,200:str", construct_unicode);


class YAMLRPCCall(yaml.YAMLObject):
    """ This class represents YAML-RPC procedure calls. """
    yaml_tag = u'!yamlrpccall'
    def __init__(self, id, version, method, params):
        self.id = id
        self.version = version
        self.method = method
        self.params = params
        
    def __repr__(self):
        theid = getattr(self, "id", None);
        theparams = getattr(self, "params", None);
        return "%s(id=%r, version=%r, method=%r, params=%r)" % (
            self.__class__.__name__, theid, self.version, self.method, theparams)

class YAMLRPCReturn(yaml.YAMLObject):
    """ This class represents YAML-RPC return objects. """
    yaml_tag = u'!yamlrpcret'
    def __init__(self, id, version, result, error):
        self.id = id
        self.version = version
        self.result = result
        self.error = error
        
    def __repr__(self):
        theid = getattr(self, "id", None);
        theresult = getattr(self, "result", None);
        theerror = getattr(self, "error", None);
        return "%s(id=%r, version=%r, result=%r, error=%r)" % (
            self.__class__.__name__, theid, self.version, theresult, theerror)

class YAMLRPCError(yaml.YAMLObject):
    """ This class represents YAML-RPC error objects. """
    yaml_tag = u'!yamlrpcerr'
    def __init__(self, name, code, message, error):
        self.name = name
        self.code = code
        self.message = message
        self.error = error
        
    def __repr__(self):
        theerror = getattr(self, "error", None);
        return "%s(name=%r, code=%r, message=%r, error=%r)" % (
            self.__class__.__name__, self.name, self.code, self.message, theerror)

class YAMLRPCServDesc(yaml.YAMLObject):
    """ This class represents YAML-RPC service desciptions. """
    yaml_tag = u'!yamlrpcservdesc'
    def __init__(self, sdversion, name, id, version, summary, help, 
            address, procs):
        self.sdversion = sdversion;
        self.name = name
        self.id = id
        self.version = version
        self.summary = summary
        self.help = help
        self.address = address
        self.procs = procs
        
    def __repr__(self):
        theversion = getattr(self, "version", None);
        thesummary = getattr(self, "summary", None);
        thehelp = getattr(self, "help", None);
        theaddress = getattr(self, "address", None);
        theprocs = getattr(self, "procs", None);
        
        return "%s(sdversion=%r, name=%r, id=%r, version=%r, summary=%r, \
            help=%r, address=%r, procs=%r)" % (
            self.__class__.__name__, self.sdversion, self.name, self.id, 
            theversion, thesummary, thehelp, theaddress, theprocs)

class YAMLRPCSPD(yaml.YAMLObject):
    """ This class represents YAML-RPC service procedure desciptions. """
    yaml_tag = u'!yamlrpcspd'
    def __init__(self, name, summary, help, idempotent, params, returntype):
        self.name = name
        self.summary = summary
        self.help = help
        self.idempotent = idempotent
        self.params = params
        self.returntype = returntype
        
    def __repr__(self):
        thesummary = getattr(self, "summary", None);
        thehelp = getattr(self, "help", None);
        theidempotent = getattr(self, "idempotent", None);
        theparams = getattr(self, "params", None);
        thereturntype = getattr(self, "returntype", None);
        
        return "%s(name=%r, summary=%r, help=%r, idempotent=%r, params=%r, returntype=%r)" % (
            self.__class__.__name__, self.name, thesummary, thehelp, 
            theidempotent, theparams, thereturntype)

class YAMLRPCPPD(yaml.YAMLObject):
    """ This class represents YAML-RPC procedure parameter descriptions. """
    yaml_tag = u'!yamlrpcppd'
    def __init__(self, name, type):
        self.name = name
        self.type = type
        
    def __repr__(self):
        thetype = getattr(self, "type", None);
        return "%s(name=%r, type=%r)" % (
            self.__class__.__name__, self.name, thetype)


def getAttrWay(obj, name, default = None):
    """ If obj is a map, return obj[name]. If obj is an object, gets obj.name. 
        If neither exists, provides default as a return value.
    """
    if operator.isMappingType(obj):
        ourdict = obj;
    else:
        ourdict = obj.__dict__;
    if ourdict.has_key(name):
        return ourdict[name];
    return default;



if __name__ == "__main__":
    thisvers = u"0.1"
    thiserror = u"YAMLRPCError";
    
    OurProc = YAMLRPCCall(None, thisvers, u"sum", [1, 2]);
    print operator.isMappingType(OurProc);
    print OurProc;
    OurProcDump = yaml.dump(OurProc);
    print OurProcDump;
    OurProcAgain = yaml.load(OurProcDump);
    print OurProcAgain;

    OurProcSucc = YAMLRPCReturn(None, thisvers, 3, None);
    print OurProcSucc;
    OurProcSuccDump = yaml.dump(OurProcSucc);
    print OurProcSuccDump;
    OurProcSuccAgain = yaml.load(OurProcSuccDump);
    print OurProcSuccAgain;
    
    OurProcError = YAMLRPCError(u"YAMLRPCError", 123, u"Python Error", None);
    OurProcFail = YAMLRPCReturn(None, thisvers, None, OurProcError);
    print OurProcFail;
    OurProcFailDump = yaml.dump(OurProcFail);
    print OurProcFailDump;
    OurProcFailAgain = yaml.load(OurProcFailDump);
    print OurProcFailAgain;
    

    YAMLSum = YAMLRPCSPD("sum", "Sums two numbers.",
        "http://www.example.com/service/sum.html", True,
        [YAMLRPCPPD("a", "!!int"), YAMLRPCPPD("b", "!!int")],
        "!!int");
    YAMLTime = YAMLRPCSPD("time", "Returns the current date and time.",
        "http://www.example.com/service/time.html", False, None, 
        "!!timestamp");
    OurServDesc =YAMLRPCServDesc("1.0", "DemoService", 
        "urn:uuid:41544946-415a-495a-5645-454441534646", thisvers, 
        "A simple demonstration service.",
        "http://www.example.com/service/index.html",
        "http://www.example.com/service",
        [YAMLSum, YAMLTime]); 
    print OurServDesc;
    OurServDescDump = yaml.dump(OurServDesc);
    print OurServDescDump;
    OurServDescAgain = yaml.load(OurServDescDump);
    print OurServDescAgain;

 
    OurMap = {"version": thisvers, "method":"sum", "params": [1,2]};
    print getAttrWay(OurMap, "id", 1);
    print getAttrWay(OurMap, "method", 2); 
    print getAttrWay(OurProc, "idi", 1);
    print getAttrWay(OurProc, "method", 2); 
