← Back to team overview

ladon-dev-team team mailing list archive

[Merge] lp:~roger-lp/ladon/trunk into lp:ladon

 

roger has proposed merging lp:~roger-lp/ladon/trunk into lp:ladon.

Requested reviews:
  Mikhus (mikhus)

For more details, see:
https://code.launchpad.net/~roger-lp/ladon/trunk/+merge/131028

Contains implementation of JSON-RPC 1.0 specification for request and response, notifications hasn't implemented yet. Also contains fix for python 3.3 __qualname__ (PEP 3155 -- Qualified name for classes and functions).
-- 
https://code.launchpad.net/~roger-lp/ladon/trunk/+merge/131028
Your team Ladon Developer is subscribed to branch lp:ladon.
=== modified file 'frameworks/python/src/ladon/interfaces/__init__.py'
--- frameworks/python/src/ladon/interfaces/__init__.py	2012-05-04 14:43:58 +0000
+++ frameworks/python/src/ladon/interfaces/__init__.py	2012-10-23 15:04:45 +0000
@@ -71,3 +71,4 @@
 import ladon.interfaces.soap
 import ladon.interfaces.soap11
 import ladon.interfaces.jsonwsp
+import ladon.interfaces.jsonrpc10

=== modified file 'frameworks/python/src/ladon/interfaces/base.py'
--- frameworks/python/src/ladon/interfaces/base.py	2012-01-06 12:44:37 +0000
+++ frameworks/python/src/ladon/interfaces/base.py	2012-10-23 15:04:45 +0000
@@ -81,6 +81,12 @@
 
 	def description_content_type(self):
 		return self._service_descriptor._content_type
+	
+	def get_passback_params(self,req_dict):
+		return self._request_handler.get_passback_params(req_dict)
+	
+	def add_passback_params(self,res_dict,passback_dict):
+		return self._fault_handler.add_passback_params(res_dict,passback_dict)
 
 class ServiceDescriptor(object):
 	
@@ -108,6 +114,9 @@
 	
 	def parse_request(self,req,sinfo,encoding):
 		return {}
+	
+	def get_passback_params(self,req_dict):
+		return {}
 
 
 class BaseResponseHandler(object):
@@ -127,6 +136,9 @@
 	
 	def build_response(self,method_result,sinfo,encoding):
 		return ''
+	
+	def add_passback_params(self,res_dict,passback_dict):
+		return res_dict
 
 class BaseFaultHandler(object):
 	
@@ -145,3 +157,6 @@
 	
 	def build_fault_response(self,exc,sinfo,methodname,encoding):
 		return ''
+	
+	def add_passback_params(self,res_dict,passback_dict):
+		return res_dict

=== added file 'frameworks/python/src/ladon/interfaces/jsonrpc10.py'
--- frameworks/python/src/ladon/interfaces/jsonrpc10.py	1970-01-01 00:00:00 +0000
+++ frameworks/python/src/ladon/interfaces/jsonrpc10.py	2012-10-23 15:04:45 +0000
@@ -0,0 +1,209 @@
+# -*- coding: utf-8 -*-
+
+from ladon.interfaces.base import BaseInterface,ServiceDescriptor,BaseRequestHandler,BaseResponseHandler,BaseFaultHandler
+from ladon.interfaces import expose
+from ladon.compat import type_to_jsontype,pytype_support,PORTABLE_STRING
+import json,sys,traceback
+from ladon.exceptions.service import ServiceFault
+from ladon.exceptions.base import LadonException
+
+def _add_passback_params(res_dict,passback_dict):
+	merged_dict = dict((k,v) for (k,v) in res_dict.items())	
+	for k,v in passback_dict.items():
+		merged_dict[k] = v
+	return merged_dict
+
+class RequestPropFault(ServiceFault):
+	def __init__(self,prop, passback_dict):
+		self.prop = prop
+		self.passback_dict = passback_dict
+		super(RequestPropFault,self).__init__('service','Request doesn\'t have "%s" property.' % self.prop, None, 2)
+
+	def __str__(self):
+		return self.faultstring
+
+class RequestParamFault(ServiceFault):
+	def __init__(self,param, passback_dict):
+		self.param = param
+		self.passback_dict = passback_dict
+		super(RequestParamFault,self).__init__('service','Request doesn\'t have "%s" parameter.' % self.param, None, 2)
+
+	def __str__(self):
+		return self.faultstring
+
+class JSONRPCServiceDescriptor(ServiceDescriptor):	
+	javascript_type_map = type_to_jsontype
+	version = '1.0'
+	_content_type = 'application/json'
+
+	def generate(self,servicename,servicenumber,typemanager,methodlist,service_url,encoding):
+		type_dict = typemanager.type_dict
+		type_order = typemanager.type_order
+
+		def map_type(typ):
+			if typ in JSONRPCServiceDescriptor.javascript_type_map:
+				return JSONRPCServiceDescriptor.javascript_type_map[typ]
+			else:
+				return typ.__name__
+
+		desc = {
+			'servicename': servicename,
+			'url': service_url,
+			'type': 'jsonrpc/description',
+			'version': self.version,
+			'types': {},
+			'methods': {}
+		}
+		
+		types = desc['types']
+		for typ in type_order:
+			if type(typ)==dict:
+				desc_type = {}
+				types[typ['name']] = desc_type
+				for k,v,props in typ['attributes']:
+					if type(v)==list:
+						desc_type_val = [map_type(v[0])]
+					else:
+						desc_type_val = map_type(v)
+					desc_type[k] = desc_type_val
+
+		methods = desc['methods']
+		for m in methodlist:
+			desc_mparams = {}
+			order = 1
+			desc_method = {'params': desc_mparams, 'doc_lines': m._method_doc}
+			methods[m.name()] = desc_method
+			for arg in m.args():
+				if [list,tuple].count(type(arg['type'])):
+					desc_param_type = [map_type(arg['type'][0])]
+				else:
+					desc_param_type = map_type(arg['type'])
+				desc_mparams[arg['name']] = {
+					"type": desc_param_type,
+					"def_order": order,
+					"optional": arg['optional'],
+					}
+				if 'doc' in arg:
+					desc_mparams[arg['name']]["doc_lines"] = arg['doc']
+				else:
+					desc_mparams[arg['name']]["doc_lines"] = []
+				order += 1
+				
+			if [list,tuple].count(type(m._rtype)):
+				desc_rtype = [map_type(m._rtype[0])]
+			else:
+				desc_rtype = map_type(m._rtype)
+			desc_method['ret_info'] = {
+				'type': desc_rtype,
+				'doc_lines': m._rtype_doc
+			}
+			
+		if sys.version_info[0]>=3:
+			return json.dumps(desc)
+		return json.dumps(desc,encoding=encoding)
+
+class JSONRPCRequestHandler(BaseRequestHandler):
+	def parse_request(self,json_body,sinfo,encoding):
+		def parse_number(x):
+			return PORTABLE_STRING(x)
+		def parse_constant(x):
+			if x=='null':
+				return PORTABLE_STRING("None")
+			return PORTABLE_STRING(x)
+		req_dict = json.loads(PORTABLE_STRING(json_body,encoding), parse_int=parse_number, parse_float=parse_number, \
+			parse_constant=parse_constant)
+		passback_dict = self.get_passback_params(req_dict)
+		if 'method' not in req_dict:
+			raise RequestPropFault('method',passback_dict)
+		if 'params' not in req_dict:
+			raise RequestPropFault('params',passback_dict)
+		if 'id' not in req_dict:
+			raise RequestPropFault('id',passback_dict)
+		minfo = sinfo.methods[req_dict['method']]
+		if (req_dict['params'] is None or len(req_dict['params']) == 0) and len(minfo.args()) > 0:
+			raise RequestParamFault(minfo.args()[0]['name'],passback_dict)
+		else:
+			for arg in minfo.args():
+				isgiven = False
+				for param in req_dict['params']:
+					if param == arg['name']:
+						isgiven = True
+				if not isgiven:
+					raise RequestParamFault(arg['name'],passback_dict)
+		req_dict['args'] = req_dict['params']
+		del req_dict['params']
+		return req_dict
+	
+	def get_passback_params(self, req_dict):
+		if 'id' in req_dict:
+			return {'id': req_dict['id']}
+		else:
+			return {}
+
+class JSONRPCResponseHandler(BaseResponseHandler):
+	_content_type = 'application/json'
+	_stringify_res_dict = False
+	
+	def build_response(self,res_dict,sinfo,encoding):
+		res_dict['error'] = None
+		del res_dict['servicenumber']
+		del res_dict['servicename']
+		del res_dict['method']
+		return json.dumps(res_dict,ensure_ascii=False).encode(encoding)
+	
+	def add_passback_params(self,res_dict,passback_dict):
+		return _add_passback_params(res_dict,passback_dict)
+
+class JSONRPCFaultHandler(BaseFaultHandler):
+	_content_type = 'application/json'
+	_stringify_res_dict = False
+	
+	def build_fault_response(self,service_exc,sinfo,methodname,encoding):
+		if service_exc.detail:
+			detail = service_exc.detail
+		else:
+			detail = traceback.format_exc()
+		detail = detail.replace('\r\n','\n').split('\n')
+		fault_dict = {
+			'result': None,
+			'error': {
+				'code': service_exc.faultcode,
+				'string': service_exc.faultstring,
+				'detail': detail,
+				'filename': service_exc.mod,
+				'lineno': service_exc.lineno
+			},
+		}
+		if hasattr(service_exc,'passback_dict'):
+			fault_dict = self.add_passback_params(fault_dict,service_exc.passback_dict)
+		return json.dumps(fault_dict,ensure_ascii=False).encode(encoding)
+	
+	def add_passback_params(self, res_dict, passback_dict):
+		return _add_passback_params(res_dict,passback_dict)
+
+@expose
+class JSONRPCInterface(BaseInterface):
+	def __init__(self,sinfo,**kw):
+		def_kw = {
+			'service_descriptor': JSONRPCServiceDescriptor,
+			'request_handler': JSONRPCRequestHandler,
+			'response_handler': JSONRPCResponseHandler,
+			'fault_handler': JSONRPCFaultHandler}
+		def_kw.update(kw)
+		BaseInterface.__init__(self,sinfo,**def_kw)
+
+	@staticmethod
+	def _interface_name():
+		return 'jsonrpc10'
+
+	@staticmethod
+	def _accept_basetype(typ):
+		return pytype_support.count(typ)>0
+
+	@staticmethod
+	def _accept_list():
+		return True
+
+	@staticmethod
+	def _accept_dict():
+		return False
\ No newline at end of file

=== modified file 'frameworks/python/src/ladon/server/dispatcher.py'
--- frameworks/python/src/ladon/server/dispatcher.py	2012-09-05 12:39:33 +0000
+++ frameworks/python/src/ladon/server/dispatcher.py	2012-10-23 15:04:45 +0000
@@ -37,7 +37,7 @@
 		self.logging = logging
 
 
-	def call_method(self,method,req_dict,tc,export_dict,log_line):
+	def call_method(self,methodname,method,req_dict,tc,export_dict,log_line):
 		"""
 		call_method converts the res_dict delivered from an interface
 		to the type of arguments expected by the service method.
@@ -83,7 +83,7 @@
 		#service_module = imp.load_module(mname,file, pathname, description)
 		service_class_instance = getattr(service_module,method.sinfo.servicename)()
 		if self.logging & LOG_REQUEST_ACCESS:
-			log_line += ['Method:%s.%s' % (method.sinfo.servicename,req_dict['methodname'])]
+			log_line += ['Method:%s.%s' % (method.sinfo.servicename,methodname)]
 		if self.logging & LOG_REQUEST_DICT:
 			log_line += ['RequestDict:%s' % (str(req_dict))]
 		if self.logging & LOG_EXECUTION_TIME:
@@ -91,9 +91,9 @@
 		if method._has_keywords:
 			kw = {'LADON_METHOD_TC':tc}
 			kw.update(export_dict)
-			result = getattr(service_class_instance,req_dict['methodname'])(*args,**kw)
+			result = getattr(service_class_instance,methodname)(*args,**kw)
 		else:
-			result = getattr(service_class_instance,req_dict['methodname'])(*args)
+			result = getattr(service_class_instance,methodname)(*args)
 		if self.logging & LOG_EXECUTION_TIME:
 			log_line.insert(0,'ExecutionTime:%s' % str(time.time()-start))
 		return result
@@ -148,7 +148,8 @@
 			export_dict['response_attachments'] = AttachmentHandler()
 			methodname,method = None,None
 			req_dict = self.iface.parse_request(request_data,encoding=self.response_encoding)
-			methodname = req_dict['methodname']
+			passback_dict = self.iface.get_passback_params(req_dict)
+			methodname = req_dict['methodname'] if 'methodname' in req_dict else req_dict['method']
 			method = self.sinst.method(methodname)
 			if not method:
 				raise UndefinedServiceMethod(self.iface._interface_name(),self.sinst.servicename,'Service method "%s" is not declared in service' % methodname)
@@ -156,7 +157,7 @@
 				encoding=method._encoding,
 				allow_unsafe_conversion=method._allow_unsafe_conversion,
 				only_strings_to_unicode=(not self.iface.stringify_res_dict()))
-			result = self.call_method(method,req_dict,tc,export_dict,log_line)
+			result = self.call_method(methodname,method,req_dict,tc,export_dict,log_line)
 		except Exception as e:
 			if isinstance(e,ServiceFault):
 				response = self.iface.build_fault_response(e,methodname,encoding=self.response_encoding)
@@ -176,9 +177,10 @@
 			# so the service developer has full control over response headers and data.
 			return result
 		res_dict = self.result_to_dict(method,result,tc,export_dict['response_attachments'],log_line=log_line)
+		res_dict = self.iface.add_passback_params(res_dict, passback_dict)
 		if 'mirror' in req_dict:
 			res_dict['reflection'] = req_dict['mirror']
 		response = self.iface.build_response(res_dict,encoding=self.response_encoding)
 		if self.logging:
-			debug('\t%s' % ('\t'.join(log_line)))
+			debug('\t%s' % ('\t'.join(log_line)))	
 		return response

=== modified file 'frameworks/python/src/ladon/types/typemanager.py'
--- frameworks/python/src/ladon/types/typemanager.py	2012-01-28 14:40:18 +0000
+++ frameworks/python/src/ladon/types/typemanager.py	2012-10-23 15:04:45 +0000
@@ -9,7 +9,9 @@
 	this_cls_attrs = dir(cls)
 	res = []
 	for attr in this_cls_attrs:
-		if base_attrs.count(attr) or (exclude_methods and inspect.ismethod(getattr(cls,attr))):
+		# attr == '__qualname__'
+		# Python 3.3 __qualname__ (PEP 3155 -- Qualified name for classes and functions) fix
+		if base_attrs.count(attr) or (exclude_methods and inspect.ismethod(getattr(cls,attr))) or attr == '__qualname__':
 			continue
 		res += [attr]
 	return res

=== modified file 'frameworks/python/tests/servicerunner.py'
--- frameworks/python/tests/servicerunner.py	2012-10-15 15:05:09 +0000
+++ frameworks/python/tests/servicerunner.py	2012-10-23 15:04:45 +0000
@@ -9,6 +9,7 @@
 	'stringtests',
 	'typetests',
 	'attachmenttests',
+	'jsonrpc10',
 	'collectiontest'
 ]
 

=== added file 'frameworks/python/tests/services/jsonrpc10.py'
--- frameworks/python/tests/services/jsonrpc10.py	1970-01-01 00:00:00 +0000
+++ frameworks/python/tests/services/jsonrpc10.py	2012-10-23 15:04:45 +0000
@@ -0,0 +1,54 @@
+from ladon.ladonizer import ladonize
+from ladon.compat import PORTABLE_BYTES,PORTABLE_STRING
+import binascii
+import sys
+
+class JsonPrc10Service(object):
+    @ladonize(rtype=PORTABLE_STRING)
+    def return_string(self):
+        if sys.version_info[0]>=3:
+            return 'Yo!!!'
+        return PORTABLE_STRING('Yo!!!','utf-8')
+    
+    @ladonize(rtype=int)
+    def return_int(self):
+        return 11
+    
+    @ladonize(rtype=float)
+    def return_float(self):
+        return 11.11
+    
+    @ladonize(rtype=bool)
+    def return_bool(self):
+        return True
+    
+    @ladonize(rtype=PORTABLE_BYTES)
+    def return_bytes(self):
+        if sys.version_info[0]>=3:
+            return PORTABLE_BYTES('Yo!!!','utf-8')
+        return 'Yo!!!'
+    
+    @ladonize(PORTABLE_STRING, rtype=PORTABLE_STRING)
+    def passback_string(self,arg):
+        return arg
+    
+    @ladonize(int,rtype=int)
+    def passback_int(self,arg):
+        return arg
+    
+    @ladonize(float,rtype=float)
+    def passback_float(self,arg):
+        return arg
+    
+    @ladonize(bool,rtype=bool)
+    def passback_bool(self,arg):
+        return arg
+    
+    @ladonize(PORTABLE_BYTES,rtype=PORTABLE_BYTES)
+    def passback_bytes(self,arg):
+        return arg
+    
+    @ladonize(int,float,PORTABLE_STRING,rtype=bool)
+    def params(self,arg0,arg1,arg2):
+        return True
+        
\ No newline at end of file

=== added file 'frameworks/python/tests/testjsonrpc10.py'
--- frameworks/python/tests/testjsonrpc10.py	1970-01-01 00:00:00 +0000
+++ frameworks/python/tests/testjsonrpc10.py	2012-10-23 15:04:45 +0000
@@ -0,0 +1,254 @@
+# -*- coding: utf-8 -*-
+
+import unittest
+import servicerunner
+import sys
+import xml.dom.minidom as md
+if sys.version_info[0]>=3:
+	from urllib.parse import urlparse,splitport
+	from http.client import HTTPConnection, HTTPSConnection
+else:
+	from urllib import splitport
+	from urlparse import urlparse
+	from httplib import HTTPConnection, HTTPSConnection
+import sys,json
+from ladon.compat import PORTABLE_BYTES,PORTABLE_STRING,PORTABLE_STRING_TYPES
+from testladon import HTTPRequestPoster
+from testladon import str_to_portable_string
+import binascii
+
+class JsonRpc10Tests(unittest.TestCase):
+
+	def setUp(self):
+		self.post_helper = HTTPRequestPoster('http://localhost:2376/JsonPrc10Service')
+
+	def test_get_string(self):
+		req = {'method':'return_string','params':None,'id':0}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+		expected_result = str_to_portable_string('Yo!!!')
+		self.assertEqual(res['result'], expected_result)
+		
+	def test_get_int(self):
+		req = {'method':'return_int','params':None,'id':0}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+		
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+		expected_result = 11
+		self.assertEqual(res['result'], expected_result)
+	
+	def test_get_float(self):
+		req = {'method':'return_float','params':None,'id':0}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+		
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+		expected_result = 11.11
+		self.assertEqual(res['result'], expected_result)
+		
+	def test_get_bool(self):
+		req = {'method':'return_bool','params':None,'id':0}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+		
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+		expected_result = True
+		self.assertEqual(res['result'], expected_result)
+		
+	def test_get_bytes(self):
+		req = {'method':'return_bytes','params':None,'id':0}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+		
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+		expected_result = 'Yo!!!'
+		self.assertEqual(res['result'], expected_result)
+		
+	def test_passback_string(self):
+		val = 'Yo!!!'
+		req = {'method':'passback_string','params':{'arg':val},'id':0}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+		expected_result = str_to_portable_string(val)
+		self.assertEqual(res['result'], expected_result)
+		
+	def test_passback_int(self):
+		val = 11
+		req = {'method':'passback_int','params':{'arg':val},'id':0}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+		
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+		expected_result = val
+		self.assertEqual(res['result'], expected_result)
+	
+	def test_passback_float(self):
+		val = 11.11
+		req = {'method':'passback_float','params':{'arg':val},'id':0}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+		
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+		expected_result = val
+		self.assertEqual(res['result'], expected_result)
+		
+	def test_passback_bool(self):
+		val = True
+		req = {'method':'passback_bool','params':{'arg':val},'id':0}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+		
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+		expected_result = val
+		self.assertEqual(res['result'], expected_result)
+		
+	def test_passback_bytes(self):
+		val = 'Yo!!!'
+		req = {'method':'passback_bytes','params':{'arg':val},'id':0}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+		
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+		self.assertEqual(res['result'], val)
+		
+	def test_validate_request_response_structure(self):
+		req = {}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+		
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+
+		self.assertIs(type(res['error']), dict)
+		self.assertTrue('id' is not res)
+		self.assertIs(res['result'], None)
+		self.assertTrue('"method"' in res['error']['string'])
+		
+		req = {'method':'passback_string'}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+		
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+		self.assertIs(type(res['error']), dict)
+		self.assertTrue('id' is not res)
+		self.assertIs(res['result'], None)
+		self.assertTrue('"params"' in res['error']['string'])
+		
+		req = {'method':'passback_string','params':None}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+		
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+		self.assertIs(type(res['error']), dict)
+		self.assertTrue('id' is not res)
+		self.assertIs(res['result'], None)
+		self.assertTrue('"id"' in res['error']['string'])
+		
+		req = {'method':'passback_string','params':None,'id':0}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+		
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+		
+		self.assertIs(type(res['error']), dict)
+		self.assertEqual(res['id'], '0')
+		self.assertIs(res['result'], None)
+		
+		req = {'method':'passback_string','params':None,'id':'0'}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+		
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+		
+		self.assertIs(type(res['error']), dict)
+		self.assertEqual(res['id'], '0')
+		self.assertIs(res['result'], None)
+		
+		req = {'method':'passback_string','params':{'arg': 'Yo!!!'},'id':0}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+		
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+
+		self.assertIs(res['error'], None)
+		self.assertEqual(res['id'], '0')
+		self.assertEqual(res['result'], 'Yo!!!')
+		
+		req = {'method':'params','params':{},'id':0}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+		
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+
+		self.assertIs(type(res['error']), dict)
+		self.assertEqual(res['id'], '0')
+		self.assertTrue('"arg0"' in res['error']['string'])
+		
+		req = {'method':'params','params':{'arg0':11},'id':0}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+		
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+
+		self.assertIs(type(res['error']), dict)
+		self.assertEqual(res['id'], '0')
+		self.assertTrue('"arg1"' in res['error']['string'])
+		
+		req = {'method':'params','params':{'arg0':11, 'arg1':11.11},'id':0}
+		jreq = json.dumps(req)
+		
+		status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
+		
+		self.assertEqual(status, 200)
+		res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
+
+		self.assertIs(type(res['error']), dict)
+		self.assertEqual(res['id'], '0')
+		self.assertTrue('"arg2"' in res['error']['string'])
+
+if __name__ == '__main__':
+	import servicerunner
+	servicerunner
+	service_thread = servicerunner.serve_test_service(as_thread=True)
+	unittest.main(exit=False)
+	service_thread.server.shutdown()
\ No newline at end of file


Follow ups