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

Proposed by roger
Status: Merged
Merged at revision: 95
Proposed branch: lp:~roger-lp/ladon/trunk
Merge into: lp:ladon
Diff against target: 883 lines (+520/-148)
8 files modified
frameworks/python/src/ladon/interfaces/__init__.py (+1/-0)
frameworks/python/src/ladon/interfaces/base.py (+0/-147)
frameworks/python/src/ladon/interfaces/jsonrpc10.py (+205/-0)
frameworks/python/src/ladon/server/dispatcher.py (+2/-0)
frameworks/python/src/ladon/types/typemanager.py (+3/-1)
frameworks/python/tests/servicerunner.py (+1/-0)
frameworks/python/tests/services/jsonrpc10.py (+54/-0)
frameworks/python/tests/testjsonrpc10.py (+254/-0)
To merge this branch: bzr merge lp:~roger-lp/ladon/trunk
Reviewer Review Type Date Requested Status
jsgaarde code Approve
Mikhus Pending
Review via email: mp+131028@code.launchpad.net

This proposal supersedes a proposal from 2012-10-23.

Description of the change

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).

To post a comment you must log in.
Revision history for this message
Mikhus (mikhus) wrote : Posted in a previous version of this proposal

Please, remove .bzrignore from merge

review: Needs Fixing
Revision history for this message
roger (roger-lp) wrote : Posted in a previous version of this proposal

.bzrignore was removed

Revision history for this message
jsgaarde (jakob-simon-gaarde) wrote :

Please explain what these do and why they are nessecary:
add_passback_params(), get_passback_params()

Revision history for this message
roger (roger-lp) wrote :

I have decided to implement these two methods because I have faced with needs of interaction with request parameters which should be returned to client without changes (pass back parameters), it means that such parameters are used as markers, like "id" parameter in json-rpc requests and responses.

get_passback_params() is used for extracting "pass back parameters" from request parameters list.
add_passback_params() is used for putting, earlier extracted, "pass back parameters" into response parameters list.

Revision history for this message
roger (roger-lp) wrote :

One more clarification, these methods are needed for implementation request/response id mechanism as described in json-rpc 1.0 specification.

Revision history for this message
jsgaarde (jakob-simon-gaarde) wrote :

OK, I faced the same problem with jsonwsp implementing the mirror/reflection mechanism (built it into dispatcher as a general rule for all service protocols). To be honest - it should probably have been done something like your solution.
I'll finish my review tonight :-)

Revision history for this message
jsgaarde (jakob-simon-gaarde) wrote :

Only one more question. What is the effect of not having check for '__qualname__' in the TypeManager?
/ Jakob

Revision history for this message
jsgaarde (jakob-simon-gaarde) wrote :

Changes needed:

1. Here is something I don't do everyday, I'm going to ask you to to un-nice your solution (but to my defense it's for simplicity)

I have thought about the passback methods in interfaces.base.* - I think your solution is nice and everything, but I don't think we should extend the interfaces.base.Base* classes just to copy an id.
So I suggest a smaller not as nice solution for now:

http://pastebin.com/raw.php?i=GyPMSTBV

I know you meen to make the nicest solution I'm just worried about extending the interfaces.base.Base* classes much more - they are bloated enough already and these are exactly the classes we want to attract other people to use for contributing new interfaces.

2. please remove the methodname argument in call_method() of the dispatcher it is redundant cause the methodname _MUST_ recide in req_dict["methodname"] - we have to stick to some kind of format in req_dict and res_dict as they are very central in Ladon's design.
That also means you have to change your jsonrpc10 interface class so the method name doesn't end up in req_dict["method"].

Best Regards Jakob

Revision history for this message
roger (roger-lp) wrote :

If there are not check for '__qualname__' when I run ladon application using python 3.3 I get following exception for any exposed with ladonize method:

Traceback (most recent call last):
  File "/home/roger/projects/ladon/frameworks/python/tests/ladon/server/wsgi_application.py", line 318, in __call__
    self.import_services(self.service_list)
  File "/home/roger/projects/ladon/frameworks/python/tests/ladon/server/wsgi_application.py", line 274, in import_services
    __import__(service)
  File "/home/roger/projects/ladon/frameworks/python/tests/services/attachmenttests.py", line 23, in <module>
    class AttachmentTestService(object):
  File "/home/roger/projects/ladon/frameworks/python/tests/services/attachmenttests.py", line 30, in AttachmentTestService
    @ladonize(File,rtype=UploadFileResponse)
  File "/home/roger/projects/ladon/frameworks/python/tests/ladon/ladonizer/decorator.py", line 88, in decorator
    ladon_method_info = global_service_collection().add_service_method(f,*def_args,**def_kw)
  File "/home/roger/projects/ladon/frameworks/python/tests/ladon/ladonizer/collection.py", line 133, in add_service_method
    method = self.services[(src_fname,clsname)].add_method(f,*def_args,**def_kw)
  File "/home/roger/projects/ladon/frameworks/python/tests/ladon/ladonizer/collection.py", line 325, in add_method
    method = LadonMethodInfo(self,f,*def_args,**def_kw)
  File "/home/roger/projects/ladon/frameworks/python/tests/ladon/ladonizer/collection.py", line 507, in __init__
    self._multipart_response_required = sinfo.typemanager.analyze_param(self._rtype)
  File "/home/roger/projects/ladon/frameworks/python/tests/ladon/types/typemanager.py", line 158, in analyze_param
    self.analyze_class(param)
  File "/home/roger/projects/ladon/frameworks/python/tests/ladon/types/typemanager.py", line 116, in analyze_class
    raise NeedToDefineParseTimeException("class attributes on LadonTypes must be defined as types, lists.\\nclass: %s\\nattr: %s" % (cls.__name__,attr))\nladon.exceptions.types.NeedToDefineParseTimeException: class attributes on LadonTypes must be defined as types, lists.
class: UploadFileResponse\nattr: __qualname__

Revision history for this message
jsgaarde (jakob-simon-gaarde) wrote :

OK, so this is a fix Python 3.3+? good :-)

> If there are not check for '__qualname__' when I run ladon application using
> python 3.3 I get following exception for any exposed with ladonize method:
>
> Traceback (most recent call last):
> File "/home/roger/projects/ladon/frameworks/python/tests/ladon/server/wsgi_a
> pplication.py", line 318, in __call__
> self.import_services(self.service_list)
> File "/home/roger/projects/ladon/frameworks/python/tests/ladon/server/wsgi_a
> pplication.py", line 274, in import_services
> __import__(service)
> File "/home/roger/projects/ladon/frameworks/python/tests/services/attachment
> tests.py", line 23, in <module>
> class AttachmentTestService(object):
> File "/home/roger/projects/ladon/frameworks/python/tests/services/attachment
> tests.py", line 30, in AttachmentTestService
> @ladonize(File,rtype=UploadFileResponse)
> File "/home/roger/projects/ladon/frameworks/python/tests/ladon/ladonizer/dec
> orator.py", line 88, in decorator
> ladon_method_info =
> global_service_collection().add_service_method(f,*def_args,**def_kw)
> File "/home/roger/projects/ladon/frameworks/python/tests/ladon/ladonizer/col
> lection.py", line 133, in add_service_method
> method =
> self.services[(src_fname,clsname)].add_method(f,*def_args,**def_kw)
> File "/home/roger/projects/ladon/frameworks/python/tests/ladon/ladonizer/col
> lection.py", line 325, in add_method
> method = LadonMethodInfo(self,f,*def_args,**def_kw)
> File "/home/roger/projects/ladon/frameworks/python/tests/ladon/ladonizer/col
> lection.py", line 507, in __init__
> self._multipart_response_required =
> sinfo.typemanager.analyze_param(self._rtype)
> File "/home/roger/projects/ladon/frameworks/python/tests/ladon/types/typeman
> ager.py", line 158, in analyze_param
> self.analyze_class(param)
> File "/home/roger/projects/ladon/frameworks/python/tests/ladon/types/typeman
> ager.py", line 116, in analyze_class
> raise NeedToDefineParseTimeException("class attributes on LadonTypes must
> be defined as types, lists.\\nclass: %s\\nattr: %s" %
> (cls.__name__,attr))\nladon.exceptions.types.NeedToDefineParseTimeException:
> class attributes on LadonTypes must be defined as types, lists.
> class: UploadFileResponse\nattr: __qualname__

Revision history for this message
roger (roger-lp) wrote :

Yup :)

> OK, so this is a fix Python 3.3+? good :-)
>
> > If there are not check for '__qualname__' when I run ladon application using
> > python 3.3 I get following exception for any exposed with ladonize method:
> >
> > Traceback (most recent call last):
> > File
> "/home/roger/projects/ladon/frameworks/python/tests/ladon/server/wsgi_a
> > pplication.py", line 318, in __call__
> > self.import_services(self.service_list)
> > File
> "/home/roger/projects/ladon/frameworks/python/tests/ladon/server/wsgi_a
> > pplication.py", line 274, in import_services
> > __import__(service)
> > File
> "/home/roger/projects/ladon/frameworks/python/tests/services/attachment
> > tests.py", line 23, in <module>
> > class AttachmentTestService(object):
> > File
> "/home/roger/projects/ladon/frameworks/python/tests/services/attachment
> > tests.py", line 30, in AttachmentTestService
> > @ladonize(File,rtype=UploadFileResponse)
> > File
> "/home/roger/projects/ladon/frameworks/python/tests/ladon/ladonizer/dec
> > orator.py", line 88, in decorator
> > ladon_method_info =
> > global_service_collection().add_service_method(f,*def_args,**def_kw)
> > File
> "/home/roger/projects/ladon/frameworks/python/tests/ladon/ladonizer/col
> > lection.py", line 133, in add_service_method
> > method =
> > self.services[(src_fname,clsname)].add_method(f,*def_args,**def_kw)
> > File
> "/home/roger/projects/ladon/frameworks/python/tests/ladon/ladonizer/col
> > lection.py", line 325, in add_method
> > method = LadonMethodInfo(self,f,*def_args,**def_kw)
> > File
> "/home/roger/projects/ladon/frameworks/python/tests/ladon/ladonizer/col
> > lection.py", line 507, in __init__
> > self._multipart_response_required =
> > sinfo.typemanager.analyze_param(self._rtype)
> > File
> "/home/roger/projects/ladon/frameworks/python/tests/ladon/types/typeman
> > ager.py", line 158, in analyze_param
> > self.analyze_class(param)
> > File
> "/home/roger/projects/ladon/frameworks/python/tests/ladon/types/typeman
> > ager.py", line 116, in analyze_class
> > raise NeedToDefineParseTimeException("class attributes on LadonTypes
> must
> > be defined as types, lists.\\nclass: %s\\nattr: %s" %
> > (cls.__name__,attr))\nladon.exceptions.types.NeedToDefineParseTimeException:
> > class attributes on LadonTypes must be defined as types, lists.
> > class: UploadFileResponse\nattr: __qualname__

Revision history for this message
roger (roger-lp) wrote :

Remarks were applied.

Regards Roger

