Ticket #45: yamlrpcbasic.py

File yamlrpcbasic.py, 9.0 KB (added by pkmurphy at postmaster dot co dot uk, 7 years ago)

Base classes

Line 
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3# yamlrpcbasic.py
4# Copyright (c) Peter Murphy 2007.
5# Contains classes to represent procedure calls, return values, and errors.
6#
7# The classes here are designed for some compatibility with the JSON-RPC
8# Draft Specification:
9# http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html
10#
11# However, there are some differences.
12#
13# (a) Objects in YAML-RPC are given explicit tags, unlike JSON-RPC:
14# (a.1) YAML RPC Calls are shown by the tag !yamlrpccall.
15# (a.2) YAML RPC Returns are shown by the tag !yamlrpcret.
16# (a.3) YAML RPC Errors are shown by the tag !yamlrpcerr.
17# (a.4) Service Descriptions are shown by the tag !yamlrpcservdesc.
18# (a.5) Procedure Descriptions are shown by the tag !yamlrpcspd.
19# (a.6) Parameter Descriptions are shown by the tag !yamlrpcppd.
20#
21# (b) The type member of the Procedure Parameter Description should be a valid
22# YAML tag, or a sequence of them. For example, if the type is "!!int", expect
23# an integer. This is different from JSON-RPC, which has a delimited number of
24# return types.
25#
26# (c) In the JSON-RPC specification, Procedure Descriptions have a parameter
27# called "return" which indicates the type of objects returned from a routine.
28# In adapting this to YAML, I made some changes. Firstly, I renamed the
29# parameter as "returntype". It makes it easier to code in Python (as "return"
30# is a keyword). Secondly, values should be YAML tags, not procedure parameter
31# descriptions.
32#
33# (d) Other differences between YAML-RPC and JSON-RPC come from the differences
34# in languages. Unlike JSON-RPC:
35# (d.1) YAML-RPC permits objects to be referenced multiple times.
36# (d.2) YAML-RPC permits cyclic links.
37# (d.3) Typing can be set for objects by tags.
38# (d.4) YAML-RPC allows information to be structured that may not be
39# representable in Python (or anywhere else). For example, a "black hat" may
40# decide to break a Python installation by coding YAML like this, and sending
41# it to a RPC-Server:
42#
43# --- !yamlrpccall
44# id: null
45# method: sum
46# params: [{{key_is: a_map}: unrepresentable_in_Python}] # Danger!
47# version: '0.1'
48#
49# THE PRECEEDING IS SUBJECT TO CHANGE (AND CONSENSUS).
50
51import operator;
52import yaml;
53from xmlrpclib import Binary;
54
55# For YAML_RPC, we treat the Python unicode type as "!!str" as well.
56
57def represent_unicode(dumper, data):
58    return dumper.represent_scalar("tag:yaml.org,200:str", data);
59def construct_unicode(loader, node):
60    return unicode(loader.construct_scalar(node));
61   
62   
63yaml.add_representer(unicode, represent_unicode);
64yaml.add_constructor("tag:yaml.org,200:str", construct_unicode);
65
66
67class YAMLRPCCall(yaml.YAMLObject):
68    """ This class represents YAML-RPC procedure calls. """
69    yaml_tag = u'!yamlrpccall'
70    def __init__(self, id, version, method, params):
71        self.id = id
72        self.version = version
73        self.method = method
74        self.params = params
75       
76    def __repr__(self):
77        theid = getattr(self, "id", None);
78        theparams = getattr(self, "params", None);
79        return "%s(id=%r, version=%r, method=%r, params=%r)" % (
80            self.__class__.__name__, theid, self.version, self.method, theparams)
81
82class YAMLRPCReturn(yaml.YAMLObject):
83    """ This class represents YAML-RPC return objects. """
84    yaml_tag = u'!yamlrpcret'
85    def __init__(self, id, version, result, error):
86        self.id = id
87        self.version = version
88        self.result = result
89        self.error = error
90       
91    def __repr__(self):
92        theid = getattr(self, "id", None);
93        theresult = getattr(self, "result", None);
94        theerror = getattr(self, "error", None);
95        return "%s(id=%r, version=%r, result=%r, error=%r)" % (
96            self.__class__.__name__, theid, self.version, theresult, theerror)
97
98class YAMLRPCError(yaml.YAMLObject):
99    """ This class represents YAML-RPC error objects. """
100    yaml_tag = u'!yamlrpcerr'
101    def __init__(self, name, code, message, error):
102        self.name = name
103        self.code = code
104        self.message = message
105        self.error = error
106       
107    def __repr__(self):
108        theerror = getattr(self, "error", None);
109        return "%s(name=%r, code=%r, message=%r, error=%r)" % (
110            self.__class__.__name__, self.name, self.code, self.message, theerror)
111
112class YAMLRPCServDesc(yaml.YAMLObject):
113    """ This class represents YAML-RPC service desciptions. """
114    yaml_tag = u'!yamlrpcservdesc'
115    def __init__(self, sdversion, name, id, version, summary, help, 
116            address, procs):
117        self.sdversion = sdversion;
118        self.name = name
119        self.id = id
120        self.version = version
121        self.summary = summary
122        self.help = help
123        self.address = address
124        self.procs = procs
125       
126    def __repr__(self):
127        theversion = getattr(self, "version", None);
128        thesummary = getattr(self, "summary", None);
129        thehelp = getattr(self, "help", None);
130        theaddress = getattr(self, "address", None);
131        theprocs = getattr(self, "procs", None);
132       
133        return "%s(sdversion=%r, name=%r, id=%r, version=%r, summary=%r, \
134            help=%r, address=%r, procs=%r)" % (
135            self.__class__.__name__, self.sdversion, self.name, self.id, 
136            theversion, thesummary, thehelp, theaddress, theprocs)
137
138class YAMLRPCSPD(yaml.YAMLObject):
139    """ This class represents YAML-RPC service procedure desciptions. """
140    yaml_tag = u'!yamlrpcspd'
141    def __init__(self, name, summary, help, idempotent, params, returntype):
142        self.name = name
143        self.summary = summary
144        self.help = help
145        self.idempotent = idempotent
146        self.params = params
147        self.returntype = returntype
148       
149    def __repr__(self):
150        thesummary = getattr(self, "summary", None);
151        thehelp = getattr(self, "help", None);
152        theidempotent = getattr(self, "idempotent", None);
153        theparams = getattr(self, "params", None);
154        thereturntype = getattr(self, "returntype", None);
155       
156        return "%s(name=%r, summary=%r, help=%r, idempotent=%r, params=%r, returntype=%r)" % (
157            self.__class__.__name__, self.name, thesummary, thehelp, 
158            theidempotent, theparams, thereturntype)
159
160class YAMLRPCPPD(yaml.YAMLObject):
161    """ This class represents YAML-RPC procedure parameter descriptions. """
162    yaml_tag = u'!yamlrpcppd'
163    def __init__(self, name, type):
164        self.name = name
165        self.type = type
166       
167    def __repr__(self):
168        thetype = getattr(self, "type", None);
169        return "%s(name=%r, type=%r)" % (
170            self.__class__.__name__, self.name, thetype)
171
172
173def getAttrWay(obj, name, default = None):
174    """ If obj is a map, return obj[name]. If obj is an object, gets obj.name.
175        If neither exists, provides default as a return value.
176    """
177    if operator.isMappingType(obj):
178        ourdict = obj;
179    else:
180        ourdict = obj.__dict__;
181    if ourdict.has_key(name):
182        return ourdict[name];
183    return default;
184
185
186
187if __name__ == "__main__":
188    thisvers = u"0.1"
189    thiserror = u"YAMLRPCError";
190   
191    OurProc = YAMLRPCCall(None, thisvers, u"sum", [1, 2]);
192    print operator.isMappingType(OurProc);
193    print OurProc;
194    OurProcDump = yaml.dump(OurProc);
195    print OurProcDump;
196    OurProcAgain = yaml.load(OurProcDump);
197    print OurProcAgain;
198
199    OurProcSucc = YAMLRPCReturn(None, thisvers, 3, None);
200    print OurProcSucc;
201    OurProcSuccDump = yaml.dump(OurProcSucc);
202    print OurProcSuccDump;
203    OurProcSuccAgain = yaml.load(OurProcSuccDump);
204    print OurProcSuccAgain;
205   
206    OurProcError = YAMLRPCError(u"YAMLRPCError", 123, u"Python Error", None);
207    OurProcFail = YAMLRPCReturn(None, thisvers, None, OurProcError);
208    print OurProcFail;
209    OurProcFailDump = yaml.dump(OurProcFail);
210    print OurProcFailDump;
211    OurProcFailAgain = yaml.load(OurProcFailDump);
212    print OurProcFailAgain;
213   
214
215    YAMLSum = YAMLRPCSPD("sum", "Sums two numbers.",
216        "http://www.example.com/service/sum.html", True,
217        [YAMLRPCPPD("a", "!!int"), YAMLRPCPPD("b", "!!int")],
218        "!!int");
219    YAMLTime = YAMLRPCSPD("time", "Returns the current date and time.",
220        "http://www.example.com/service/time.html", False, None, 
221        "!!timestamp");
222    OurServDesc =YAMLRPCServDesc("1.0", "DemoService", 
223        "urn:uuid:41544946-415a-495a-5645-454441534646", thisvers, 
224        "A simple demonstration service.",
225        "http://www.example.com/service/index.html",
226        "http://www.example.com/service",
227        [YAMLSum, YAMLTime]); 
228    print OurServDesc;
229    OurServDescDump = yaml.dump(OurServDesc);
230    print OurServDescDump;
231    OurServDescAgain = yaml.load(OurServDescDump);
232    print OurServDescAgain;
233
234 
235    OurMap = {"version": thisvers, "method":"sum", "params": [1,2]};
236    print getAttrWay(OurMap, "id", 1);
237    print getAttrWay(OurMap, "method", 2); 
238    print getAttrWay(OurProc, "idi", 1);
239    print getAttrWay(OurProc, "method", 2);