Ticket #45: yamlrpcbasic.py

File yamlrpcbasic.py, 9.0 kB (added by pkmurphy at postmaster dot co dot uk, 2 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
51 import operator;
52 import yaml;
53 from xmlrpclib import Binary;
54
55 # For YAML_RPC, we treat the Python unicode type as "!!str" as well.
56
57 def represent_unicode(dumper, data):
58     return dumper.represent_scalar("tag:yaml.org,200:str", data);
59 def construct_unicode(loader, node):
60     return unicode(loader.construct_scalar(node));
61    
62    
63 yaml.add_representer(unicode, represent_unicode);
64 yaml.add_constructor("tag:yaml.org,200:str", construct_unicode);
65
66
67 class 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
82 class 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
98 class 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
112 class 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
138 class 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
160 class 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
173 def 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
187 if __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);