> Changes needed:
>
> 1. Here is something I don't do everyday, I'm going to ask you to to un-nice
> your solution (but to my defense it's for simplicity)
>
> I have thought about the passback methods in interfaces.base.* - I think your
> solution is nice and everything, but I don't think we should extend the
> interfaces.base.Base* classes just to copy an id.
> So I suggest a smaller not as nice solution for now:
>
> http://pastebin.com/raw.php?i=GyPMSTBV
>
> I know you meen to make the nicest solution I'm just worried about extending
> the interfaces.base.Base* classes much more - they are bloated enough already
> and these are exactly the classes we want to attract other people to use for
> contributing new interfaces.
>
> 2. please remove the methodname argument in call_method() of the dispatcher it
> is redundant cause the methodname _MUST_ recide in req_dict["methodname"] - we
> have to stick to some kind of format in req_dict and res_dict as they are very
> central in Ladon's design.
> That also means you have to change your jsonrpc10 interface class so the
> method name doesn't end up in req_dict["method"].
>
> Best Regards Jakob

lp:~roger-lp/ladon/trunk updated
95. By jsgaarde

Merged Rogers json-rpc 1.0 interface

Revision history for this message
jsgaarde (jakob-simon-gaarde) wrote :

Merged - thanks Roger!

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'frameworks/python/src/ladon/interfaces/__init__.py'
2--- frameworks/python/src/ladon/interfaces/__init__.py 2012-05-04 14:43:58 +0000
3+++ frameworks/python/src/ladon/interfaces/__init__.py 2012-10-26 11:52:28 +0000
4@@ -71,3 +71,4 @@
5 import ladon.interfaces.soap
6 import ladon.interfaces.soap11
7 import ladon.interfaces.jsonwsp
8+import ladon.interfaces.jsonrpc10
9
10=== added file 'frameworks/python/src/ladon/interfaces/base.py'
11--- frameworks/python/src/ladon/interfaces/base.py 1970-01-01 00:00:00 +0000
12+++ frameworks/python/src/ladon/interfaces/base.py 2012-10-26 11:52:28 +0000
13@@ -0,0 +1,147 @@
14+# -*- coding: utf-8 -*-
15+
16+import inspect
17+
18+class BaseInterface(object):
19+ """All interface implementations must descend from BaseInterface. The interface
20+ implementation can be thought of as an aggregator that ties the three basic functions
21+ of a web service protocol together:
22+
23+ 1. ServiceDescriptor - A generator class that can provide a description for the service.
24+ ie. WSDL for soap.
25+ 2. BaseRequestHandler - A handler that can parse the raw request objects from the client and
26+ convert them into a so-called req_dict (Request dictionary)
27+ 3. BaseResponseHandler - A handler that can process the res_dict (Result Dictionary) of a
28+ service call and build a response object that fit's the protocol being implemented.
29+ 4. BaseFaultHandler - A handler that can convert a ladon.exceptions.service.ServiceFault object
30+ to a fault response that fits the interface.
31+
32+ """
33+ def __init__(self,sinfo,**kw):
34+ self._sinfo = sinfo
35+ sd = ServiceDescriptor()
36+ if 'service_descriptor' in kw:
37+ sd = kw['service_descriptor']
38+ if inspect.getmro(sd).count(ServiceDescriptor):
39+ self._service_descriptor = sd()
40+ if 'request_handler' in kw:
41+ req_handler = kw['request_handler']
42+ if inspect.getmro(req_handler).count(BaseRequestHandler):
43+ self._request_handler = req_handler()
44+ if 'response_handler' in kw:
45+ resp_handler = kw['response_handler']
46+ if inspect.getmro(resp_handler).count(BaseResponseHandler):
47+ self._response_handler = resp_handler()
48+ if 'fault_handler' in kw:
49+ fault_handler = kw['fault_handler']
50+ if inspect.getmro(fault_handler).count(BaseFaultHandler):
51+ self._fault_handler = fault_handler()
52+
53+ @staticmethod
54+ def _interface_name(typ):
55+ return None
56+
57+ @staticmethod
58+ def _accept_basetype(typ):
59+ return False
60+
61+ @staticmethod
62+ def _accept_list():
63+ return False
64+
65+ @staticmethod
66+ def _accept_dict():
67+ return False
68+
69+ def service_info(self):
70+ return self._sinfo
71+
72+ def parse_request(self,soap_body,encoding='UTF-8'):
73+ return self._request_handler.parse_request(soap_body,self._sinfo,encoding)
74+
75+ def build_response(self,res_dict,encoding='UTF-8'):
76+ return self._response_handler.build_response(res_dict,self._sinfo,encoding)
77+
78+ def build_fault_response(self,exc,methodname=None,encoding='UTF-8'):
79+ return self._fault_handler.build_fault_response(exc,self._sinfo,methodname,encoding)
80+
81+ def stringify_res_dict(self):
82+ return self._response_handler._stringify_res_dict
83+
84+ def response_content_type(self):
85+ return self._response_handler._content_type
86+
87+ def description(self,service_url,encoding='UTF-8'):
88+ return self._service_descriptor.generate(
89+ self._sinfo.servicename,
90+ self._sinfo.servicenumber,
91+ self._sinfo.typemanager,
92+ self._sinfo.method_list(),
93+ service_url,encoding)
94+
95+ def description_content_type(self):
96+ return self._service_descriptor._content_type
97+
98+class ServiceDescriptor(object):
99+
100+ _content_type = 'text/plain'
101+
102+ def generate(self,servicename,servicenumber,typemanager,methodlist,encoding):
103+ """
104+ Implement the method that can generate a service description file for the
105+ type of interface you are developing. Thus if you were developing a new
106+ and better SOAP interface and a new and better service descriptor for it,
107+ you will be implementing a WSDL generator in this method.
108+
109+ @param servicename This is a string containing the service class name
110+ @param servicenumber An integer that reflects the order in which the service classes are parsed (useful for creating namespaces)
111+ @param typemanager An instance of ladon.ladonizer.collection.TypeManager which manages the types used by the service
112+ @param methodlist The list of methods exposed by the service
113+ @param encoding The client-side controlled encoding to use in the descriptor
114+
115+ @rtype String representation of the service descriptor (ie. wsdl-formatted if it were SOAP)
116+ """
117+ return ''
118+
119+
120+class BaseRequestHandler(object):
121+
122+ def parse_request(self,req,sinfo,encoding):
123+ return {}
124+
125+
126+class BaseResponseHandler(object):
127+
128+ """
129+ This is the base class of all response handlers. To implement a new response handler
130+ inherit it and overload the build_response() method so it implements the protocol you are
131+ extending Ladon with.
132+
133+ set _stringify_res_dict to True if you want the Ladon dispatcher to convert all result
134+ values to unicode regardless of their defined types during ladonization. This can be useful
135+ in some interfaces. ie. SOAP where all values are presented the same way inside XML tags no
136+ matter if it is a string or a number (example: <name>string</name><age>37</age>)
137+ """
138+ _content_type = 'text/plain'
139+ _stringify_res_dict = False
140+
141+ def build_response(self,method_result,sinfo,encoding):
142+ return ''
143+
144+class BaseFaultHandler(object):
145+
146+ """
147+ This is the base class of all fault handlers. To implement a new fault handler
148+ inherit it and overload the build_fault_response() method so it implements the
149+ protocol you are extending Ladon with.
150+
151+ set _stringify_res_dict to True if you want the Ladon dispatcher to convert all result
152+ values to unicode regardless of their defined types during ladonization. This can be useful
153+ in some interfaces. ie. SOAP where all values are presented the same way inside XML tags no
154+ matter if it is a string or a number (example: <name>string</name><age>37</age>)
155+ """
156+ _content_type = 'text/plain'
157+ _stringify_res_dict = False
158+
159+ def build_fault_response(self,exc,sinfo,methodname,encoding):
160+ return ''
161
162=== removed file 'frameworks/python/src/ladon/interfaces/base.py'
163--- frameworks/python/src/ladon/interfaces/base.py 2012-01-06 12:44:37 +0000
164+++ frameworks/python/src/ladon/interfaces/base.py 1970-01-01 00:00:00 +0000
165@@ -1,147 +0,0 @@
166-# -*- coding: utf-8 -*-
167-
168-import inspect
169-
170-class BaseInterface(object):
171- """All interface implementations must descend from BaseInterface. The interface
172- implementation can be thought of as an aggregator that ties the three basic functions
173- of a web service protocol together:
174-
175- 1. ServiceDescriptor - A generator class that can provide a description for the service.
176- ie. WSDL for soap.
177- 2. BaseRequestHandler - A handler that can parse the raw request objects from the client and
178- convert them into a so-called req_dict (Request dictionary)
179- 3. BaseResponseHandler - A handler that can process the res_dict (Result Dictionary) of a
180- service call and build a response object that fit's the protocol being implemented.
181- 4. BaseFaultHandler - A handler that can convert a ladon.exceptions.service.ServiceFault object
182- to a fault response that fits the interface.
183-
184- """
185- def __init__(self,sinfo,**kw):
186- self._sinfo = sinfo
187- sd = ServiceDescriptor()
188- if 'service_descriptor' in kw:
189- sd = kw['service_descriptor']
190- if inspect.getmro(sd).count(ServiceDescriptor):
191- self._service_descriptor = sd()
192- if 'request_handler' in kw:
193- req_handler = kw['request_handler']
194- if inspect.getmro(req_handler).count(BaseRequestHandler):
195- self._request_handler = req_handler()
196- if 'response_handler' in kw:
197- resp_handler = kw['response_handler']
198- if inspect.getmro(resp_handler).count(BaseResponseHandler):
199- self._response_handler = resp_handler()
200- if 'fault_handler' in kw:
201- fault_handler = kw['fault_handler']
202- if inspect.getmro(fault_handler).count(BaseFaultHandler):
203- self._fault_handler = fault_handler()
204-
205- @staticmethod
206- def _interface_name(typ):
207- return None
208-
209- @staticmethod
210- def _accept_basetype(typ):
211- return False
212-
213- @staticmethod
214- def _accept_list():
215- return False
216-
217- @staticmethod
218- def _accept_dict():
219- return False
220-
221- def service_info(self):
222- return self._sinfo
223-
224- def parse_request(self,soap_body,encoding='UTF-8'):
225- return self._request_handler.parse_request(soap_body,self._sinfo,encoding)
226-
227- def build_response(self,res_dict,encoding='UTF-8'):
228- return self._response_handler.build_response(res_dict,self._sinfo,encoding)
229-
230- def build_fault_response(self,exc,methodname=None,encoding='UTF-8'):
231- return self._fault_handler.build_fault_response(exc,self._sinfo,methodname,encoding)
232-
233- def stringify_res_dict(self):
234- return self._response_handler._stringify_res_dict
235-
236- def response_content_type(self):
237- return self._response_handler._content_type
238-
239- def description(self,service_url,encoding='UTF-8'):
240- return self._service_descriptor.generate(
241- self._sinfo.servicename,
242- self._sinfo.servicenumber,
243- self._sinfo.typemanager,
244- self._sinfo.method_list(),
245- service_url,encoding)
246-
247- def description_content_type(self):
248- return self._service_descriptor._content_type
249-
250-class ServiceDescriptor(object):
251-
252- _content_type = 'text/plain'
253-
254- def generate(self,servicename,servicenumber,typemanager,methodlist,encoding):
255- """
256- Implement the method that can generate a service description file for the
257- type of interface you are developing. Thus if you were developing a new
258- and better SOAP interface and a new and better service descriptor for it,
259- you will be implementing a WSDL generator in this method.
260-
261- @param servicename This is a string containing the service class name
262- @param servicenumber An integer that reflects the order in which the service classes are parsed (useful for creating namespaces)
263- @param typemanager An instance of ladon.ladonizer.collection.TypeManager which manages the types used by the service
264- @param methodlist The list of methods exposed by the service
265- @param encoding The client-side controlled encoding to use in the descriptor
266-
267- @rtype String representation of the service descriptor (ie. wsdl-formatted if it were SOAP)
268- """
269- return ''
270-
271-
272-class BaseRequestHandler(object):
273-
274- def parse_request(self,req,sinfo,encoding):
275- return {}
276-
277-
278-class BaseResponseHandler(object):
279-
280- """
281- This is the base class of all response handlers. To implement a new response handler
282- inherit it and overload the build_response() method so it implements the protocol you are
283- extending Ladon with.
284-
285- set _stringify_res_dict to True if you want the Ladon dispatcher to convert all result
286- values to unicode regardless of their defined types during ladonization. This can be useful
287- in some interfaces. ie. SOAP where all values are presented the same way inside XML tags no
288- matter if it is a string or a number (example: <name>string</name><age>37</age>)
289- """
290- _content_type = 'text/plain'
291- _stringify_res_dict = False
292-
293- def build_response(self,method_result,sinfo,encoding):
294- return ''
295-
296-class BaseFaultHandler(object):
297-
298- """
299- This is the base class of all fault handlers. To implement a new fault handler
300- inherit it and overload the build_fault_response() method so it implements the
301- protocol you are extending Ladon with.
302-
303- set _stringify_res_dict to True if you want the Ladon dispatcher to convert all result
304- values to unicode regardless of their defined types during ladonization. This can be useful
305- in some interfaces. ie. SOAP where all values are presented the same way inside XML tags no
306- matter if it is a string or a number (example: <name>string</name><age>37</age>)
307- """
308- _content_type = 'text/plain'
309- _stringify_res_dict = False
310-
311- def build_fault_response(self,exc,sinfo,methodname,encoding):
312- return ''
313
314=== added file 'frameworks/python/src/ladon/interfaces/jsonrpc10.py'
315--- frameworks/python/src/ladon/interfaces/jsonrpc10.py 1970-01-01 00:00:00 +0000
316+++ frameworks/python/src/ladon/interfaces/jsonrpc10.py 2012-10-26 11:52:28 +0000
317@@ -0,0 +1,205 @@
318+# -*- coding: utf-8 -*-
319+
320+from ladon.interfaces.base import BaseInterface,ServiceDescriptor,BaseRequestHandler,BaseResponseHandler,BaseFaultHandler
321+from ladon.interfaces import expose
322+from ladon.compat import type_to_jsontype,pytype_support,PORTABLE_STRING
323+import json,sys,traceback
324+from ladon.exceptions.service import ServiceFault
325+from ladon.exceptions.base import LadonException
326+
327+def _add_passback_params(res_dict,passback_dict):
328+ merged_dict = dict((k,v) for (k,v) in res_dict.items())
329+ for k,v in passback_dict.items():
330+ merged_dict[k] = v
331+ return merged_dict
332+
333+class RequestPropFault(ServiceFault):
334+ def __init__(self,prop, passback_dict):
335+ self.prop = prop
336+ self.passback_dict = passback_dict
337+ super(RequestPropFault,self).__init__('service','Request doesn\'t have "%s" property.' % self.prop, None, 2)
338+
339+ def __str__(self):
340+ return self.faultstring
341+
342+class RequestParamFault(ServiceFault):
343+ def __init__(self,param, passback_dict):
344+ self.param = param
345+ self.passback_dict = passback_dict
346+ super(RequestParamFault,self).__init__('service','Request doesn\'t have "%s" parameter.' % self.param, None, 2)
347+
348+ def __str__(self):
349+ return self.faultstring
350+
351+class JSONRPCServiceDescriptor(ServiceDescriptor):
352+ javascript_type_map = type_to_jsontype
353+ version = '1.0'
354+ _content_type = 'application/json'
355+
356+ def generate(self,servicename,servicenumber,typemanager,methodlist,service_url,encoding):
357+ type_dict = typemanager.type_dict
358+ type_order = typemanager.type_order
359+
360+ def map_type(typ):
361+ if typ in JSONRPCServiceDescriptor.javascript_type_map:
362+ return JSONRPCServiceDescriptor.javascript_type_map[typ]
363+ else:
364+ return typ.__name__
365+
366+ desc = {
367+ 'servicename': servicename,
368+ 'url': service_url,
369+ 'type': 'jsonrpc/description',
370+ 'version': self.version,
371+ 'types': {},
372+ 'methods': {}
373+ }
374+
375+ types = desc['types']
376+ for typ in type_order:
377+ if type(typ)==dict:
378+ desc_type = {}
379+ types[typ['name']] = desc_type
380+ for k,v,props in typ['attributes']:
381+ if type(v)==list:
382+ desc_type_val = [map_type(v[0])]
383+ else:
384+ desc_type_val = map_type(v)
385+ desc_type[k] = desc_type_val
386+
387+ methods = desc['methods']
388+ for m in methodlist:
389+ desc_mparams = {}
390+ order = 1
391+ desc_method = {'params': desc_mparams, 'doc_lines': m._method_doc}
392+ methods[m.name()] = desc_method
393+ for arg in m.args():
394+ if [list,tuple].count(type(arg['type'])):
395+ desc_param_type = [map_type(arg['type'][0])]
396+ else:
397+ desc_param_type = map_type(arg['type'])
398+ desc_mparams[arg['name']] = {
399+ "type": desc_param_type,
400+ "def_order": order,
401+ "optional": arg['optional'],
402+ }
403+ if 'doc' in arg:
404+ desc_mparams[arg['name']]["doc_lines"] = arg['doc']
405+ else:
406+ desc_mparams[arg['name']]["doc_lines"] = []
407+ order += 1
408+
409+ if [list,tuple].count(type(m._rtype)):
410+ desc_rtype = [map_type(m._rtype[0])]
411+ else:
412+ desc_rtype = map_type(m._rtype)
413+ desc_method['ret_info'] = {
414+ 'type': desc_rtype,
415+ 'doc_lines': m._rtype_doc
416+ }
417+
418+ if sys.version_info[0]>=3:
419+ return json.dumps(desc)
420+ return json.dumps(desc,encoding=encoding)
421+
422+class JSONRPCRequestHandler(BaseRequestHandler):
423+ def parse_request(self,json_body,sinfo,encoding):
424+ def parse_number(x):
425+ return PORTABLE_STRING(x)
426+ def parse_constant(x):
427+ if x=='null':
428+ return PORTABLE_STRING("None")
429+ return PORTABLE_STRING(x)
430+ req_dict = json.loads(PORTABLE_STRING(json_body,encoding), parse_int=parse_number, parse_float=parse_number, \
431+ parse_constant=parse_constant)
432+ passback_dict = self.get_passback_params(req_dict)
433+ if 'method' not in req_dict:
434+ raise RequestPropFault('method',passback_dict)
435+ if 'params' not in req_dict:
436+ raise RequestPropFault('params',passback_dict)
437+ if 'id' not in req_dict:
438+ raise RequestPropFault('id',passback_dict)
439+ minfo = sinfo.methods[req_dict['method']]
440+ if (req_dict['params'] is None or len(req_dict['params']) == 0) and len(minfo.args()) > 0:
441+ raise RequestParamFault(minfo.args()[0]['name'],passback_dict)
442+ else:
443+ for arg in minfo.args():
444+ isgiven = False
445+ for param in req_dict['params']:
446+ if param == arg['name']:
447+ isgiven = True
448+ if not isgiven:
449+ raise RequestParamFault(arg['name'],passback_dict)
450+ req_dict['args'] = req_dict['params']
451+ req_dict['methodname'] = req_dict['method']
452+ del req_dict['params']
453+ del req_dict['method']
454+ return req_dict
455+
456+ def get_passback_params(self, req_dict):
457+ if 'id' in req_dict:
458+ return {'id': req_dict['id']}
459+ else:
460+ return {}
461+
462+class JSONRPCResponseHandler(BaseResponseHandler):
463+ _content_type = 'application/json'
464+ _stringify_res_dict = False
465+
466+ def build_response(self,res_dict,sinfo,encoding):
467+ res_dict['error'] = None
468+ del res_dict['servicenumber']
469+ del res_dict['servicename']
470+ del res_dict['method']
471+ return json.dumps(res_dict,ensure_ascii=False).encode(encoding)
472+
473+class JSONRPCFaultHandler(BaseFaultHandler):
474+ _content_type = 'application/json'
475+ _stringify_res_dict = False
476+
477+ def build_fault_response(self,service_exc,sinfo,methodname,encoding):
478+ if service_exc.detail:
479+ detail = service_exc.detail
480+ else:
481+ detail = traceback.format_exc()
482+ detail = detail.replace('\r\n','\n').split('\n')
483+ fault_dict = {
484+ 'result': None,
485+ 'error': {
486+ 'code': service_exc.faultcode,
487+ 'string': service_exc.faultstring,
488+ 'detail': detail,
489+ 'filename': service_exc.mod,
490+ 'lineno': service_exc.lineno
491+ },
492+ }
493+ if hasattr(service_exc,'passback_dict'):
494+ fault_dict = _add_passback_params(fault_dict,service_exc.passback_dict)
495+ return json.dumps(fault_dict,ensure_ascii=False).encode(encoding)
496+
497+@expose
498+class JSONRPCInterface(BaseInterface):
499+ def __init__(self,sinfo,**kw):
500+ def_kw = {
501+ 'service_descriptor': JSONRPCServiceDescriptor,
502+ 'request_handler': JSONRPCRequestHandler,
503+ 'response_handler': JSONRPCResponseHandler,
504+ 'fault_handler': JSONRPCFaultHandler}
505+ def_kw.update(kw)
506+ BaseInterface.__init__(self,sinfo,**def_kw)
507+
508+ @staticmethod
509+ def _interface_name():
510+ return 'jsonrpc10'
511+
512+ @staticmethod
513+ def _accept_basetype(typ):
514+ return pytype_support.count(typ)>0
515+
516+ @staticmethod
517+ def _accept_list():
518+ return True
519+
520+ @staticmethod
521+ def _accept_dict():
522+ return False
523\ No newline at end of file
524
525=== modified file 'frameworks/python/src/ladon/server/dispatcher.py'
526--- frameworks/python/src/ladon/server/dispatcher.py 2012-09-05 12:39:33 +0000
527+++ frameworks/python/src/ladon/server/dispatcher.py 2012-10-26 11:52:28 +0000
528@@ -178,6 +178,8 @@
529 res_dict = self.result_to_dict(method,result,tc,export_dict['response_attachments'],log_line=log_line)
530 if 'mirror' in req_dict:
531 res_dict['reflection'] = req_dict['mirror']
532+ if 'id' in req_dict:
533+ res_dict['id'] = req_dict['id']
534 response = self.iface.build_response(res_dict,encoding=self.response_encoding)
535 if self.logging:
536 debug('\t%s' % ('\t'.join(log_line)))
537
538=== modified file 'frameworks/python/src/ladon/types/typemanager.py'
539--- frameworks/python/src/ladon/types/typemanager.py 2012-01-28 14:40:18 +0000
540+++ frameworks/python/src/ladon/types/typemanager.py 2012-10-26 11:52:28 +0000
541@@ -9,7 +9,9 @@
542 this_cls_attrs = dir(cls)
543 res = []
544 for attr in this_cls_attrs:
545- if base_attrs.count(attr) or (exclude_methods and inspect.ismethod(getattr(cls,attr))):
546+ # attr == '__qualname__'
547+ # Python 3.3 __qualname__ (PEP 3155 -- Qualified name for classes and functions) fix
548+ if base_attrs.count(attr) or (exclude_methods and inspect.ismethod(getattr(cls,attr))) or attr == '__qualname__':
549 continue
550 res += [attr]
551 return res
552
553=== modified file 'frameworks/python/tests/servicerunner.py'
554--- frameworks/python/tests/servicerunner.py 2012-10-15 15:05:09 +0000
555+++ frameworks/python/tests/servicerunner.py 2012-10-26 11:52:28 +0000
556@@ -9,6 +9,7 @@
557 'stringtests',
558 'typetests',
559 'attachmenttests',
560+ 'jsonrpc10',
561 'collectiontest'
562 ]
563
564
565=== added file 'frameworks/python/tests/services/jsonrpc10.py'
566--- frameworks/python/tests/services/jsonrpc10.py 1970-01-01 00:00:00 +0000
567+++ frameworks/python/tests/services/jsonrpc10.py 2012-10-26 11:52:28 +0000
568@@ -0,0 +1,54 @@
569+from ladon.ladonizer import ladonize
570+from ladon.compat import PORTABLE_BYTES,PORTABLE_STRING
571+import binascii
572+import sys
573+
574+class JsonPrc10Service(object):
575+ @ladonize(rtype=PORTABLE_STRING)
576+ def return_string(self):
577+ if sys.version_info[0]>=3:
578+ return 'Yo!!!'
579+ return PORTABLE_STRING('Yo!!!','utf-8')
580+
581+ @ladonize(rtype=int)
582+ def return_int(self):
583+ return 11
584+
585+ @ladonize(rtype=float)
586+ def return_float(self):
587+ return 11.11
588+
589+ @ladonize(rtype=bool)
590+ def return_bool(self):
591+ return True
592+
593+ @ladonize(rtype=PORTABLE_BYTES)
594+ def return_bytes(self):
595+ if sys.version_info[0]>=3:
596+ return PORTABLE_BYTES('Yo!!!','utf-8')
597+ return 'Yo!!!'
598+
599+ @ladonize(PORTABLE_STRING, rtype=PORTABLE_STRING)
600+ def passback_string(self,arg):
601+ return arg
602+
603+ @ladonize(int,rtype=int)
604+ def passback_int(self,arg):
605+ return arg
606+
607+ @ladonize(float,rtype=float)
608+ def passback_float(self,arg):
609+ return arg
610+
611+ @ladonize(bool,rtype=bool)
612+ def passback_bool(self,arg):
613+ return arg
614+
615+ @ladonize(PORTABLE_BYTES,rtype=PORTABLE_BYTES)
616+ def passback_bytes(self,arg):
617+ return arg
618+
619+ @ladonize(int,float,PORTABLE_STRING,rtype=bool)
620+ def params(self,arg0,arg1,arg2):
621+ return True
622+
623\ No newline at end of file
624
625=== added file 'frameworks/python/tests/testjsonrpc10.py'
626--- frameworks/python/tests/testjsonrpc10.py 1970-01-01 00:00:00 +0000
627+++ frameworks/python/tests/testjsonrpc10.py 2012-10-26 11:52:28 +0000
628@@ -0,0 +1,254 @@
629+# -*- coding: utf-8 -*-
630+
631+import unittest
632+import servicerunner
633+import sys
634+import xml.dom.minidom as md
635+if sys.version_info[0]>=3:
636+ from urllib.parse import urlparse,splitport
637+ from http.client import HTTPConnection, HTTPSConnection
638+else:
639+ from urllib import splitport
640+ from urlparse import urlparse
641+ from httplib import HTTPConnection, HTTPSConnection
642+import sys,json
643+from ladon.compat import PORTABLE_BYTES,PORTABLE_STRING,PORTABLE_STRING_TYPES
644+from testladon import HTTPRequestPoster
645+from testladon import str_to_portable_string
646+import binascii
647+
648+class JsonRpc10Tests(unittest.TestCase):
649+
650+ def setUp(self):
651+ self.post_helper = HTTPRequestPoster('http://localhost:2376/JsonPrc10Service')
652+
653+ def test_get_string(self):
654+ req = {'method':'return_string','params':None,'id':0}
655+ jreq = json.dumps(req)
656+
657+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
658+
659+ self.assertEqual(status, 200)
660+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
661+ expected_result = str_to_portable_string('Yo!!!')
662+ self.assertEqual(res['result'], expected_result)
663+
664+ def test_get_int(self):
665+ req = {'method':'return_int','params':None,'id':0}
666+ jreq = json.dumps(req)
667+
668+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
669+
670+ self.assertEqual(status, 200)
671+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
672+ expected_result = 11
673+ self.assertEqual(res['result'], expected_result)
674+
675+ def test_get_float(self):
676+ req = {'method':'return_float','params':None,'id':0}
677+ jreq = json.dumps(req)
678+
679+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
680+
681+ self.assertEqual(status, 200)
682+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
683+ expected_result = 11.11
684+ self.assertEqual(res['result'], expected_result)
685+
686+ def test_get_bool(self):
687+ req = {'method':'return_bool','params':None,'id':0}
688+ jreq = json.dumps(req)
689+
690+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
691+
692+ self.assertEqual(status, 200)
693+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
694+ expected_result = True
695+ self.assertEqual(res['result'], expected_result)
696+
697+ def test_get_bytes(self):
698+ req = {'method':'return_bytes','params':None,'id':0}
699+ jreq = json.dumps(req)
700+
701+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
702+
703+ self.assertEqual(status, 200)
704+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
705+ expected_result = 'Yo!!!'
706+ self.assertEqual(res['result'], expected_result)
707+
708+ def test_passback_string(self):
709+ val = 'Yo!!!'
710+ req = {'method':'passback_string','params':{'arg':val},'id':0}
711+ jreq = json.dumps(req)
712+
713+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
714+
715+ self.assertEqual(status, 200)
716+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
717+ expected_result = str_to_portable_string(val)
718+ self.assertEqual(res['result'], expected_result)
719+
720+ def test_passback_int(self):
721+ val = 11
722+ req = {'method':'passback_int','params':{'arg':val},'id':0}
723+ jreq = json.dumps(req)
724+
725+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
726+
727+ self.assertEqual(status, 200)
728+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
729+ expected_result = val
730+ self.assertEqual(res['result'], expected_result)
731+
732+ def test_passback_float(self):
733+ val = 11.11
734+ req = {'method':'passback_float','params':{'arg':val},'id':0}
735+ jreq = json.dumps(req)
736+
737+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
738+
739+ self.assertEqual(status, 200)
740+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
741+ expected_result = val
742+ self.assertEqual(res['result'], expected_result)
743+
744+ def test_passback_bool(self):
745+ val = True
746+ req = {'method':'passback_bool','params':{'arg':val},'id':0}
747+ jreq = json.dumps(req)
748+
749+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
750+
751+ self.assertEqual(status, 200)
752+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
753+ expected_result = val
754+ self.assertEqual(res['result'], expected_result)
755+
756+ def test_passback_bytes(self):
757+ val = 'Yo!!!'
758+ req = {'method':'passback_bytes','params':{'arg':val},'id':0}
759+ jreq = json.dumps(req)
760+
761+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
762+
763+ self.assertEqual(status, 200)
764+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
765+ self.assertEqual(res['result'], val)
766+
767+ def test_validate_request_response_structure(self):
768+ req = {}
769+ jreq = json.dumps(req)
770+
771+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
772+
773+ self.assertEqual(status, 200)
774+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
775+
776+ self.assertIs(type(res['error']), dict)
777+ self.assertTrue('id' is not res)
778+ self.assertIs(res['result'], None)
779+ self.assertTrue('"method"' in res['error']['string'])
780+
781+ req = {'method':'passback_string'}
782+ jreq = json.dumps(req)
783+
784+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
785+
786+ self.assertEqual(status, 200)
787+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
788+ self.assertIs(type(res['error']), dict)
789+ self.assertTrue('id' is not res)
790+ self.assertIs(res['result'], None)
791+ self.assertTrue('"params"' in res['error']['string'])
792+
793+ req = {'method':'passback_string','params':None}
794+ jreq = json.dumps(req)
795+
796+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
797+
798+ self.assertEqual(status, 200)
799+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
800+ self.assertIs(type(res['error']), dict)
801+ self.assertTrue('id' is not res)
802+ self.assertIs(res['result'], None)
803+ self.assertTrue('"id"' in res['error']['string'])
804+
805+ req = {'method':'passback_string','params':None,'id':0}
806+ jreq = json.dumps(req)
807+
808+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
809+
810+ self.assertEqual(status, 200)
811+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
812+
813+ self.assertIs(type(res['error']), dict)
814+ self.assertEqual(res['id'], '0')
815+ self.assertIs(res['result'], None)
816+
817+ req = {'method':'passback_string','params':None,'id':'0'}
818+ jreq = json.dumps(req)
819+
820+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
821+
822+ self.assertEqual(status, 200)
823+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
824+
825+ self.assertIs(type(res['error']), dict)
826+ self.assertEqual(res['id'], '0')
827+ self.assertIs(res['result'], None)
828+
829+ req = {'method':'passback_string','params':{'arg': 'Yo!!!'},'id':0}
830+ jreq = json.dumps(req)
831+
832+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
833+
834+ self.assertEqual(status, 200)
835+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
836+
837+ self.assertIs(res['error'], None)
838+ self.assertEqual(res['id'], '0')
839+ self.assertEqual(res['result'], 'Yo!!!')
840+
841+ req = {'method':'params','params':{},'id':0}
842+ jreq = json.dumps(req)
843+
844+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
845+
846+ self.assertEqual(status, 200)
847+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
848+
849+ self.assertIs(type(res['error']), dict)
850+ self.assertEqual(res['id'], '0')
851+ self.assertTrue('"arg0"' in res['error']['string'])
852+
853+ req = {'method':'params','params':{'arg0':11},'id':0}
854+ jreq = json.dumps(req)
855+
856+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
857+
858+ self.assertEqual(status, 200)
859+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
860+
861+ self.assertIs(type(res['error']), dict)
862+ self.assertEqual(res['id'], '0')
863+ self.assertTrue('"arg1"' in res['error']['string'])
864+
865+ req = {'method':'params','params':{'arg0':11, 'arg1':11.11},'id':0}
866+ jreq = json.dumps(req)
867+
868+ status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
869+
870+ self.assertEqual(status, 200)
871+ res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
872+
873+ self.assertIs(type(res['error']), dict)
874+ self.assertEqual(res['id'], '0')
875+ self.assertTrue('"arg2"' in res['error']['string'])
876+
877+if __name__ == '__main__':
878+ import servicerunner
879+ servicerunner
880+ service_thread = servicerunner.serve_test_service(as_thread=True)
881+ unittest.main(exit=False)
882+ service_thread.server.shutdown()
883\ No newline at end of file

Subscribers

People subscribed via source and target branches

to all changes: