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
=== 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-26 11:52:28 +0000
@@ -71,3 +71,4 @@
71import ladon.interfaces.soap71import ladon.interfaces.soap
72import ladon.interfaces.soap1172import ladon.interfaces.soap11
73import ladon.interfaces.jsonwsp73import ladon.interfaces.jsonwsp
74import ladon.interfaces.jsonrpc10
7475
=== added file 'frameworks/python/src/ladon/interfaces/base.py'
--- frameworks/python/src/ladon/interfaces/base.py 1970-01-01 00:00:00 +0000
+++ frameworks/python/src/ladon/interfaces/base.py 2012-10-26 11:52:28 +0000
@@ -0,0 +1,147 @@
1# -*- coding: utf-8 -*-
2
3import inspect
4
5class BaseInterface(object):
6 """All interface implementations must descend from BaseInterface. The interface
7 implementation can be thought of as an aggregator that ties the three basic functions
8 of a web service protocol together:
9
10 1. ServiceDescriptor - A generator class that can provide a description for the service.
11 ie. WSDL for soap.
12 2. BaseRequestHandler - A handler that can parse the raw request objects from the client and
13 convert them into a so-called req_dict (Request dictionary)
14 3. BaseResponseHandler - A handler that can process the res_dict (Result Dictionary) of a
15 service call and build a response object that fit's the protocol being implemented.
16 4. BaseFaultHandler - A handler that can convert a ladon.exceptions.service.ServiceFault object
17 to a fault response that fits the interface.
18
19 """
20 def __init__(self,sinfo,**kw):
21 self._sinfo = sinfo
22 sd = ServiceDescriptor()
23 if 'service_descriptor' in kw:
24 sd = kw['service_descriptor']
25 if inspect.getmro(sd).count(ServiceDescriptor):
26 self._service_descriptor = sd()
27 if 'request_handler' in kw:
28 req_handler = kw['request_handler']
29 if inspect.getmro(req_handler).count(BaseRequestHandler):
30 self._request_handler = req_handler()
31 if 'response_handler' in kw:
32 resp_handler = kw['response_handler']
33 if inspect.getmro(resp_handler).count(BaseResponseHandler):
34 self._response_handler = resp_handler()
35 if 'fault_handler' in kw:
36 fault_handler = kw['fault_handler']
37 if inspect.getmro(fault_handler).count(BaseFaultHandler):
38 self._fault_handler = fault_handler()
39
40 @staticmethod
41 def _interface_name(typ):
42 return None
43
44 @staticmethod
45 def _accept_basetype(typ):
46 return False
47
48 @staticmethod
49 def _accept_list():
50 return False
51
52 @staticmethod
53 def _accept_dict():
54 return False
55
56 def service_info(self):
57 return self._sinfo
58
59 def parse_request(self,soap_body,encoding='UTF-8'):
60 return self._request_handler.parse_request(soap_body,self._sinfo,encoding)
61
62 def build_response(self,res_dict,encoding='UTF-8'):
63 return self._response_handler.build_response(res_dict,self._sinfo,encoding)
64
65 def build_fault_response(self,exc,methodname=None,encoding='UTF-8'):
66 return self._fault_handler.build_fault_response(exc,self._sinfo,methodname,encoding)
67
68 def stringify_res_dict(self):
69 return self._response_handler._stringify_res_dict
70
71 def response_content_type(self):
72 return self._response_handler._content_type
73
74 def description(self,service_url,encoding='UTF-8'):
75 return self._service_descriptor.generate(
76 self._sinfo.servicename,
77 self._sinfo.servicenumber,
78 self._sinfo.typemanager,
79 self._sinfo.method_list(),
80 service_url,encoding)
81
82 def description_content_type(self):
83 return self._service_descriptor._content_type
84
85class ServiceDescriptor(object):
86
87 _content_type = 'text/plain'
88
89 def generate(self,servicename,servicenumber,typemanager,methodlist,encoding):
90 """
91 Implement the method that can generate a service description file for the
92 type of interface you are developing. Thus if you were developing a new
93 and better SOAP interface and a new and better service descriptor for it,
94 you will be implementing a WSDL generator in this method.
95
96 @param servicename This is a string containing the service class name
97 @param servicenumber An integer that reflects the order in which the service classes are parsed (useful for creating namespaces)
98 @param typemanager An instance of ladon.ladonizer.collection.TypeManager which manages the types used by the service
99 @param methodlist The list of methods exposed by the service
100 @param encoding The client-side controlled encoding to use in the descriptor
101
102 @rtype String representation of the service descriptor (ie. wsdl-formatted if it were SOAP)
103 """
104 return ''
105
106
107class BaseRequestHandler(object):
108
109 def parse_request(self,req,sinfo,encoding):
110 return {}
111
112
113class BaseResponseHandler(object):
114
115 """
116 This is the base class of all response handlers. To implement a new response handler
117 inherit it and overload the build_response() method so it implements the protocol you are
118 extending Ladon with.
119
120 set _stringify_res_dict to True if you want the Ladon dispatcher to convert all result
121 values to unicode regardless of their defined types during ladonization. This can be useful
122 in some interfaces. ie. SOAP where all values are presented the same way inside XML tags no
123 matter if it is a string or a number (example: <name>string</name><age>37</age>)
124 """
125 _content_type = 'text/plain'
126 _stringify_res_dict = False
127
128 def build_response(self,method_result,sinfo,encoding):
129 return ''
130
131class BaseFaultHandler(object):
132
133 """
134 This is the base class of all fault handlers. To implement a new fault handler
135 inherit it and overload the build_fault_response() method so it implements the
136 protocol you are extending Ladon with.
137
138 set _stringify_res_dict to True if you want the Ladon dispatcher to convert all result
139 values to unicode regardless of their defined types during ladonization. This can be useful
140 in some interfaces. ie. SOAP where all values are presented the same way inside XML tags no
141 matter if it is a string or a number (example: <name>string</name><age>37</age>)
142 """
143 _content_type = 'text/plain'
144 _stringify_res_dict = False
145
146 def build_fault_response(self,exc,sinfo,methodname,encoding):
147 return ''
0148
=== removed 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 1970-01-01 00:00:00 +0000
@@ -1,147 +0,0 @@
1# -*- coding: utf-8 -*-
2
3import inspect
4
5class BaseInterface(object):
6 """All interface implementations must descend from BaseInterface. The interface
7 implementation can be thought of as an aggregator that ties the three basic functions
8 of a web service protocol together:
9
10 1. ServiceDescriptor - A generator class that can provide a description for the service.
11 ie. WSDL for soap.
12 2. BaseRequestHandler - A handler that can parse the raw request objects from the client and
13 convert them into a so-called req_dict (Request dictionary)
14 3. BaseResponseHandler - A handler that can process the res_dict (Result Dictionary) of a
15 service call and build a response object that fit's the protocol being implemented.
16 4. BaseFaultHandler - A handler that can convert a ladon.exceptions.service.ServiceFault object
17 to a fault response that fits the interface.
18
19 """
20 def __init__(self,sinfo,**kw):
21 self._sinfo = sinfo
22 sd = ServiceDescriptor()
23 if 'service_descriptor' in kw:
24 sd = kw['service_descriptor']
25 if inspect.getmro(sd).count(ServiceDescriptor):
26 self._service_descriptor = sd()
27 if 'request_handler' in kw:
28 req_handler = kw['request_handler']
29 if inspect.getmro(req_handler).count(BaseRequestHandler):
30 self._request_handler = req_handler()
31 if 'response_handler' in kw:
32 resp_handler = kw['response_handler']
33 if inspect.getmro(resp_handler).count(BaseResponseHandler):
34 self._response_handler = resp_handler()
35 if 'fault_handler' in kw:
36 fault_handler = kw['fault_handler']
37 if inspect.getmro(fault_handler).count(BaseFaultHandler):
38 self._fault_handler = fault_handler()
39
40 @staticmethod
41 def _interface_name(typ):
42 return None
43
44 @staticmethod
45 def _accept_basetype(typ):
46 return False
47
48 @staticmethod
49 def _accept_list():
50 return False
51
52 @staticmethod
53 def _accept_dict():
54 return False
55
56 def service_info(self):
57 return self._sinfo
58
59 def parse_request(self,soap_body,encoding='UTF-8'):
60 return self._request_handler.parse_request(soap_body,self._sinfo,encoding)
61
62 def build_response(self,res_dict,encoding='UTF-8'):
63 return self._response_handler.build_response(res_dict,self._sinfo,encoding)
64
65 def build_fault_response(self,exc,methodname=None,encoding='UTF-8'):
66 return self._fault_handler.build_fault_response(exc,self._sinfo,methodname,encoding)
67
68 def stringify_res_dict(self):
69 return self._response_handler._stringify_res_dict
70
71 def response_content_type(self):
72 return self._response_handler._content_type
73
74 def description(self,service_url,encoding='UTF-8'):
75 return self._service_descriptor.generate(
76 self._sinfo.servicename,
77 self._sinfo.servicenumber,
78 self._sinfo.typemanager,
79 self._sinfo.method_list(),
80 service_url,encoding)
81
82 def description_content_type(self):
83 return self._service_descriptor._content_type
84
85class ServiceDescriptor(object):
86
87 _content_type = 'text/plain'
88
89 def generate(self,servicename,servicenumber,typemanager,methodlist,encoding):
90 """
91 Implement the method that can generate a service description file for the
92 type of interface you are developing. Thus if you were developing a new
93 and better SOAP interface and a new and better service descriptor for it,
94 you will be implementing a WSDL generator in this method.
95
96 @param servicename This is a string containing the service class name
97 @param servicenumber An integer that reflects the order in which the service classes are parsed (useful for creating namespaces)
98 @param typemanager An instance of ladon.ladonizer.collection.TypeManager which manages the types used by the service
99 @param methodlist The list of methods exposed by the service
100 @param encoding The client-side controlled encoding to use in the descriptor
101
102 @rtype String representation of the service descriptor (ie. wsdl-formatted if it were SOAP)
103 """
104 return ''
105
106
107class BaseRequestHandler(object):
108
109 def parse_request(self,req,sinfo,encoding):
110 return {}
111
112
113class BaseResponseHandler(object):
114
115 """
116 This is the base class of all response handlers. To implement a new response handler
117 inherit it and overload the build_response() method so it implements the protocol you are
118 extending Ladon with.
119
120 set _stringify_res_dict to True if you want the Ladon dispatcher to convert all result
121 values to unicode regardless of their defined types during ladonization. This can be useful
122 in some interfaces. ie. SOAP where all values are presented the same way inside XML tags no
123 matter if it is a string or a number (example: <name>string</name><age>37</age>)
124 """
125 _content_type = 'text/plain'
126 _stringify_res_dict = False
127
128 def build_response(self,method_result,sinfo,encoding):
129 return ''
130
131class BaseFaultHandler(object):
132
133 """
134 This is the base class of all fault handlers. To implement a new fault handler
135 inherit it and overload the build_fault_response() method so it implements the
136 protocol you are extending Ladon with.
137
138 set _stringify_res_dict to True if you want the Ladon dispatcher to convert all result
139 values to unicode regardless of their defined types during ladonization. This can be useful
140 in some interfaces. ie. SOAP where all values are presented the same way inside XML tags no
141 matter if it is a string or a number (example: <name>string</name><age>37</age>)
142 """
143 _content_type = 'text/plain'
144 _stringify_res_dict = False
145
146 def build_fault_response(self,exc,sinfo,methodname,encoding):
147 return ''
1480
=== 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-26 11:52:28 +0000
@@ -0,0 +1,205 @@
1# -*- coding: utf-8 -*-
2
3from ladon.interfaces.base import BaseInterface,ServiceDescriptor,BaseRequestHandler,BaseResponseHandler,BaseFaultHandler
4from ladon.interfaces import expose
5from ladon.compat import type_to_jsontype,pytype_support,PORTABLE_STRING
6import json,sys,traceback
7from ladon.exceptions.service import ServiceFault
8from ladon.exceptions.base import LadonException
9
10def _add_passback_params(res_dict,passback_dict):
11 merged_dict = dict((k,v) for (k,v) in res_dict.items())
12 for k,v in passback_dict.items():
13 merged_dict[k] = v
14 return merged_dict
15
16class RequestPropFault(ServiceFault):
17 def __init__(self,prop, passback_dict):
18 self.prop = prop
19 self.passback_dict = passback_dict
20 super(RequestPropFault,self).__init__('service','Request doesn\'t have "%s" property.' % self.prop, None, 2)
21
22 def __str__(self):
23 return self.faultstring
24
25class RequestParamFault(ServiceFault):
26 def __init__(self,param, passback_dict):
27 self.param = param
28 self.passback_dict = passback_dict
29 super(RequestParamFault,self).__init__('service','Request doesn\'t have "%s" parameter.' % self.param, None, 2)
30
31 def __str__(self):
32 return self.faultstring
33
34class JSONRPCServiceDescriptor(ServiceDescriptor):
35 javascript_type_map = type_to_jsontype
36 version = '1.0'
37 _content_type = 'application/json'
38
39 def generate(self,servicename,servicenumber,typemanager,methodlist,service_url,encoding):
40 type_dict = typemanager.type_dict
41 type_order = typemanager.type_order
42
43 def map_type(typ):
44 if typ in JSONRPCServiceDescriptor.javascript_type_map:
45 return JSONRPCServiceDescriptor.javascript_type_map[typ]
46 else:
47 return typ.__name__
48
49 desc = {
50 'servicename': servicename,
51 'url': service_url,
52 'type': 'jsonrpc/description',
53 'version': self.version,
54 'types': {},
55 'methods': {}
56 }
57
58 types = desc['types']
59 for typ in type_order:
60 if type(typ)==dict:
61 desc_type = {}
62 types[typ['name']] = desc_type
63 for k,v,props in typ['attributes']:
64 if type(v)==list:
65 desc_type_val = [map_type(v[0])]
66 else:
67 desc_type_val = map_type(v)
68 desc_type[k] = desc_type_val
69
70 methods = desc['methods']
71 for m in methodlist:
72 desc_mparams = {}
73 order = 1
74 desc_method = {'params': desc_mparams, 'doc_lines': m._method_doc}
75 methods[m.name()] = desc_method
76 for arg in m.args():
77 if [list,tuple].count(type(arg['type'])):
78 desc_param_type = [map_type(arg['type'][0])]
79 else:
80 desc_param_type = map_type(arg['type'])
81 desc_mparams[arg['name']] = {
82 "type": desc_param_type,
83 "def_order": order,
84 "optional": arg['optional'],
85 }
86 if 'doc' in arg:
87 desc_mparams[arg['name']]["doc_lines"] = arg['doc']
88 else:
89 desc_mparams[arg['name']]["doc_lines"] = []
90 order += 1
91
92 if [list,tuple].count(type(m._rtype)):
93 desc_rtype = [map_type(m._rtype[0])]
94 else:
95 desc_rtype = map_type(m._rtype)
96 desc_method['ret_info'] = {
97 'type': desc_rtype,
98 'doc_lines': m._rtype_doc
99 }
100
101 if sys.version_info[0]>=3:
102 return json.dumps(desc)
103 return json.dumps(desc,encoding=encoding)
104
105class JSONRPCRequestHandler(BaseRequestHandler):
106 def parse_request(self,json_body,sinfo,encoding):
107 def parse_number(x):
108 return PORTABLE_STRING(x)
109 def parse_constant(x):
110 if x=='null':
111 return PORTABLE_STRING("None")
112 return PORTABLE_STRING(x)
113 req_dict = json.loads(PORTABLE_STRING(json_body,encoding), parse_int=parse_number, parse_float=parse_number, \
114 parse_constant=parse_constant)
115 passback_dict = self.get_passback_params(req_dict)
116 if 'method' not in req_dict:
117 raise RequestPropFault('method',passback_dict)
118 if 'params' not in req_dict:
119 raise RequestPropFault('params',passback_dict)
120 if 'id' not in req_dict:
121 raise RequestPropFault('id',passback_dict)
122 minfo = sinfo.methods[req_dict['method']]
123 if (req_dict['params'] is None or len(req_dict['params']) == 0) and len(minfo.args()) > 0:
124 raise RequestParamFault(minfo.args()[0]['name'],passback_dict)
125 else:
126 for arg in minfo.args():
127 isgiven = False
128 for param in req_dict['params']:
129 if param == arg['name']:
130 isgiven = True
131 if not isgiven:
132 raise RequestParamFault(arg['name'],passback_dict)
133 req_dict['args'] = req_dict['params']
134 req_dict['methodname'] = req_dict['method']
135 del req_dict['params']
136 del req_dict['method']
137 return req_dict
138
139 def get_passback_params(self, req_dict):
140 if 'id' in req_dict:
141 return {'id': req_dict['id']}
142 else:
143 return {}
144
145class JSONRPCResponseHandler(BaseResponseHandler):
146 _content_type = 'application/json'
147 _stringify_res_dict = False
148
149 def build_response(self,res_dict,sinfo,encoding):
150 res_dict['error'] = None
151 del res_dict['servicenumber']
152 del res_dict['servicename']
153 del res_dict['method']
154 return json.dumps(res_dict,ensure_ascii=False).encode(encoding)
155
156class JSONRPCFaultHandler(BaseFaultHandler):
157 _content_type = 'application/json'
158 _stringify_res_dict = False
159
160 def build_fault_response(self,service_exc,sinfo,methodname,encoding):
161 if service_exc.detail:
162 detail = service_exc.detail
163 else:
164 detail = traceback.format_exc()
165 detail = detail.replace('\r\n','\n').split('\n')
166 fault_dict = {
167 'result': None,
168 'error': {
169 'code': service_exc.faultcode,
170 'string': service_exc.faultstring,
171 'detail': detail,
172 'filename': service_exc.mod,
173 'lineno': service_exc.lineno
174 },
175 }
176 if hasattr(service_exc,'passback_dict'):
177 fault_dict = _add_passback_params(fault_dict,service_exc.passback_dict)
178 return json.dumps(fault_dict,ensure_ascii=False).encode(encoding)
179
180@expose
181class JSONRPCInterface(BaseInterface):
182 def __init__(self,sinfo,**kw):
183 def_kw = {
184 'service_descriptor': JSONRPCServiceDescriptor,
185 'request_handler': JSONRPCRequestHandler,
186 'response_handler': JSONRPCResponseHandler,
187 'fault_handler': JSONRPCFaultHandler}
188 def_kw.update(kw)
189 BaseInterface.__init__(self,sinfo,**def_kw)
190
191 @staticmethod
192 def _interface_name():
193 return 'jsonrpc10'
194
195 @staticmethod
196 def _accept_basetype(typ):
197 return pytype_support.count(typ)>0
198
199 @staticmethod
200 def _accept_list():
201 return True
202
203 @staticmethod
204 def _accept_dict():
205 return False
0\ No newline at end of file206\ No newline at end of file
1207
=== 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-26 11:52:28 +0000
@@ -178,6 +178,8 @@
178 res_dict = self.result_to_dict(method,result,tc,export_dict['response_attachments'],log_line=log_line)178 res_dict = self.result_to_dict(method,result,tc,export_dict['response_attachments'],log_line=log_line)
179 if 'mirror' in req_dict:179 if 'mirror' in req_dict:
180 res_dict['reflection'] = req_dict['mirror']180 res_dict['reflection'] = req_dict['mirror']
181 if 'id' in req_dict:
182 res_dict['id'] = req_dict['id']
181 response = self.iface.build_response(res_dict,encoding=self.response_encoding)183 response = self.iface.build_response(res_dict,encoding=self.response_encoding)
182 if self.logging:184 if self.logging:
183 debug('\t%s' % ('\t'.join(log_line)))185 debug('\t%s' % ('\t'.join(log_line)))
184186
=== 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-26 11:52:28 +0000
@@ -9,7 +9,9 @@
9 this_cls_attrs = dir(cls)9 this_cls_attrs = dir(cls)
10 res = []10 res = []
11 for attr in this_cls_attrs:11 for attr in this_cls_attrs:
12 if base_attrs.count(attr) or (exclude_methods and inspect.ismethod(getattr(cls,attr))):12 # attr == '__qualname__'
13 # Python 3.3 __qualname__ (PEP 3155 -- Qualified name for classes and functions) fix
14 if base_attrs.count(attr) or (exclude_methods and inspect.ismethod(getattr(cls,attr))) or attr == '__qualname__':
13 continue15 continue
14 res += [attr]16 res += [attr]
15 return res17 return res
1618
=== 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-26 11:52:28 +0000
@@ -9,6 +9,7 @@
9 'stringtests',9 'stringtests',
10 'typetests',10 'typetests',
11 'attachmenttests',11 'attachmenttests',
12 'jsonrpc10',
12 'collectiontest'13 'collectiontest'
13]14]
1415
1516
=== 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-26 11:52:28 +0000
@@ -0,0 +1,54 @@
1from ladon.ladonizer import ladonize
2from ladon.compat import PORTABLE_BYTES,PORTABLE_STRING
3import binascii
4import sys
5
6class JsonPrc10Service(object):
7 @ladonize(rtype=PORTABLE_STRING)
8 def return_string(self):
9 if sys.version_info[0]>=3:
10 return 'Yo!!!'
11 return PORTABLE_STRING('Yo!!!','utf-8')
12
13 @ladonize(rtype=int)
14 def return_int(self):
15 return 11
16
17 @ladonize(rtype=float)
18 def return_float(self):
19 return 11.11
20
21 @ladonize(rtype=bool)
22 def return_bool(self):
23 return True
24
25 @ladonize(rtype=PORTABLE_BYTES)
26 def return_bytes(self):
27 if sys.version_info[0]>=3:
28 return PORTABLE_BYTES('Yo!!!','utf-8')
29 return 'Yo!!!'
30
31 @ladonize(PORTABLE_STRING, rtype=PORTABLE_STRING)
32 def passback_string(self,arg):
33 return arg
34
35 @ladonize(int,rtype=int)
36 def passback_int(self,arg):
37 return arg
38
39 @ladonize(float,rtype=float)
40 def passback_float(self,arg):
41 return arg
42
43 @ladonize(bool,rtype=bool)
44 def passback_bool(self,arg):
45 return arg
46
47 @ladonize(PORTABLE_BYTES,rtype=PORTABLE_BYTES)
48 def passback_bytes(self,arg):
49 return arg
50
51 @ladonize(int,float,PORTABLE_STRING,rtype=bool)
52 def params(self,arg0,arg1,arg2):
53 return True
54
0\ No newline at end of file55\ No newline at end of file
156
=== 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-26 11:52:28 +0000
@@ -0,0 +1,254 @@
1# -*- coding: utf-8 -*-
2
3import unittest
4import servicerunner
5import sys
6import xml.dom.minidom as md
7if sys.version_info[0]>=3:
8 from urllib.parse import urlparse,splitport
9 from http.client import HTTPConnection, HTTPSConnection
10else:
11 from urllib import splitport
12 from urlparse import urlparse
13 from httplib import HTTPConnection, HTTPSConnection
14import sys,json
15from ladon.compat import PORTABLE_BYTES,PORTABLE_STRING,PORTABLE_STRING_TYPES
16from testladon import HTTPRequestPoster
17from testladon import str_to_portable_string
18import binascii
19
20class JsonRpc10Tests(unittest.TestCase):
21
22 def setUp(self):
23 self.post_helper = HTTPRequestPoster('http://localhost:2376/JsonPrc10Service')
24
25 def test_get_string(self):
26 req = {'method':'return_string','params':None,'id':0}
27 jreq = json.dumps(req)
28
29 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
30
31 self.assertEqual(status, 200)
32 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
33 expected_result = str_to_portable_string('Yo!!!')
34 self.assertEqual(res['result'], expected_result)
35
36 def test_get_int(self):
37 req = {'method':'return_int','params':None,'id':0}
38 jreq = json.dumps(req)
39
40 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
41
42 self.assertEqual(status, 200)
43 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
44 expected_result = 11
45 self.assertEqual(res['result'], expected_result)
46
47 def test_get_float(self):
48 req = {'method':'return_float','params':None,'id':0}
49 jreq = json.dumps(req)
50
51 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
52
53 self.assertEqual(status, 200)
54 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
55 expected_result = 11.11
56 self.assertEqual(res['result'], expected_result)
57
58 def test_get_bool(self):
59 req = {'method':'return_bool','params':None,'id':0}
60 jreq = json.dumps(req)
61
62 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
63
64 self.assertEqual(status, 200)
65 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
66 expected_result = True
67 self.assertEqual(res['result'], expected_result)
68
69 def test_get_bytes(self):
70 req = {'method':'return_bytes','params':None,'id':0}
71 jreq = json.dumps(req)
72
73 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
74
75 self.assertEqual(status, 200)
76 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
77 expected_result = 'Yo!!!'
78 self.assertEqual(res['result'], expected_result)
79
80 def test_passback_string(self):
81 val = 'Yo!!!'
82 req = {'method':'passback_string','params':{'arg':val},'id':0}
83 jreq = json.dumps(req)
84
85 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
86
87 self.assertEqual(status, 200)
88 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
89 expected_result = str_to_portable_string(val)
90 self.assertEqual(res['result'], expected_result)
91
92 def test_passback_int(self):
93 val = 11
94 req = {'method':'passback_int','params':{'arg':val},'id':0}
95 jreq = json.dumps(req)
96
97 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
98
99 self.assertEqual(status, 200)
100 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
101 expected_result = val
102 self.assertEqual(res['result'], expected_result)
103
104 def test_passback_float(self):
105 val = 11.11
106 req = {'method':'passback_float','params':{'arg':val},'id':0}
107 jreq = json.dumps(req)
108
109 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
110
111 self.assertEqual(status, 200)
112 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
113 expected_result = val
114 self.assertEqual(res['result'], expected_result)
115
116 def test_passback_bool(self):
117 val = True
118 req = {'method':'passback_bool','params':{'arg':val},'id':0}
119 jreq = json.dumps(req)
120
121 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
122
123 self.assertEqual(status, 200)
124 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
125 expected_result = val
126 self.assertEqual(res['result'], expected_result)
127
128 def test_passback_bytes(self):
129 val = 'Yo!!!'
130 req = {'method':'passback_bytes','params':{'arg':val},'id':0}
131 jreq = json.dumps(req)
132
133 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
134
135 self.assertEqual(status, 200)
136 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
137 self.assertEqual(res['result'], val)
138
139 def test_validate_request_response_structure(self):
140 req = {}
141 jreq = json.dumps(req)
142
143 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
144
145 self.assertEqual(status, 200)
146 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
147
148 self.assertIs(type(res['error']), dict)
149 self.assertTrue('id' is not res)
150 self.assertIs(res['result'], None)
151 self.assertTrue('"method"' in res['error']['string'])
152
153 req = {'method':'passback_string'}
154 jreq = json.dumps(req)
155
156 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
157
158 self.assertEqual(status, 200)
159 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
160 self.assertIs(type(res['error']), dict)
161 self.assertTrue('id' is not res)
162 self.assertIs(res['result'], None)
163 self.assertTrue('"params"' in res['error']['string'])
164
165 req = {'method':'passback_string','params':None}
166 jreq = json.dumps(req)
167
168 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
169
170 self.assertEqual(status, 200)
171 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
172 self.assertIs(type(res['error']), dict)
173 self.assertTrue('id' is not res)
174 self.assertIs(res['result'], None)
175 self.assertTrue('"id"' in res['error']['string'])
176
177 req = {'method':'passback_string','params':None,'id':0}
178 jreq = json.dumps(req)
179
180 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
181
182 self.assertEqual(status, 200)
183 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
184
185 self.assertIs(type(res['error']), dict)
186 self.assertEqual(res['id'], '0')
187 self.assertIs(res['result'], None)
188
189 req = {'method':'passback_string','params':None,'id':'0'}
190 jreq = json.dumps(req)
191
192 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
193
194 self.assertEqual(status, 200)
195 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
196
197 self.assertIs(type(res['error']), dict)
198 self.assertEqual(res['id'], '0')
199 self.assertIs(res['result'], None)
200
201 req = {'method':'passback_string','params':{'arg': 'Yo!!!'},'id':0}
202 jreq = json.dumps(req)
203
204 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
205
206 self.assertEqual(status, 200)
207 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
208
209 self.assertIs(res['error'], None)
210 self.assertEqual(res['id'], '0')
211 self.assertEqual(res['result'], 'Yo!!!')
212
213 req = {'method':'params','params':{},'id':0}
214 jreq = json.dumps(req)
215
216 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
217
218 self.assertEqual(status, 200)
219 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
220
221 self.assertIs(type(res['error']), dict)
222 self.assertEqual(res['id'], '0')
223 self.assertTrue('"arg0"' in res['error']['string'])
224
225 req = {'method':'params','params':{'arg0':11},'id':0}
226 jreq = json.dumps(req)
227
228 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
229
230 self.assertEqual(status, 200)
231 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
232
233 self.assertIs(type(res['error']), dict)
234 self.assertEqual(res['id'], '0')
235 self.assertTrue('"arg1"' in res['error']['string'])
236
237 req = {'method':'params','params':{'arg0':11, 'arg1':11.11},'id':0}
238 jreq = json.dumps(req)
239
240 status,reason,resdata = self.post_helper.post_request(jreq.encode('utf-8'),extra_path='jsonrpc10',encoding='utf-8')
241
242 self.assertEqual(status, 200)
243 res = json.loads(PORTABLE_STRING(resdata,'utf-8'))
244
245 self.assertIs(type(res['error']), dict)
246 self.assertEqual(res['id'], '0')
247 self.assertTrue('"arg2"' in res['error']['string'])
248
249if __name__ == '__main__':
250 import servicerunner
251 servicerunner
252 service_thread = servicerunner.serve_test_service(as_thread=True)
253 unittest.main(exit=False)
254 service_thread.server.shutdown()
0\ No newline at end of file255\ No newline at end of file

Subscribers

People subscribed via source and target branches

to all changes: